From 0c57c832b950192405004021c618315bbe786ddf Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Wed, 26 Jul 2006 07:24:50 +0000
Subject: [PATCH] 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.
---
 contrib/pgbench/README.pgbench     |  62 ++++++--
 contrib/pgbench/README.pgbench_jis |  93 ++++++++---
 contrib/pgbench/pgbench.c          | 239 +++++++++++++++++++++++++----
 3 files changed, 333 insertions(+), 61 deletions(-)

diff --git a/contrib/pgbench/README.pgbench b/contrib/pgbench/README.pgbench
index debff559064..9ad30b317db 100644
--- a/contrib/pgbench/README.pgbench
+++ b/contrib/pgbench/README.pgbench
@@ -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.
 
diff --git a/contrib/pgbench/README.pgbench_jis b/contrib/pgbench/README.pgbench_jis
index d7f9d89e5d0..37bd4d9280a 100644
--- a/contrib/pgbench/README.pgbench_jis
+++ b/contrib/pgbench/README.pgbench_jis
@@ -1,11 +1,10 @@
-pgbench README		2005/09/29 Tatsuo Ishii
+pgbench README		2006/07/26 Tatsuo Ishii
 
 ■pgbench とは?
 
-pgbench はベンチマークテストを行なうプログラムです.今のところ 
-PostgreSQL 専用です.
+pgbench はPostgreSQLのベンチマークテストを行なうプログラムです.
 
-pgbench は select/update/insert を含むトランザクションを実行し,全体の
+pgbench は SELECT/UPDATE/INSERT を含むトランザクションを実行し,全体の
 実行時間と実際に完了したトランザクションの数から 1 秒間に実行できたト
 ランザクション数 (tps) を表示します.処理の対象となるテーブルはデフォ
 ルトでは 10万タプルのデータを含みます.
@@ -44,7 +43,8 @@ $ pgbench [データベース名]
 
 です.データベース名を省略すると,ユーザ名と同じデータベースを指定した
 ものとみなします.データベースは後述の -i オプションを使ってあらかじめ
-初期化しておく必要があります.
+初期化しておくことができます.-fオプションを使って独自のトランザクショ
+ンを定義する場合は,自分でデータベースの初期化をしておく必要があります.
 
 pgbench にはいろいろなオプションがあります.
 
@@ -72,6 +72,14 @@ pgbench にはいろいろなオプションがあります.
 		クターを変えることにより,テストの対象となるテーブルの
 		大きさが 10万 x [スケーリングファクター]になります.
 		デフォルトのスケーリングファクターは 1 です.
+		-f オプションで指定したファイルからスケーリングファク
+		ターを参照するには tps という変数名を使用します.
+
+-D varname=value
+
+		変数を定義します.定義した変数は -f オプションで指定したファイ
+		ルから参照できます.-D オプションでは変数名と値を = (イコール)
+		で区切って指定します.-D オプションは複数指定できます.
 
 -U login	DBユーザのログイン名を指定します.
 
@@ -129,7 +137,9 @@ pgbench にはいろいろなオプションがあります.
 ■データベースの初期化
 
 pgbench でベンチマークテストを実施するためには,あらかじめデータベース
-を初期化し,テストデータを作る必要があります.
+を初期化し,テストデータを作る必要があります.-fオプションを使って独自
+のトランザクションを定義する場合は,自分でデータベースの初期化をしてお
+く必要があります.
 
 $ pgbench -i [データベース名]
 
@@ -146,8 +156,11 @@ accounts	100000
 history		0
 
 スケーリングファクターを 10,100,1000 などに変更すると,上記タプル数は
-それに応じて10倍,100倍,1000倍になります.たとえば,スケーリングファ
-クターを 10 とすると,
+それに応じて10倍,100倍,1000倍になります.テーブルとインデックスのサ
+イズはデータベースサイズは概ねそれぞれ,130MB,1.3GB,13GBほどになりま
+す.
+
+たとえば,スケーリングファクターを 10 とすると,
 
 テーブル名	タプル数
 -------------------------
@@ -201,37 +214,69 @@ pgbench では,以下のシーケンスを全部完了して1トランザクションと数えて����現在のところ,以下のメタコマンドが定義されています.
 
+\set name operand1 [ operator operand2 ]
+	被演算数 operand1 と operand2 を演算子 operator によって演算し
+	た結果を変数 name に設定します.現状では整数の四則演算のみに対
+	応しています.なお,演算子と 2 つ目の被演算数を省略すると単純
+	に 1 つ目の被演算数を変数に設定します.
+
+	変数に演算の結果を設定するには,\set メタコマンドを使用して以
+	下のように記述します.
+
+	\set ntellers 10 * :tps
+
+	これは,変数 ntellers にスケーリングファクター (-s オプション
+	で指定した) を 10 倍した結果を設定します.
+
 \setrandom name min max
+
 	最小値 min と最大値 max の間の値を取る乱数を,name 変数に設定
 	します.
 
-変数に乱数を設定するには,\setrandom メタコマンドを使用して以下のよう
-に記述します.
+	変数に乱数を設定するには,\setrandom メタコマンドを使用して以下のよう
+	に記述します.
+
+	\setrandom aid 1 100000
 
-\setrandom aid 1 100000
+	これは,変数 aid に 1 から 100000 の間の乱数を設定します.
 
-これは,変数 aid に 1 から 100000 の間の乱数を設定します.また,変数の
-値を SQL コマンドに埋め込むには,以下のようにその名前の前にコロンを付
-けます.
+変数は SQL コマンドおよびメタコマンドから参照できます.それには以下の
+ように変数名の前にコロンを付けます.
 
 SELECT abalance FROM accounts WHERE aid = :aid
 
+変数を定義するにはメタコマンド以外に -D オプションを使用することもでき
+ます. -D オプションで定義した変数も変数名の前にコロンを付けて参照しま
+す.
+
 例えば,TCP-B に類似したベンチマークを計測するには,以下のようにトラン
 ザクションの内容をファイルに記述し,-f オプションによってそのファイル
 を指定して pgbench を実行します.
 
-\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
 
+スケーリングファクターを branches テーブルのタプル数から自動的に設定し
+たい場合,以下のように -s オプションとシェルのコマンド置換などを組み合
+わせます.
+
+pgbench -s $(psql -At -c "SELECT count(*) FROM branches") -f tpc_b.sql
+
+なお,-f オプションを指定した場合,ベンチマーク開始前に vacuum と
+history のクリアは自動的に行われません.
+
 ■作者とライセンス条件
 
 pgbench は石井 達夫によって書かれました.ライセンス条件は pgbench.c の
@@ -240,6 +285,18 @@ pgbench は石井 達夫によって書かれました.ライセンス条件は pgbench.c
 
 ■改定履歴
 
+2006/07/26
+	* 佐藤さんのパッチを適用.以下の機能追加.PostgreSQL 8.2に取り
+	込まれます.
+
+	変数 tps
+	    -s オプションで指定したスケーリングファクターをファイル内で変数とし
+	   て参照する機能
+        -D オプション
+	   コマンドのオプションとして定義した変数をファイル内から参照する機能
+	\set コマンド
+	   ファイル内で四則演算を行い、その結果を変数に代入する機能
+
 2005/09/29
 	* 佐藤さんのパッチを適用.-f オプションの追加.
 
diff --git a/contrib/pgbench/pgbench.c b/contrib/pgbench/pgbench.c
index 79f6ba66727..9183ac38686 100644
--- a/contrib/pgbench/pgbench.c
+++ b/contrib/pgbench/pgbench.c
@@ -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;
+			}
 		}
 	}
 }
-- 
GitLab