diff --git a/contrib/pgbench/pgbench.c b/contrib/pgbench/pgbench.c index 8c202bf..799dfcd 100644 --- a/contrib/pgbench/pgbench.c +++ b/contrib/pgbench/pgbench.c @@ -137,6 +137,12 @@ int unlogged_tables = 0; double sample_rate = 0.0; /* + * When clients are throttled to a given rate limit, this is the target delay + * to reach that rate in usec. 0 is the default and means no throttling. + */ +int64 throttle_delay = 0; + +/* * tablespace selection */ char *tablespace = NULL; @@ -205,6 +211,9 @@ 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 */ + int64 throttle_lag; /* transaction lag behind throttling */ int use_file; /* index in sql_files for this client */ bool prepared[MAX_FILES]; } CState; @@ -348,6 +357,8 @@ usage(void) " -D VARNAME=VALUE\n" " define variable for use by custom script\n" " -f FILENAME read transaction script from FILENAME\n" + " -R SPEC, --rate SPEC\n" + " target rate per client in transactions per second\n" " -j NUM number of threads (default: 1)\n" " -l write transaction times to log file\n" " -M simple|extended|prepared\n" @@ -902,13 +913,41 @@ doCustom(TState *thread, CState *st, instr_time *conn_time, FILE *logfile, AggVa top: commands = sql_files[st->use_file]; + /* handle throttling once per transaction by inserting a sleep. + * this is simpler than doing it at the end. + */ + if (throttle_delay && ! 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_delay * -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 "INT64_FORMAT" us\n", + st->id, wait); + } + if (st->sleeping) { /* are we sleeping? */ instr_time now; + int64 now_us; INSTR_TIME_SET_CURRENT(now); - if (st->until <= INSTR_TIME_GET_MICROSEC(now)) + now_us = INSTR_TIME_GET_MICROSEC(now); + if (st->until <= now_us) + { st->sleeping = 0; /* Done sleeping, go ahead with next command */ + if (throttle_delay && st->state==0) + /* measure lag of throttled transaction */ + st->throttle_lag += (now_us - st->until); + } else return true; /* Still sleeping, nothing to do here */ } @@ -1095,6 +1134,7 @@ top: st->state = 0; st->use_file = (int) getrand(thread, 0, num_files - 1); commands = sql_files[st->use_file]; + st->throttled = false; } } @@ -2015,7 +2055,8 @@ process_builtin(char *tb) /* print out results */ static void -printResults(int ttype, int normal_xacts, int nclients, +printResults(int ttype, int normal_xacts, + CState *clients, int nclients, TState *threads, int nthreads, instr_time total_time, instr_time conn_total_time) { @@ -2055,6 +2096,23 @@ printResults(int ttype, int normal_xacts, int nclients, printf("number of transactions actually processed: %d\n", normal_xacts); } + + if (throttle_delay) + { + /* Report average transaction lag under throttling, i.e. the delay + between scheduled and actual start times for the transaction. + The measured lag may be linked to the thread/client load, + the database load, or the Poisson throttling process. + should it report the maximum encountered lag? + */ + int64 throttle_lag = 0; + int c; + for (c = 0; c < nclients; c++) + throttle_lag += clients[c].throttle_lag; + printf("average transaction lag: %.3f ms\n", + 0.001 * throttle_lag / normal_xacts); + } + printf("tps = %f (including connections establishing)\n", tps_include); printf("tps = %f (excluding connections establishing)\n", tps_exclude); @@ -2115,6 +2173,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}, + {"rate", required_argument, NULL, 'R'}, {NULL, 0, NULL, 0} }; @@ -2181,7 +2240,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:R:", long_options, &optindex)) != -1) { switch (c) { @@ -2336,6 +2395,19 @@ main(int argc, char **argv) exit(1); } break; + case 'R': + { + /* get a double from the beginning of option value */ + double throttle_value = atof(optarg); + if (throttle_value <= 0.0) + { + fprintf(stderr, "invalid rate limit: %s\n", optarg); + exit(1); + } + /* Invert rate limit into a time offset */ + throttle_delay = (int64) (1000000.0 / throttle_value); + } + break; case 0: /* This covers long options which take no argument. */ break; @@ -2626,6 +2698,14 @@ main(int argc, char **argv) /* get start up time */ INSTR_TIME_SET_CURRENT(start_time); + /* set initial client throttling trigger */ + if (throttle_delay) + { + state[0].trigger = INSTR_TIME_GET_MICROSEC(start_time); + for (i = 1; i < nclients; i++) + state[i].trigger = state[0].trigger; + } + /* set alarm if duration is specified. */ if (duration > 0) setalarm(duration); @@ -2680,7 +2760,7 @@ main(int argc, char **argv) /* get end time */ INSTR_TIME_SET_CURRENT(total_time); INSTR_TIME_SUBTRACT(total_time, start_time); - printResults(ttype, total_xacts, nclients, threads, nthreads, + printResults(ttype, total_xacts, state, nclients, threads, nthreads, total_time, conn_total_time); return 0; diff --git a/doc/src/sgml/pgbench.sgml b/doc/src/sgml/pgbench.sgml index e9900d3..ee7e216 100644 --- a/doc/src/sgml/pgbench.sgml +++ b/doc/src/sgml/pgbench.sgml @@ -310,6 +310,25 @@ pgbench options dbname + rate + rate + + + Execute client transactions targeting the specified rate instead of + running as fast as possible (the default). The rate is given in + transactions per second and is per client. If the targeted rate is + above the maximum possible rate these transactions can execute at, + the rate limit won't have any impact on results. + + Each client connection targets this rate by starting transactions + along a Poisson-distributed event time line. When a rate limit is + active, the average transaction lag time (the delay between + the scheduled and actual transaction start times) is reported in ms. + + + + + threads