Add --throttle to pgbench

Each client is throttled to the specified rate, which can be expressed in tps or in time (s, ms, us). Throttling is achieved by scheduling transactions along a Poisson-distribution.

This is an update of the previous proposal which fix a typo in the sgml documentation.

The use case of the option is to be able to generate a continuous gentle load for functional tests, eg in a practice session with students or for testing features on a laptop.

--
Fabien.
diff --git a/contrib/pgbench/pgbench.c b/contrib/pgbench/pgbench.c
index bc01f07..0142ed0 100644
--- a/contrib/pgbench/pgbench.c
+++ b/contrib/pgbench/pgbench.c
@@ -137,6 +137,12 @@ int			unlogged_tables = 0;
 double		sample_rate = 0.0;
 
 /*
+ * whether clients are throttled to a given rate, expressed as a delay in us.
+ * 0, the default means no throttling.
+ */
+int64		throttle = 0;
+
+/*
  * tablespace selection
  */
 char	   *tablespace = NULL;
@@ -204,6 +210,8 @@ typedef struct
 	int			nvariables;
 	instr_time	txn_begin;		/* used for measuring transaction latencies */
 	instr_time	stmt_begin;		/* used for measuring statement latencies */
+	int64		trigger;		/* previous/next throttling (us) */
+	bool		throttled;      /* whether current transaction was throttled */
 	int			use_file;		/* index in sql_files for this client */
 	bool		prepared[MAX_FILES];
 } CState;
@@ -361,6 +369,9 @@ usage(void)
 		   "  -S           perform SELECT-only transactions\n"
 	 "  -t NUM       number of transactions each client runs (default: 10)\n"
 		   "  -T NUM       duration of benchmark test in seconds\n"
+		   "  -H SPEC, --throttle SPEC\n"
+		   "               delay in second to throttle each client\n"
+		   "               sample specs: 0.025 40tps 25ms 25000us\n"
 		   "  -v           vacuum all four standard tables before tests\n"
 		   "\nCommon options:\n"
 		   "  -d             print debugging output\n"
@@ -1027,7 +1038,7 @@ top:
 			}
 		}
 
-		if (commands[st->state]->type == SQL_COMMAND)
+		if (!st->throttled && commands[st->state]->type == SQL_COMMAND)
 		{
 			/*
 			 * Read and discard the query result; note this is not included in
@@ -1049,26 +1060,54 @@ top:
 			discard_response(st);
 		}
 
+		/* some stuff done at the end */
 		if (commands[st->state + 1] == NULL)
 		{
-			if (is_connect)
+			/* disconnect if required and needed */
+			if (is_connect && st->con)
 			{
 				PQfinish(st->con);
 				st->con = NULL;
 			}
 
-			++st->cnt;
-			if ((st->cnt >= nxacts && duration <= 0) || timer_exceeded)
-				return clientDone(st, true);	/* exit success */
+			/* update transaction counter once, and possibly end */
+			if (!st->throttled)
+			{
+				++st->cnt;
+				if ((st->cnt >= nxacts && duration <= 0) || timer_exceeded)
+					return clientDone(st, true);	/* exit success */
+			}
+
+			/* handle throttling once, as the last post-transaction stuff */
+			if (throttle && !st->throttled)
+			{
+				/* compute delay to approximate a Poisson distribution
+				 * 1000000 => 13.8 .. 0 multiplier
+				 * if transactions are too slow or a given wait shorter than
+				 * a transaction, the next transaction will start right away.
+				 */
+				int64 wait = (int64)
+					throttle * -log(getrand(thread, 1, 1000000)/1000000.0);
+				st->trigger += wait;
+				st->sleeping = 1;
+				st->until = st->trigger;
+				st->throttled = true;
+				if (debug)
+					fprintf(stderr, "client %d throttling %d us\n",
+							st->id, (int) wait);
+				return true;
+			}
 		}
 
 		/* increment state counter */
 		st->state++;
 		if (commands[st->state] == NULL)
 		{
+			/* reset */
 			st->state = 0;
 			st->use_file = (int) getrand(thread, 0, num_files - 1);
 			commands = sql_files[st->use_file];
+			st->throttled = false;
 		}
 	}
 
@@ -2086,6 +2125,7 @@ main(int argc, char **argv)
 		{"unlogged-tables", no_argument, &unlogged_tables, 1},
 		{"sampling-rate", required_argument, NULL, 4},
 		{"aggregate-interval", required_argument, NULL, 5},
+		{"throttle", required_argument, NULL, 'H'},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -2152,7 +2192,7 @@ main(int argc, char **argv)
 	state = (CState *) pg_malloc(sizeof(CState));
 	memset(state, 0, sizeof(CState));
 
-	while ((c = getopt_long(argc, argv, "ih:nvp:dqSNc:j:Crs:t:T:U:lf:D:F:M:", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "ih:nvp:dqSNc:j:Crs:t:T:U:lf:D:F:M:H:", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -2307,6 +2347,26 @@ main(int argc, char **argv)
 					exit(1);
 				}
 				break;
+			case 'H':
+			{
+				/* get a double from the beginning of option value */
+				double throttle_value = atof(optarg);
+				if (throttle_value <= 0.0)
+				{
+					fprintf(stderr, "invalid throttle value: %s\n", optarg);
+					exit(1);
+				}
+				/* rough handling of possible units */
+				if (strstr(optarg, "us"))
+					throttle = (int64) throttle_value;
+				else if (strstr(optarg, "ms"))
+					throttle = (int64) (1000.0 * throttle_value);
+				else if (strstr(optarg, "tps"))
+					throttle = (int64) (1000000.0 / throttle_value);
+				else /* assume that default is in second */
+					throttle = (int64) (1000000.0 * throttle_value);
+			}
+				break;
 			case 0:
 				/* This covers long options which take no argument. */
 				break;
@@ -2533,6 +2593,18 @@ main(int argc, char **argv)
 	INSTR_TIME_SET_CURRENT(start_time);
 	srandom((unsigned int) INSTR_TIME_GET_MICROSEC(start_time));
 
+	/* initial throttling setup with regular increasing delays */
+	if (throttle)
+	{
+		int delay = throttle / nclients;
+		for (i=0; i<nclients; i++)
+		{
+			state[i].trigger = INSTR_TIME_GET_MICROSEC(start_time) + i*delay;
+			state[i].sleeping = 1;
+			state[i].until = state[i].trigger;
+		}
+	}
+
 	/* process builtin SQL scripts */
 	switch (ttype)
 	{
diff --git a/doc/src/sgml/pgbench.sgml b/doc/src/sgml/pgbench.sgml
index 79b4baf..b5c6c1c 100644
--- a/doc/src/sgml/pgbench.sgml
+++ b/doc/src/sgml/pgbench.sgml
@@ -310,6 +310,26 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
      </varlistentry>
 
      <varlistentry>
+      <term><option>-H</option> <replaceable>rate</></term>
+      <term><option>--throttle</option> <replaceable>rate</></term>
+      <listitem>
+       <para>
+	Do client transaction throttling at the specified rate instead of
+	maximizing the load.
+	Each client connection targets this rate by starting transactions
+	along a Poisson-distributed event time line.
+	Obviously, the targetted rate must be below the maximum possible rate
+	of the system.
+	Example equivalent <replaceable>rate</> specifications which aim at
+	40 transactions-per-second, that is one transaction every 25 ms:
+	<literal>0.025</>, <literal>0.025s</>, <literal>25ms</>,
+        <literal>25000us</> and finally <literal>40tps</>.
+        Default is no throttling.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><option>-j</option> <replaceable>threads</></term>
       <listitem>
        <para>
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to