New features contributed by Tomoaki Sato.
authorTatsuo Ishii <ishii@postgresql.org>
Wed, 26 Jul 2006 07:24:50 +0000 (07:24 +0000)
committerTatsuo Ishii <ishii@postgresql.org>
Wed, 26 Jul 2006 07:24:50 +0000 (07:24 +0000)
- predefined variable "tps"
  The value of variable tps is taken from the scaling factor
  specified by -s option.

- -D option
  Variable values can be defined by -D option.

- \set command now allows arithmetic calculations.

contrib/pgbench/README.pgbench
contrib/pgbench/README.pgbench_jis
contrib/pgbench/pgbench.c

index debff559064fffa833522d1308b8ff553e100312..9ad30b317db1b656d1ba1fb099e8643ba3353a75 100644 (file)
@@ -1,14 +1,14 @@
-pgbench README     2005/10/04 Tatsuo Ishii
+pgbench README     2006/07/26 Tatsuo Ishii
 
 o What is pgbench?
 
-  pgbench is a simple program to run a benchmark test sort of
-  "TPC-B". pgbench is a client application of PostgreSQL and runs
-  with PostgreSQL only. It performs lots of small and simple
-  transactions including select/update/insert operations then
-  calculates number of transactions successfully completed within a
-  second (transactions per second, tps). Targeting data includes a
-  table with at least 100k tuples.
+  pgbench is a simple program to run a benchmark test. pgbench is a
+  client application of PostgreSQL and runs with PostgreSQL only. It
+  performs lots of small and simple transactions including
+  SELECT/UPDATE/INSERT operations then calculates number of
+  transactions successfully completed within a second (transactions
+  per second, tps). Targeting data includes a table with at least 100k
+  tuples.
 
   Example outputs from pgbench look like:
 
@@ -39,7 +39,7 @@ o How to install pgbench
 
 o How to use pgbench?
 
-  (1) Initialize database by:
+  (1) (optional)Initialize database by:
 
    pgbench -i <dbname>
 
@@ -95,6 +95,10 @@ o options
        as large as the largest number of clients you intend
        to test; else you'll mostly be measuring update contention.
 
+   -D varname=value
+       Define a variable. It can be refereed to by a script
+       provided by using -f option. Multile -D options are allowed.
+
    -U login
        Specify db user's login name if it is different from
        the Unix login name.
@@ -173,6 +177,15 @@ o -f option
   slash). A meta command takes some arguments separted by white
   spaces. Currently following meta command is supported:
 
+  \set name operand1 [ operator operand2 ]
+        set the calculated value using "operand1" "operator"
+        "operand2" to variable "name". If "operator" and "operand2"
+        are omitted, the value of operand1 is set to variable "name".
+
+  example:
+
+  \set ntellers 10 * :tps
+
   \setrandom name min max
 
    assign random integer to name between min and max
@@ -188,12 +201,17 @@ o -f option
 
   SELECT abalance FROM accounts WHERE aid = :aid
 
-  For example, TPC-B like benchmark can be defined as follows(scaling
+  Variables can also be defined by using -D option.
+
+  Example, TPC-B like benchmark can be defined as follows(scaling
   factor = 1):
 
-\setrandom aid 1 100000
-\setrandom bid 1 1
-\setrandom tid 1 10
+\set nbranches :tps
+\set ntellers 10 * :tps
+\set naccounts 100000 * :tps
+\setrandom aid 1 :naccounts
+\setrandom bid 1 :nbranches
+\setrandom tid 1 :ntellers
 \setrandom delta 1 10000
 BEGIN
 UPDATE accounts SET abalance = abalance + :delta WHERE aid = :aid
@@ -203,12 +221,30 @@ UPDATE branches SET bbalance = bbalance + :delta WHERE bid = :bid
 INSERT INTO history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, 'now')
 END
 
+If you want to automatically set the scaling factor from the number of
+tuples in branches table, use -s option and shell command like this:
+
+pgbench -s $(psql -At -c "SELECT count(*) FROM branches") -f tpc_b.sql
+
+Notice that -f option does not execute vacuum and clearing history
+table before starting benchmark.
+
 o License?
 
 Basically it is same as BSD license. See pgbench.c for more details.
 
 o History
 
+2006/07/26
+   * New features contributed by Tomoaki Sato.
+
+   * predefined variable "tps"
+       The value of variable tps is taken from the scaling factor
+       specified by -s option.
+        * -D option
+      Variable values can be defined by -D option.
+   * \set command now allows arithmetic calculations.
+
 2005/09/29
    * add -f option. contributed by Tomoaki Sato.
 
index d7f9d89e5d09fb03c7768bf082d65cc5cfaa745a..37bd4d9280a14742e0bc650201226e450e357da9 100644 (file)
@@ -1,11 +1,10 @@
-pgbench README     2005/09/29 Tatsuo Ishii
+pgbench README     2006/07/26 Tatsuo Ishii
 
 \e$B"#\e(Bpgbench \e$B$H$O!)\e(B
 
-pgbench \e$B$O%Y%s%A%^!<%/%F%9%H$r9T$J$&%W%m%0%i%`$G$9!%:#$N$H$3$m\e(B 
-PostgreSQL \e$B@lMQ$G$9!%\e(B
+pgbench \e$B$O\e(BPostgreSQL\e$B$N%Y%s%A%^!<%/%F%9%H$r9T$J$&%W%m%0%i%`$G$9!%\e(B
 
-pgbench \e$B$O\e(B select/update/insert \e$B$r4^$`%H%i%s%6%/%7%g%s$r<B9T$7!$A4BN$N\e(B
+pgbench \e$B$O\e(B SELECT/UPDATE/INSERT \e$B$r4^$`%H%i%s%6%/%7%g%s$r<B9T$7!$A4BN$N\e(B
 \e$B<B9T;~4V$H<B:]$K40N;$7$?%H%i%s%6%/%7%g%s$N?t$+$i\e(B 1 \e$BIC4V$K<B9T$G$-$?%H\e(B
 \e$B%i%s%6%/%7%g%s?t\e(B (tps) \e$B$rI=<($7$^$9!%=hM}$NBP>]$H$J$k%F!<%V%k$O%G%U%)\e(B
 \e$B%k%H$G$O\e(B 10\e$BK|%?%W%k$N%G!<%?$r4^$_$^$9!%\e(B
@@ -44,7 +43,8 @@ $ pgbench [\e$B%G!<%?%Y!<%9L>\e(B]
 
 \e$B$G$9!%%G!<%?%Y!<%9L>$r>JN,$9$k$H!$%f!<%6L>$HF1$8%G!<%?%Y!<%9$r;XDj$7$?\e(B
 \e$B$b$N$H$_$J$7$^$9!%%G!<%?%Y!<%9$O8e=R$N\e(B -i \e$B%*%W%7%g%s$r;H$C$F$"$i$+$8$a\e(B
-\e$B=i4|2=$7$F$*$/I,MW$,$"$j$^$9!%\e(B
+\e$B=i4|2=$7$F$*$/$3$H$,$G$-$^$9!%\e(B-f\e$B%*%W%7%g%s$r;H$C$FFH<+$N%H%i%s%6%/%7%g\e(B
+\e$B%s$rDj5A$9$k>l9g$O!$<+J,$G%G!<%?%Y!<%9$N=i4|2=$r$7$F$*$/I,MW$,$"$j$^$9!%\e(B
 
 pgbench \e$B$K$O$$$m$$$m$J%*%W%7%g%s$,$"$j$^$9!%\e(B
 
@@ -72,6 +72,14 @@ pgbench \e$B$K$O$$$m$$$m$J%*%W%7%g%s$,$"$j$^$9!%\e(B
        \e$B%/%?!<$rJQ$($k$3$H$K$h$j!$%F%9%H$NBP>]$H$J$k%F!<%V%k$N\e(B
        \e$BBg$-$5$,\e(B 10\e$BK|\e(B x [\e$B%9%1!<%j%s%0%U%!%/%?!<\e(B]\e$B$K$J$j$^$9!%\e(B
        \e$B%G%U%)%k%H$N%9%1!<%j%s%0%U%!%/%?!<$O\e(B 1 \e$B$G$9!%\e(B
+       -f \e$B%*%W%7%g%s$G;XDj$7$?%U%!%$%k$+$i%9%1!<%j%s%0%U%!%/\e(B
+       \e$B%?!<$r;2>H$9$k$K$O\e(B tps \e$B$H$$$&JQ?tL>$r;HMQ$7$^$9!%\e(B
+
+-D varname=value
+
+       \e$BJQ?t$rDj5A$7$^$9!%Dj5A$7$?JQ?t$O\e(B -f \e$B%*%W%7%g%s$G;XDj$7$?%U%!%$\e(B
+       \e$B%k$+$i;2>H$G$-$^$9!%\e(B-D \e$B%*%W%7%g%s$G$OJQ?tL>$HCM$r\e(B = (\e$B%$%3!<%k\e(B)
+       \e$B$G6h@Z$C$F;XDj$7$^$9!%\e(B-D \e$B%*%W%7%g%s$OJ#?t;XDj$G$-$^$9!%\e(B
 
 -U login   DB\e$B%f!<%6$N%m%0%$%sL>$r;XDj$7$^$9!%\e(B
 
@@ -129,7 +137,9 @@ pgbench \e$B$K$O$$$m$$$m$J%*%W%7%g%s$,$"$j$^$9!%\e(B
 \e$B"#%G!<%?%Y!<%9$N=i4|2=\e(B
 
 pgbench \e$B$G%Y%s%A%^!<%/%F%9%H$r<B;\$9$k$?$a$K$O!$$"$i$+$8$a%G!<%?%Y!<%9\e(B
-\e$B$r=i4|2=$7!$%F%9%H%G!<%?$r:n$kI,MW$,$"$j$^$9!%\e(B
+\e$B$r=i4|2=$7!$%F%9%H%G!<%?$r:n$kI,MW$,$"$j$^$9!%\e(B-f\e$B%*%W%7%g%s$r;H$C$FFH<+\e(B
+\e$B$N%H%i%s%6%/%7%g%s$rDj5A$9$k>l9g$O!$<+J,$G%G!<%?%Y!<%9$N=i4|2=$r$7$F$*\e(B
+\e$B$/I,MW$,$"$j$^$9!%\e(B
 
 $ pgbench -i [\e$B%G!<%?%Y!<%9L>\e(B]
 
@@ -146,8 +156,11 @@ accounts   100000
 history        0
 
 \e$B%9%1!<%j%s%0%U%!%/%?!<$r\e(B 10,100,1000 \e$B$J$I$KJQ99$9$k$H!$>e5-%?%W%k?t$O\e(B
-\e$B$=$l$K1~$8$F\e(B10\e$BG\!$\e(B100\e$BG\!$\e(B1000\e$BG\$K$J$j$^$9!%$?$H$($P!$%9%1!<%j%s%0%U%!\e(B
-\e$B%/%?!<$r\e(B 10 \e$B$H$9$k$H!$\e(B
+\e$B$=$l$K1~$8$F\e(B10\e$BG\!$\e(B100\e$BG\!$\e(B1000\e$BG\$K$J$j$^$9!%%F!<%V%k$H%$%s%G%C%/%9$N%5\e(B
+\e$B%$%:$O%G!<%?%Y!<%9%5%$%:$O35$M$=$l$>$l!$\e(B130MB\e$B!$\e(B1.3GB\e$B!$\e(B13GB\e$B$[$I$K$J$j$^\e(B
+\e$B$9!%\e(B
+
+\e$B$?$H$($P!$%9%1!<%j%s%0%U%!%/%?!<$r\e(B 10 \e$B$H$9$k$H!$\e(B
 
 \e$B%F!<%V%kL>\e(B   \e$B%?%W%k?t\e(B
 -------------------------
@@ -201,37 +214,69 @@ pgbench \e$B$G$O!$0J2<$N%7!<%1%s%9$rA4It40N;$7$F\e(B1\e$B%H%i%s%6%/%7%g%s$H?t$($F\e(
 
 \e$B8=:_$N$H$3$m!$0J2<$N%a%?%3%^%s%I$,Dj5A$5$l$F$$$^$9!%\e(B
 
+\set name operand1 [ operator operand2 ]
+   \e$BHo1i;;?t\e(B operand1 \e$B$H\e(B operand2 \e$B$r1i;;;R\e(B operator \e$B$K$h$C$F1i;;$7\e(B
+   \e$B$?7k2L$rJQ?t\e(B name \e$B$K@_Dj$7$^$9!%8=>u$G$O@0?t$N;MB'1i;;$N$_$KBP\e(B
+   \e$B1~$7$F$$$^$9!%$J$*!$1i;;;R$H\e(B 2 \e$B$DL\$NHo1i;;?t$r>JN,$9$k$HC1=c\e(B
+   \e$B$K\e(B 1 \e$B$DL\$NHo1i;;?t$rJQ?t$K@_Dj$7$^$9!%\e(B
+
+   \e$BJQ?t$K1i;;$N7k2L$r@_Dj$9$k$K$O!$\e(B\set \e$B%a%?%3%^%s%I$r;HMQ$7$F0J\e(B
+   \e$B2<$N$h$&$K5-=R$7$^$9!%\e(B
+
+   \set ntellers 10 * :tps
+
+   \e$B$3$l$O!$JQ?t\e(B ntellers \e$B$K%9%1!<%j%s%0%U%!%/%?!<\e(B (-s \e$B%*%W%7%g%s\e(B
+   \e$B$G;XDj$7$?\e(B) \e$B$r\e(B 10 \e$BG\$7$?7k2L$r@_Dj$7$^$9!%\e(B
+
 \setrandom name min max
+
    \e$B:G>.CM\e(B min \e$B$H:GBgCM\e(B max \e$B$N4V$NCM$r<h$kMp?t$r!$\e(Bname \e$BJQ?t$K@_Dj\e(B
    \e$B$7$^$9!%\e(B
 
-\e$BJQ?t$KMp?t$r@_Dj$9$k$K$O!$\e(B\setrandom \e$B%a%?%3%^%s%I$r;HMQ$7$F0J2<$N$h$&\e(B
-\e$B$K5-=R$7$^$9!%\e(B
+   \e$BJQ?t$KMp?t$r@_Dj$9$k$K$O!$\e(B\setrandom \e$B%a%?%3%^%s%I$r;HMQ$7$F0J2<$N$h$&\e(B
+   \e$B$K5-=R$7$^$9!%\e(B
+
+   \setrandom aid 1 100000
 
-\setrandom aid 1 100000
+   \e$B$3$l$O!$JQ?t\e(B aid \e$B$K\e(B 1 \e$B$+$i\e(B 100000 \e$B$N4V$NMp?t$r@_Dj$7$^$9!%\e(B
 
-\e$B$3$l$O!$JQ?t\e(B aid \e$B$K\e(B 1 \e$B$+$i\e(B 100000 \e$B$N4V$NMp?t$r@_Dj$7$^$9!%$^$?!$JQ?t$N\e(B
-\e$BCM$r\e(B SQL \e$B%3%^%s%I$KKd$a9~$`$K$O!$0J2<$N$h$&$K$=$NL>A0$NA0$K%3%m%s$rIU\e(B
-\e$B$1$^$9!%\e(B
+\e$BJQ?t$O\e(B SQL \e$B%3%^%s%I$*$h$S%a%?%3%^%s%I$+$i;2>H$G$-$^$9!%$=$l$K$O0J2<$N\e(B
+\e$B$h$&$KJQ?tL>$NA0$K%3%m%s$rIU$1$^$9!%\e(B
 
 SELECT abalance FROM accounts WHERE aid = :aid
 
+\e$BJQ?t$rDj5A$9$k$K$O%a%?%3%^%s%I0J30$K\e(B -D \e$B%*%W%7%g%s$r;HMQ$9$k$3$H$b$G$-\e(B
+\e$B$^$9!%\e(B -D \e$B%*%W%7%g%s$GDj5A$7$?JQ?t$bJQ?tL>$NA0$K%3%m%s$rIU$1$F;2>H$7$^\e(B
+\e$B$9!%\e(B
+
 \e$BNc$($P!$\e(BTCP-B \e$B$KN`;w$7$?%Y%s%A%^!<%/$r7WB,$9$k$K$O!$0J2<$N$h$&$K%H%i%s\e(B
 \e$B%6%/%7%g%s$NFbMF$r%U%!%$%k$K5-=R$7!$\e(B-f \e$B%*%W%7%g%s$K$h$C$F$=$N%U%!%$%k\e(B
 \e$B$r;XDj$7$F\e(B pgbench \e$B$r<B9T$7$^$9!%\e(B
 
-\setrandom aid 1 100000
-\setrandom bid 1 1
-\setrandom tid 1 10
+\set nbranches :tps
+\set ntellers 10 * :tps
+\set naccounts 100000 * :tps
+\setrandom aid 1 :naccounts
+\setrandom bid 1 :nbranches
+\setrandom tid 1 :ntellers
 \setrandom delta 1 10000
 BEGIN
 UPDATE accounts SET abalance = abalance + :delta WHERE aid = :aid
 SELECT abalance FROM accounts WHERE aid = :aid
 UPDATE tellers SET tbalance = tbalance + :delta WHERE tid = :tid
 UPDATE branches SET bbalance = bbalance + :delta WHERE bid = :bid
-INSERT INTO history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, 'now')
+INSERT INTO history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, current_timestamp)
 END
 
+\e$B%9%1!<%j%s%0%U%!%/%?!<$r\e(B branches \e$B%F!<%V%k$N%?%W%k?t$+$i<+F0E*$K@_Dj$7\e(B
+\e$B$?$$>l9g!$0J2<$N$h$&$K\e(B -s \e$B%*%W%7%g%s$H%7%'%k$N%3%^%s%ICV49$J$I$rAH$_9g\e(B
+\e$B$o$;$^$9!%\e(B
+
+pgbench -s $(psql -At -c "SELECT count(*) FROM branches") -f tpc_b.sql
+
+\e$B$J$*!$\e(B-f \e$B%*%W%7%g%s$r;XDj$7$?>l9g!$%Y%s%A%^!<%/3+;OA0$K\e(B vacuum \e$B$H\e(B
+history \e$B$N%/%j%"$O<+F0E*$K9T$o$l$^$;$s!%\e(B
+
 \e$B"#:n<T$H%i%$%;%s%9>r7o\e(B
 
 pgbench \e$B$O@P0f\e(B \e$BC#IW$K$h$C$F=q$+$l$^$7$?!%%i%$%;%s%9>r7o$O\e(B pgbench.c \e$B$N\e(B
@@ -240,6 +285,18 @@ pgbench \e$B$O@P0f\e(B \e$BC#IW$K$h$C$F=q$+$l$^$7$?!%%i%$%;%s%9>r7o$O\e(B pgbench.c
 
 \e$B"#2~DjMzNr\e(B
 
+2006/07/26
+   * \e$B:4F#$5$s$N%Q%C%A$rE,MQ!%0J2<$N5!G=DI2C!%\e(BPostgreSQL 8.2\e$B$K<h$j\e(B
+   \e$B9~$^$l$^$9!%\e(B
+
+   \e$BJQ?t\e(B tps
+       -s \e$B%*%W%7%g%s$G;XDj$7$?%9%1!<%j%s%0%U%!%/%?!<$r%U%!%$%kFb$GJQ?t$H$7\e(B
+      \e$B$F;2>H$9$k5!G=\e(B
+        -D \e$B%*%W%7%g%s\e(B
+      \e$B%3%^%s%I$N%*%W%7%g%s$H$7$FDj5A$7$?JQ?t$r%U%!%$%kFb$+$i;2>H$9$k5!G=\e(B
+   \set \e$B%3%^%s%I\e(B
+      \e$B%U%!%$%kFb$G;MB'1i;;$r9T$$!"$=$N7k2L$rJQ?t$KBeF~$9$k5!G=\e(B
+
 2005/09/29
    * \e$B:4F#$5$s$N%Q%C%A$rE,MQ!%\e(B-f \e$B%*%W%7%g%s$NDI2C!%\e(B
 
index 79f6ba66727c62db111d43b73b193df7167ecc64..9183ac386860f2e30e2eec352ac5c88aaac7e408 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $PostgreSQL: pgsql/contrib/pgbench/pgbench.c,v 1.49 2005/12/10 01:09:07 tgl Exp $
+ * $PostgreSQL: pgsql/contrib/pgbench/pgbench.c,v 1.50 2006/07/26 07:24:50 ishii Exp $
  *
  * pgbench: a simple benchmark program for PostgreSQL
  * written by Tatsuo Ishii
@@ -169,7 +169,7 @@ static char *select_only = {
 static void
 usage(void)
 {
-   fprintf(stderr, "usage: pgbench [-h hostname][-p port][-c nclients][-t ntransactions][-s scaling_factor][-n][-C][-v][-S][-N][-f filename][-l][-U login][-P password][-d][dbname]\n");
+   fprintf(stderr, "usage: pgbench [-h hostname][-p port][-c nclients][-t ntransactions][-s scaling_factor][-D varname=value][-n][-C][-v][-S][-N][-f filename][-l][-U login][-P password][-d][dbname]\n");
    fprintf(stderr, "(initialize mode): pgbench -i [-h hostname][-p port][-s scaling_factor][-U login][-P password][-d][dbname]\n");
 }
 
@@ -552,26 +552,130 @@ top:
 
        if (strcasecmp(argv[0], "setrandom") == 0)
        {
-           char       *val;
+           char       *var;
+           int         min,
+                       max;
+           char        res[64];
+
+           if (*argv[2] == ':')
+           {
+               if ((var = getVariable(st, argv[2] + 1)) == NULL)
+               {
+                   fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[2]);
+                   st->ecnt++;
+                   return;
+               }
+               min = atoi(var);
+           }
+           else
+               min = atoi(argv[2]);
+
+           if (min < 0)
+           {
+               fprintf(stderr, "%s: invalid minimum number %d\n", argv[0], min);
+               st->ecnt++;
+               return;
+           }
 
-           if ((val = malloc(strlen(argv[3]) + 1)) == NULL)
+           if (*argv[3] == ':')
+           {
+               if ((var = getVariable(st, argv[3] + 1)) == NULL)
+               {
+                   fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[3]);
+                   st->ecnt++;
+                   return;
+               }
+               max = atoi(var);
+           }
+           else
+               max = atoi(argv[3]);
+
+           if (max < min || max > MAX_RANDOM_VALUE)
+           {
+               fprintf(stderr, "%s: invalid maximum number %d\n", argv[0], max);
+               st->ecnt++;
+               return;
+           }
+
+           snprintf(res, sizeof(res), "%d", getrand(min, max));
+
+           if (putVariable(st, argv[1], res) == false)
            {
                fprintf(stderr, "%s: out of memory\n", argv[0]);
                st->ecnt++;
                return;
            }
 
-           sprintf(val, "%d", getrand(atoi(argv[2]), atoi(argv[3])));
+           st->listen = 1;
+       }
+       else if (strcasecmp(argv[0], "set") == 0)
+       {
+           char       *var;
+           int         ope1,
+                       ope2;
+           char        res[64];
 
-           if (putVariable(st, argv[1], val) == false)
+           if (*argv[2] == ':')
+           {
+               if ((var = getVariable(st, argv[2] + 1)) == NULL)
+               {
+                   fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[2]);
+                   st->ecnt++;
+                   return;
+               }
+               ope1 = atoi(var);
+           }
+           else
+               ope1 = atoi(argv[2]);
+
+           if (argc < 5)
+               snprintf(res, sizeof(res), "%d", ope1);
+           else
+           {
+               if (*argv[4] == ':')
+               {
+                   if ((var = getVariable(st, argv[4] + 1)) == NULL)
+                   {
+                       fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[4]);
+                       st->ecnt++;
+                       return;
+                   }
+                   ope2 = atoi(var);
+               }
+               else
+                   ope2 = atoi(argv[4]);
+
+               if (strcmp(argv[3], "+") == 0)
+                   snprintf(res, sizeof(res), "%d", ope1 + ope2);
+               else if (strcmp(argv[3], "-") == 0)
+                   snprintf(res, sizeof(res), "%d", ope1 - ope2);
+               else if (strcmp(argv[3], "*") == 0)
+                   snprintf(res, sizeof(res), "%d", ope1 * ope2);
+               else if (strcmp(argv[3], "/") == 0)
+               {
+                   if (ope2 == 0)
+                   {
+                       fprintf(stderr, "%s: division by zero\n", argv[0]);
+                       st->ecnt++;
+                       return;
+                   }
+                   snprintf(res, sizeof(res), "%d", ope1 / ope2);
+               }
+               else
+               {
+                   fprintf(stderr, "%s: unsupported operator %s\n", argv[0], argv[3]);
+                   st->ecnt++;
+                   return;
+               }
+           }
+
+           if (putVariable(st, argv[1], res) == false)
            {
                fprintf(stderr, "%s: out of memory\n", argv[0]);
-               free(val);
                st->ecnt++;
                return;
            }
 
-           free(val);
            st->listen = 1;
        }
 
@@ -808,9 +912,6 @@ process_commands(char *buf)
 
        if (strcasecmp(my_commands->argv[0], "setrandom") == 0)
        {
-           int         min,
-                       max;
-
            if (my_commands->argc < 4)
            {
                fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
@@ -820,20 +921,18 @@ process_commands(char *buf)
            for (j = 4; j < my_commands->argc; j++)
                fprintf(stderr, "%s: extra argument \"%s\" ignored\n",
                        my_commands->argv[0], my_commands->argv[j]);
-
-           if ((min = atoi(my_commands->argv[2])) < 0)
+       }
+       else if (strcasecmp(my_commands->argv[0], "set") == 0)
+       {
+           if (my_commands->argc < 3)
            {
-               fprintf(stderr, "%s: invalid minimum number %s\n",
-                       my_commands->argv[0], my_commands->argv[2]);
+               fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
                return NULL;
            }
 
-           if ((max = atoi(my_commands->argv[3])) < min || max > MAX_RANDOM_VALUE)
-           {
-               fprintf(stderr, "%s: invalid maximum number %s\n",
-                       my_commands->argv[0], my_commands->argv[3]);
-               return NULL;
-           }
+           for (j = my_commands->argc < 5 ? 3 : 5; j < my_commands->argc; j++)
+               fprintf(stderr, "%s: extra argument \"%s\" ignored\n",
+                       my_commands->argv[0], my_commands->argv[j]);
        }
        else
        {
@@ -889,10 +988,14 @@ process_file(char *filename)
    while (fgets(buf, sizeof(buf), fd) != NULL)
    {
        Command    *commands;
+       int         i;
 
+       i = 0;
+       while (isspace((unsigned char) buf[i]))
+           i++;
 
-       if (strncmp(buf, "\n", 1) != 0) {
-           commands = process_commands(buf);
+       if (strncmp(&buf[i], "\n", 1) != 0 && strncmp(&buf[i], "--", 2) != 0) {
+           commands = process_commands(&buf[i]);
            if (commands == NULL)
            {
                fclose(fd);
@@ -1067,7 +1170,16 @@ main(int argc, char **argv)
    else if ((env = getenv("PGUSER")) != NULL && *env != '\0')
        login = env;
 
-   while ((c = getopt(argc, argv, "ih:nvp:dc:t:s:U:P:CNSlf:")) != -1)
+   state = (CState *) malloc(sizeof(CState));
+   if (state == NULL)
+   {
+       fprintf(stderr, "Couldn't allocate memory for state\n");
+       exit(1);
+   }
+
+   memset(state, 0, sizeof(*state));
+
+   while ((c = getopt(argc, argv, "ih:nvp:dc:t:s:U:P:CNSlf:D:")) != -1)
    {
        switch (c)
        {
@@ -1151,9 +1263,27 @@ main(int argc, char **argv)
            case 'f':
                ttype = 3;
                filename = optarg;
-               if (process_file(filename) == false)
+               if (process_file(filename) == false || *sql_files[num_files - 1] == NULL)
                    exit(1);
                break;
+           case 'D':
+               {
+                   char       *p;
+
+                   if ((p = strchr(optarg, '=')) == NULL || p == optarg || *(p + 1) == '\0')
+                   {
+                       fprintf(stderr, "invalid variable definition: %s\n", optarg);
+                       exit(1);
+                   }
+
+                   *p++ = '\0';
+                   if (putVariable(&state[0], optarg, p) == false)
+                   {
+                       fprintf(stderr, "Couldn't allocate memory for variable\n");
+                       exit(1);
+                   }
+               }
+               break;
            default:
                usage();
                exit(1);
@@ -1181,14 +1311,43 @@ main(int argc, char **argv)
 
    remains = nclients;
 
-   state = (CState *) malloc(sizeof(CState) * nclients);
-   if (state == NULL)
+   if (getVariable(&state[0], "tps") == NULL)
    {
-       fprintf(stderr, "Couldn't allocate memory for state\n");
-       exit(1);
+       char        val[64];
+
+       snprintf(val, sizeof(val), "%d", tps);
+       if (putVariable(&state[0], "tps", val) == false)
+       {
+           fprintf(stderr, "Couldn't allocate memory for variable\n");
+           exit(1);
+       }
    }
 
-   memset(state, 0, sizeof(*state) * nclients);
+   if (nclients > 1)
+   {
+       state = (CState *) realloc(state, sizeof(CState) * nclients);
+       if (state == NULL)
+       {
+           fprintf(stderr, "Couldn't allocate memory for state\n");
+           exit(1);
+       }
+
+       memset(state + sizeof(*state), 0, sizeof(*state) * (nclients - 1));
+
+       for (i = 1; i < nclients; i++)
+       {
+           int         j;
+
+           for (j = 0; j < state[0].nvariables; j++)
+           {
+               if (putVariable(&state[i], state[0].variables[j].name, state[0].variables[j].value) == false)
+               {
+                   fprintf(stderr, "Couldn't allocate memory for variable\n");
+                   exit(1);
+               }
+           }
+       }
+   }
 
    if (use_log)
    {
@@ -1357,8 +1516,19 @@ main(int argc, char **argv)
    /* send start up queries in async manner */
    for (i = 0; i < nclients; i++)
    {
+       Command   **commands = sql_files[state[i].use_file];
+       int         prev_ecnt = state[i].ecnt;
+
        state[i].use_file = getrand(0, num_files - 1);
        doCustom(state, i, debug);
+
+       if (state[i].ecnt > prev_ecnt && commands[state[i].state]->type == META_COMMAND)
+       {
+           fprintf(stderr, "Client %d aborted in state %d. Execution meta-command failed.\n", i, state[i].state);
+           remains--;              /* I've aborted */
+           PQfinish(state[i].con);
+           state[i].con = NULL;
+       }
    }
 
    for (;;)
@@ -1424,12 +1594,21 @@ main(int argc, char **argv)
        for (i = 0; i < nclients; i++)
        {
            Command   **commands = sql_files[state[i].use_file];
+           int         prev_ecnt = state[i].ecnt;
 
            if (state[i].con && (FD_ISSET(PQsocket(state[i].con), &input_mask)
                          || commands[state[i].state]->type == META_COMMAND))
            {
                doCustom(state, i, debug);
            }
+
+           if (state[i].ecnt > prev_ecnt && commands[state[i].state]->type == META_COMMAND)
+           {
+               fprintf(stderr, "Client %d aborted in state %d. Execution meta-command failed.\n", i, state[i].state);
+               remains--;              /* I've aborted */
+               PQfinish(state[i].con);
+               state[i].con = NULL;
+           }
        }
    }
 }