diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index cc80b3fc4f..dd3fb1dce0 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -262,11 +262,13 @@ pgbench options> dbname>
-
-
+
+
Add the specified builtin script to the list of executed scripts.
+ An optional integer weight after @> allows to adjust the
+ probability of drawing the script. If not specified, it is set to 1.
Available builtin scripts are: tpcb-like>,
simple-update> and select-only>.
Unambiguous prefixes of builtin names are accepted.
@@ -322,12 +324,14 @@ pgbench options> dbname>
-
-
+
+
Add a transaction script read from filename> to
the list of executed scripts.
+ An optional integer weight after @> allows to adjust the
+ probability of drawing the test.
See below for details.
@@ -687,9 +691,13 @@ pgbench options> dbname>
What is the Transaction> Actually Performed in pgbench?
- Pgbench executes test scripts chosen randomly from a specified list.
+ pgbench> executes test scripts chosen randomly
+ from a specified list.
They include built-in scripts with
@@ -1194,12 +1202,11 @@ number of clients: 10
number of threads: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
+latency average = 15.844 ms
+latency stddev = 2.715 ms
tps = 618.764555 (including connections establishing)
tps = 622.977698 (excluding connections establishing)
-SQL script 1: <builtin: TPC-B (sort of)>
- - 10000 transactions (100.0% of total, tps = 618.764555)
- - latency average = 15.844 ms
- - latency stddev = 2.715 ms
+script statistics:
- statement latencies in milliseconds:
0.004386 \set nbranches 1 * :scale
0.001343 \set ntellers 10 * :scale
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index d1f1ad2837..ecabff0522 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -38,6 +38,7 @@
#include "portability/instr_time.h"
#include
+#include
#include
#include
#include
@@ -180,6 +181,8 @@ char *login = NULL;
char *dbName;
const char *progname;
+#define WSEP '@' /* weight separator */
+
volatile bool timer_exceeded = false; /* flag from signal handler */
/* variable definitions */
@@ -298,26 +301,30 @@ typedef struct
SimpleStats stats; /* time spent in this command */
} Command;
-static struct
+typedef struct ParsedScript
{
- const char *name;
+ const char *desc;
+ int weight;
Command **commands;
- StatsData stats;
-} sql_script[MAX_SCRIPTS]; /* SQL script files */
+ StatsData stats;
+} ParsedScript;
+
+static ParsedScript sql_script[MAX_SCRIPTS]; /* SQL script files */
static int num_scripts; /* number of scripts in sql_script[] */
static int num_commands = 0; /* total number of Command structs */
+static int64 total_weight = 0;
+
static int debug = 0; /* debug flag */
-/* Define builtin test scripts */
-#define N_BUILTIN 3
-static struct
+/* Builtin test scripts */
+typedef struct BuiltinScript
{
char *name; /* very short name for -b ... */
char *desc; /* short description */
- char *commands; /* actual pgbench script */
-}
+ char *script; /* actual pgbench script */
+} BuiltinScript;
- builtin_script[] =
+static BuiltinScript builtin_script[] =
{
{
"tpcb-like",
@@ -393,9 +400,9 @@ usage(void)
" --tablespace=TABLESPACE create tables in the specified tablespace\n"
" --unlogged-tables create tables as unlogged tables\n"
"\nOptions to select what to run:\n"
- " -b, --builtin=NAME add buitin script (use \"-b list\" to display\n"
- " available scripts)\n"
- " -f, --file=FILENAME add transaction script from FILENAME\n"
+ " -b, --builtin=NAME[@W] add builtin script NAME weighted at W (default: 1)\n"
+ " (use \"-b list\" to list available scripts)\n"
+ " -f, --file=FILENAME[@W] add script FILENAME weighted at W (default: 1)\n"
" -N, --skip-some-updates skip updates of pgbench_tellers and pgbench_branches\n"
" (same as \"-b simple-update\")\n"
" -S, --select-only perform SELECT-only transactions\n"
@@ -1313,13 +1320,23 @@ clientDone(CState *st, bool ok)
return false; /* always false */
}
+/* return a script number with a weighted choice. */
static int
chooseScript(TState *thread)
{
+ int i = 0;
+ int64 w;
+
if (num_scripts == 1)
return 0;
- return getrand(thread, 0, num_scripts - 1);
+ w = getrand(thread, 0, total_weight - 1);
+ do
+ {
+ w -= sql_script[i++].weight;
+ } while (w >= 0);
+
+ return i - 1;
}
/* return false iff client should be disconnected */
@@ -1493,7 +1510,7 @@ top:
commands = sql_script[st->use_file].commands;
if (debug)
fprintf(stderr, "client %d executing script \"%s\"\n", st->id,
- sql_script[st->use_file].name);
+ sql_script[st->use_file].desc);
st->is_throttled = false;
/*
@@ -2632,34 +2649,33 @@ read_line_from_file(FILE *fd)
}
/*
- * Given a file name, read it and return the array of Commands contained
- * therein. "-" means to read stdin.
+ * Given a file name, read it and return its ParsedScript representation. "-"
+ * means to read stdin.
*/
-static Command **
+static ParsedScript
process_file(char *filename)
{
#define COMMANDS_ALLOC_NUM 128
-
- Command **my_commands;
+ ParsedScript ps;
FILE *fd;
int lineno,
index;
char *buf;
int alloc_num;
- alloc_num = COMMANDS_ALLOC_NUM;
- my_commands = (Command **) pg_malloc(sizeof(Command *) * alloc_num);
-
if (strcmp(filename, "-") == 0)
fd = stdin;
else if ((fd = fopen(filename, "r")) == NULL)
{
fprintf(stderr, "could not open file \"%s\": %s\n",
filename, strerror(errno));
- pg_free(my_commands);
- return NULL;
+ exit(1);
}
+ alloc_num = COMMANDS_ALLOC_NUM;
+ ps.commands = (Command **) pg_malloc(sizeof(Command *) * alloc_num);
+ ps.desc = filename;
+
lineno = 0;
index = 0;
@@ -2676,35 +2692,36 @@ process_file(char *filename)
if (command == NULL)
continue;
- my_commands[index] = command;
+ ps.commands[index] = command;
index++;
if (index >= alloc_num)
{
alloc_num += COMMANDS_ALLOC_NUM;
- my_commands = pg_realloc(my_commands, sizeof(Command *) * alloc_num);
+ ps.commands = pg_realloc(ps.commands, sizeof(Command *) * alloc_num);
}
}
fclose(fd);
- my_commands[index] = NULL;
+ ps.commands[index] = NULL;
- return my_commands;
+ return ps;
}
-static Command **
-process_builtin(const char *tb, const char *source)
+/* Parse the given builtin script and return the parsed representation */
+static ParsedScript
+process_builtin(BuiltinScript *bi)
{
-#define COMMANDS_ALLOC_NUM 128
-
- Command **my_commands;
int lineno,
index;
char buf[BUFSIZ];
int alloc_num;
+ char *tb = bi->script;
+ ParsedScript ps;
alloc_num = COMMANDS_ALLOC_NUM;
- my_commands = (Command **) pg_malloc(sizeof(Command *) * alloc_num);
+ ps.desc = bi->desc;
+ ps.commands = (Command **) pg_malloc(sizeof(Command *) * alloc_num);
lineno = 0;
index = 0;
@@ -2714,6 +2731,7 @@ process_builtin(const char *tb, const char *source)
char *p;
Command *command;
+ /* buffer overflow check? */
p = buf;
while (*tb && *tb != '\n')
*p++ = *tb++;
@@ -2728,58 +2746,58 @@ process_builtin(const char *tb, const char *source)
lineno += 1;
- command = process_commands(buf, source, lineno);
+ command = process_commands(buf, bi->desc, lineno);
if (command == NULL)
continue;
- my_commands[index] = command;
+ ps.commands[index] = command;
index++;
if (index >= alloc_num)
{
alloc_num += COMMANDS_ALLOC_NUM;
- my_commands = pg_realloc(my_commands, sizeof(Command *) * alloc_num);
+ ps.commands = pg_realloc(ps.commands, sizeof(Command *) * alloc_num);
}
}
- my_commands[index] = NULL;
+ ps.commands[index] = NULL;
- return my_commands;
+ return ps;
}
+/* show available builtin scripts */
static void
listAvailableScripts(void)
{
int i;
fprintf(stderr, "Available builtin scripts:\n");
- for (i = 0; i < N_BUILTIN; i++)
+ for (i = 0; i < lengthof(builtin_script); i++)
fprintf(stderr, "\t%s\n", builtin_script[i].name);
fprintf(stderr, "\n");
}
-/* return builtin script "name" if unambiguous */
-static char *
-findBuiltin(const char *name, char **desc)
+/* return builtin script "name" if unambiguous, of fails if not found */
+static BuiltinScript *
+findBuiltin(const char *name)
{
int i,
found = 0,
len = strlen(name);
- char *commands = NULL;
+ BuiltinScript *result = NULL;
- for (i = 0; i < N_BUILTIN; i++)
+ for (i = 0; i < lengthof(builtin_script); i++)
{
if (strncmp(builtin_script[i].name, name, len) == 0)
{
- *desc = builtin_script[i].desc;
- commands = builtin_script[i].commands;
+ result = &builtin_script[i];
found++;
}
}
/* ok, unambiguous result */
if (found == 1)
- return commands;
+ return result;
/* error cases */
if (found == 0)
@@ -2792,13 +2810,61 @@ findBuiltin(const char *name, char **desc)
exit(1);
}
-static void
-addScript(const char *name, Command **commands)
+/*
+ * Determine the weight specification from a script option (-b, -f), if any,
+ * and return it as an integer (1 is returned if there's no weight). The
+ * script name is returned in *script as a malloc'd string.
+ */
+static int
+parseScriptWeight(const char *option, char **script)
{
- if (commands == NULL ||
- commands[0] == NULL)
+ char *sep;
+ int weight;
+
+ if ((sep = strrchr(option, WSEP)))
{
- fprintf(stderr, "empty command list for script \"%s\"\n", name);
+ int namelen = sep - option;
+ long wtmp;
+ char *badp;
+
+ /* generate the script name */
+ *script = pg_malloc(namelen + 1);
+ strncpy(*script, option, namelen);
+ (*script)[namelen] = '\0';
+
+ /* process digits of the weight spec */
+ errno = 0;
+ wtmp = strtol(sep + 1, &badp, 10);
+ if (errno != 0 || badp == sep + 1 || *badp != '\0')
+ {
+ fprintf(stderr, "invalid weight specification: %s\n", sep);
+ exit(1);
+ }
+ if (wtmp > INT_MAX || wtmp <= 0)
+ {
+ fprintf(stderr,
+ "weight specification out of range (1 .. %u): " INT64_FORMAT "\n",
+ INT_MAX, (int64) wtmp);
+ exit(1);
+ }
+ weight = wtmp;
+ }
+ else
+ {
+ *script = pg_strdup(option);
+ weight = 1;
+ }
+
+ return weight;
+}
+
+/* append a script to the list of scripts to process */
+static void
+addScript(ParsedScript script, int weight)
+{
+ if (script.commands == NULL || script.commands[0] == NULL)
+ {
+ fprintf(stderr, "empty command list for script \"%s\"\n", script.desc);
exit(1);
}
@@ -2808,8 +2874,8 @@ addScript(const char *name, Command **commands)
exit(1);
}
- sql_script[num_scripts].name = name;
- sql_script[num_scripts].commands = commands;
+ sql_script[num_scripts] = script;
+ sql_script[num_scripts].weight = weight;
initStats(&sql_script[num_scripts].stats, 0.0);
num_scripts++;
}
@@ -2840,7 +2906,7 @@ printResults(TState *threads, StatsData *total, instr_time total_time,
(INSTR_TIME_GET_DOUBLE(conn_total_time) / nclients));
printf("transaction type: %s\n",
- num_scripts == 1 ? sql_script[0].name : "multiple scripts");
+ num_scripts == 1 ? sql_script[0].desc : "multiple scripts");
printf("scaling factor: %d\n", scale);
printf("query mode: %s\n", QUERYMODE[querymode]);
printf("number of clients: %d\n", nclients);
@@ -2894,19 +2960,24 @@ printResults(TState *threads, StatsData *total, instr_time total_time,
printf("tps = %f (including connections establishing)\n", tps_include);
printf("tps = %f (excluding connections establishing)\n", tps_exclude);
- /* Report per-command statistics */
- if (per_script_stats)
+ /* Report per-script/command statistics */
+ if (per_script_stats || latency_limit || is_latencies)
{
int i;
for (i = 0; i < num_scripts; i++)
{
- printf("SQL script %d: %s\n"
- " - " INT64_FORMAT " transactions (%.1f%% of total, tps = %f)\n",
- i + 1, sql_script[i].name,
- sql_script[i].stats.cnt,
- 100.0 * sql_script[i].stats.cnt / total->cnt,
- sql_script[i].stats.cnt / time_include);
+ if (num_scripts > 1)
+ printf("SQL script %d: %s\n"
+ " - weight = %d\n"
+ " - " INT64_FORMAT " transactions (%.1f%% of total, tps = %f)\n",
+ i + 1, sql_script[i].desc,
+ sql_script[i].weight,
+ sql_script[i].stats.cnt,
+ 100.0 * sql_script[i].stats.cnt / total->cnt,
+ sql_script[i].stats.cnt / time_include);
+ else
+ printf("script statistics:\n");
if (latency_limit)
printf(" - number of transactions skipped: " INT64_FORMAT " (%.3f%%)\n",
@@ -2914,7 +2985,8 @@ printResults(TState *threads, StatsData *total, instr_time total_time,
100.0 * sql_script[i].stats.skipped /
(sql_script[i].stats.skipped + sql_script[i].stats.cnt));
- printSimpleStats(" - latency", &sql_script[i].stats.latency);
+ if (num_scripts > 1)
+ printSimpleStats(" - latency", &sql_script[i].stats.latency);
/* Report per-command latencies */
if (is_latencies)
@@ -2997,7 +3069,7 @@ main(int argc, char **argv)
instr_time conn_total_time;
int64 latency_late = 0;
StatsData stats;
- char *desc;
+ int weight;
int i;
int nclients_dealt;
@@ -3045,6 +3117,8 @@ main(int argc, char **argv)
while ((c = getopt_long(argc, argv, "ih:nvp:dqb:SNc:j:Crs:t:T:U:lf:D:F:M:P:R:L:", long_options, &optindex)) != -1)
{
+ char *script;
+
switch (c)
{
case 'i':
@@ -3176,27 +3250,25 @@ main(int argc, char **argv)
exit(0);
}
- addScript(desc,
- process_builtin(findBuiltin(optarg, &desc), desc));
+ weight = parseScriptWeight(optarg, &script);
+ addScript(process_builtin(findBuiltin(script)), weight);
benchmarking_option_set = true;
internal_script_used = true;
break;
+
case 'S':
- addScript(desc,
- process_builtin(findBuiltin("select-only", &desc),
- desc));
+ addScript(process_builtin(findBuiltin("select-only")), 1);
benchmarking_option_set = true;
internal_script_used = true;
break;
case 'N':
- addScript(desc,
- process_builtin(findBuiltin("simple-update", &desc),
- desc));
+ addScript(process_builtin(findBuiltin("simple-update")), 1);
benchmarking_option_set = true;
internal_script_used = true;
break;
case 'f':
- addScript(optarg, process_file(optarg));
+ weight = parseScriptWeight(optarg, &script);
+ addScript(process_file(script), weight);
benchmarking_option_set = true;
break;
case 'D':
@@ -3334,12 +3406,16 @@ main(int argc, char **argv)
/* set default script if none */
if (num_scripts == 0 && !is_init_mode)
{
- addScript(desc,
- process_builtin(findBuiltin("tpcb-like", &desc), desc));
+ addScript(process_builtin(findBuiltin("tpcb-like")), 1);
benchmarking_option_set = true;
internal_script_used = true;
}
+ /* compute total_weight */
+ for (i = 0; i < num_scripts; i++)
+ /* cannot overflow: weight is 32b, total_weight 64b */
+ total_weight += sql_script[i].weight;
+
/* show per script stats if several scripts are used */
if (num_scripts > 1)
per_script_stats = true;
@@ -3745,7 +3821,7 @@ threadRun(void *arg)
commands = sql_script[st->use_file].commands;
if (debug)
fprintf(stderr, "client %d executing script \"%s\"\n", st->id,
- sql_script[st->use_file].name);
+ sql_script[st->use_file].desc);
if (!doCustom(thread, st, &aggs))
remains--; /* I've aborted */