2 * $Header: /cvsroot/pgsql/contrib/pgbench/pgbench.c,v 1.20 2002/10/07 05:10:02 ishii Exp $
4 * pgbench: a simple TPC-B like benchmark program for PostgreSQL
5 * written by Tatsuo Ishii
7 * Copyright (c) 2000-2002 Tatsuo Ishii
9 * Permission to use, copy, modify, and distribute this software and
10 * its documentation for any purpose and without fee is hereby
11 * granted, provided that the above copyright notice appear in all
12 * copies and that both that copyright notice and this permission
13 * notice appear in supporting documentation, and that the name of the
14 * author not be used in advertising or publicity pertaining to
15 * distribution of the software without specific, written prior
16 * permission. The author makes no representations about the
17 * suitability of this software for any purpose. It is provided "as
18 * is" without express or implied warranty.
20 #include "postgres_fe.h"
36 #ifdef HAVE_SYS_SELECT_H
37 #include <sys/select.h>
41 #include <sys/resource.h>
44 /********************************************************************
45 * some configurable parameters */
47 #define MAXCLIENTS 1024 /* max number of clients allowed */
49 int nclients = 1; /* default number of simulated clients */
50 int nxacts = 10; /* default number of transactions per
54 * scaling factor. for example, tps = 10 will make 1000000 tuples of
60 * end of configurable parameters
61 *********************************************************************/
65 #define naccounts 100000
69 bool use_log; /* log transaction latencies to a file */
71 int remains; /* number of remaining clients */
73 int is_connect; /* establish connection for each
78 char *pgoptions = NULL;
86 PGconn *con; /* connection handle to DB */
87 int id; /* client No. */
88 int state; /* state No. */
89 int cnt; /* xacts count */
90 int ecnt; /* error count */
91 int listen; /* 0 indicates that an async query has
93 int aid; /* account id for this transaction */
94 int bid; /* branch id for this transaction */
95 int tid; /* teller id for this transaction */
98 struct timeval txn_begin; /* used for measuring latencies */
104 fprintf(stderr, "usage: pgbench [-h hostname][-p port][-c nclients][-t ntransactions][-s scaling_factor][-n][-C][-v][-S][-N][-l][-U login][-P password][-d][dbname]\n");
105 fprintf(stderr, "(initialize mode): pgbench -i [-h hostname][-p port][-s scaling_factor][-U login][-P password][-d][dbname]\n");
108 /* random number generator */
110 getrand(int min, int max)
112 return (min + (int) (max * 1.0 * rand() / (RAND_MAX + 1.0)));
115 /* set up a connection to the backend */
121 con = PQsetdbLogin(pghost, pgport, pgoptions, pgtty, dbName,
125 fprintf(stderr, "Connection to database '%s' failed.\n", dbName);
126 fprintf(stderr, "Memory allocatin problem?\n");
130 if (PQstatus(con) == CONNECTION_BAD)
132 fprintf(stderr, "Connection to database '%s' failed.\n", dbName);
134 if (PQerrorMessage(con))
135 fprintf(stderr, "%s", PQerrorMessage(con));
137 fprintf(stderr, "No explanation from the backend\n");
144 /* throw away response from backend */
146 discard_response(CState * state)
152 res = PQgetResult(state->con);
158 /* check to see if the SQL result was good */
160 check(CState * state, PGresult *res, int n, int good)
162 CState *st = &state[n];
164 if (res && PQresultStatus(res) != good)
166 fprintf(stderr, "Client %d aborted in state %d: %s", n, st->state, PQerrorMessage(st->con));
167 remains--; /* I've aborted */
175 /* process a transaction */
177 doOne(CState * state, int n, int debug, int ttype)
181 CState *st = &state[n];
184 { /* are we receiver? */
186 fprintf(stderr, "client %d receiving\n", n);
187 if (!PQconsumeInput(st->con))
188 { /* there's something wrong */
189 fprintf(stderr, "Client %d aborted in state %d. Probably the backend died while processing.\n", n, st->state);
190 remains--; /* I've aborted */
195 if (PQisBusy(st->con))
196 return; /* don't have the whole result yet */
200 case 0: /* response to "begin" */
201 res = PQgetResult(st->con);
202 if (check(state, res, n, PGRES_COMMAND_OK))
205 discard_response(st);
207 case 1: /* response to "update accounts..." */
208 res = PQgetResult(st->con);
209 if (check(state, res, n, PGRES_COMMAND_OK))
212 discard_response(st);
214 case 2: /* response to "select abalance ..." */
215 res = PQgetResult(st->con);
216 if (check(state, res, n, PGRES_TUPLES_OK))
219 discard_response(st);
221 case 3: /* response to "update tellers ..." */
222 res = PQgetResult(st->con);
223 if (check(state, res, n, PGRES_COMMAND_OK))
226 discard_response(st);
228 case 4: /* response to "update branches ..." */
229 res = PQgetResult(st->con);
230 if (check(state, res, n, PGRES_COMMAND_OK))
233 discard_response(st);
235 case 5: /* response to "insert into history ..." */
236 res = PQgetResult(st->con);
237 if (check(state, res, n, PGRES_COMMAND_OK))
240 discard_response(st);
242 case 6: /* response to "end" */
245 * transaction finished: record the time it took in the
253 gettimeofday(&now, 0);
254 diff = (now.tv_sec - st->txn_begin.tv_sec) * 1000000 +
255 (now.tv_usec - st->txn_begin.tv_usec);
257 fprintf(LOGFILE, "%d %d %lld\n", st->id, st->cnt, diff);
260 res = PQgetResult(st->con);
261 if (check(state, res, n, PGRES_COMMAND_OK))
264 discard_response(st);
272 if (++st->cnt >= nxacts)
274 remains--; /* I'm done */
285 /* increment state counter */
293 if ((st->con = doConnect()) == NULL)
295 fprintf(stderr, "Client %d aborted in establishing connection.\n",
297 remains--; /* I've aborted */
306 case 0: /* about to start */
307 strcpy(sql, "begin");
308 st->aid = getrand(1, naccounts * tps);
309 st->bid = getrand(1, nbranches * tps);
310 st->tid = getrand(1, ntellers * tps);
311 st->delta = getrand(1, 1000);
313 gettimeofday(&(st->txn_begin), 0);
316 snprintf(sql, 256, "update accounts set abalance = abalance + %d where aid = %d\n", st->delta, st->aid);
319 snprintf(sql, 256, "select abalance from accounts where aid = %d", st->aid);
324 snprintf(sql, 256, "update tellers set tbalance = tbalance + %d where tid = %d\n",
331 snprintf(sql, 256, "update branches set bbalance = bbalance + %d where bid = %d", st->delta, st->bid);
335 snprintf(sql, 256, "insert into history(tid,bid,aid,delta,mtime) values(%d,%d,%d,%d,'now')",
336 st->tid, st->bid, st->aid, st->delta);
344 fprintf(stderr, "client %d sending %s\n", n, sql);
345 if (PQsendQuery(st->con, sql) == 0)
348 fprintf(stderr, "PQsendQuery(%s)failed\n", sql);
353 st->listen++; /* flags that should be listened */
357 /* process a select only transaction */
359 doSelectOnly(CState * state, int n, int debug)
363 CState *st = &state[n];
366 { /* are we receiver? */
368 fprintf(stderr, "client %d receiving\n", n);
369 if (!PQconsumeInput(st->con))
370 { /* there's something wrong */
371 fprintf(stderr, "Client %d aborted in state %d. Probably the backend died while processing.\n", n, st->state);
372 remains--; /* I've aborted */
377 if (PQisBusy(st->con))
378 return; /* don't have the whole result yet */
382 case 0: /* response to "select abalance ..." */
383 res = PQgetResult(st->con);
384 if (check(state, res, n, PGRES_TUPLES_OK))
387 discard_response(st);
395 if (++st->cnt >= nxacts)
397 remains--; /* I've done */
408 /* increment state counter */
416 if ((st->con = doConnect()) == NULL)
418 fprintf(stderr, "Client %d aborted in establishing connection.\n",
420 remains--; /* I've aborted */
430 st->aid = getrand(1, naccounts * tps);
431 snprintf(sql, 256, "select abalance from accounts where aid = %d", st->aid);
436 fprintf(stderr, "client %d sending %s\n", n, sql);
438 if (PQsendQuery(st->con, sql) == 0)
441 fprintf(stderr, "PQsendQuery(%s)failed\n", sql);
446 st->listen++; /* flags that should be listened */
450 /* discard connections */
452 disconnect_all(CState * state)
456 for (i = 0; i < nclients; i++)
459 PQfinish(state[i].con);
463 /* create tables and setup data */
469 static char *DDLs[] = {
470 "drop table branches",
471 "create table branches(bid int, primary key(bid),bbalance int,filler char(88))",
472 "drop table tellers",
473 "create table tellers(tid int, primary key(tid),bid int,tbalance int,filler char(84))",
474 "drop table accounts",
475 "create table accounts(aid int,primary key(aid),bid int,abalance int,filler char(84))",
476 "drop table history",
477 "create table history(tid int,bid int,aid int,delta int,mtime timestamp,filler char(22))"};
482 if ((con = doConnect()) == NULL)
485 for (i = 0; i < (sizeof(DDLs) / sizeof(char *)); i++)
487 res = PQexec(con, DDLs[i]);
488 if (strncmp(DDLs[i], "drop", 4) && PQresultStatus(res) != PGRES_COMMAND_OK)
490 fprintf(stderr, "%s", PQerrorMessage(con));
496 res = PQexec(con, "begin");
497 if (PQresultStatus(res) != PGRES_COMMAND_OK)
499 fprintf(stderr, "%s", PQerrorMessage(con));
503 for (i = 0; i < nbranches * tps; i++)
505 snprintf(sql, 256, "insert into branches(bid,bbalance) values(%d,0)", i + 1);
506 res = PQexec(con, sql);
507 if (PQresultStatus(res) != PGRES_COMMAND_OK)
509 fprintf(stderr, "%s", PQerrorMessage(con));
515 for (i = 0; i < ntellers * tps; i++)
517 snprintf(sql, 256, "insert into tellers(tid,bid,tbalance) values (%d,%d,0)"
518 ,i + 1, i / ntellers + 1);
519 res = PQexec(con, sql);
520 if (PQresultStatus(res) != PGRES_COMMAND_OK)
522 fprintf(stderr, "%s", PQerrorMessage(con));
528 res = PQexec(con, "end");
529 if (PQresultStatus(res) != PGRES_COMMAND_OK)
531 fprintf(stderr, "%s", PQerrorMessage(con));
537 * occupy accounts table with some data
539 fprintf(stderr, "creating tables...\n");
540 for (i = 0; i < naccounts * tps; i++)
546 res = PQexec(con, "copy accounts from stdin");
547 if (PQresultStatus(res) != PGRES_COPY_IN)
549 fprintf(stderr, "%s", PQerrorMessage(con));
555 snprintf(sql, 256, "%d\t%d\t%d\t\n", j, j / naccounts, 0);
556 if (PQputline(con, sql))
558 fprintf(stderr, "PQputline failed\n");
565 * every 10000 tuples, we commit the copy command. this should
566 * avoid generating too much WAL logs
568 fprintf(stderr, "%d tuples done.\n", j);
569 if (PQputline(con, "\\.\n"))
571 fprintf(stderr, "very last PQputline failed\n");
577 fprintf(stderr, "PQendcopy failed\n");
584 * do a checkpoint to purge the old WAL logs
586 res = PQexec(con, "checkpoint");
587 if (PQresultStatus(res) != PGRES_COMMAND_OK)
589 fprintf(stderr, "%s", PQerrorMessage(con));
592 #endif /* NOT_USED */
597 fprintf(stderr, "vacuum...");
598 res = PQexec(con, "vacuum analyze");
599 if (PQresultStatus(res) != PGRES_COMMAND_OK)
601 fprintf(stderr, "%s", PQerrorMessage(con));
604 fprintf(stderr, "done.\n");
609 /* print out results */
612 int ttype, CState * state,
613 struct timeval * tv1, struct timeval * tv2,
614 struct timeval * tv3)
619 int normal_xacts = 0;
622 for (i = 0; i < nclients; i++)
623 normal_xacts += state[i].cnt;
625 t1 = (tv3->tv_sec - tv1->tv_sec) * 1000000.0 + (tv3->tv_usec - tv1->tv_usec);
626 t1 = normal_xacts * 1000000.0 / t1;
628 t2 = (tv3->tv_sec - tv2->tv_sec) * 1000000.0 + (tv3->tv_usec - tv2->tv_usec);
629 t2 = normal_xacts * 1000000.0 / t2;
632 s = "TPC-B (sort of)";
634 s = "Update only accounts";
638 printf("transaction type: %s\n", s);
639 printf("scaling factor: %d\n", tps);
640 printf("number of clients: %d\n", nclients);
641 printf("number of transactions per client: %d\n", nxacts);
642 printf("number of transactions actually processed: %d/%d\n", normal_xacts, nxacts * nclients);
643 printf("tps = %f (including connections establishing)\n", t1);
644 printf("tps = %f (excluding connections establishing)\n", t2);
649 main(int argc, char **argv)
656 int is_init_mode = 0; /* initialize mode? */
657 int is_no_vacuum = 0; /* no vacuum at all before
659 int is_full_vacuum = 0; /* do full vacuum before testing? */
660 int debug = 0; /* debug flag */
661 int ttype = 0; /* transaction type. 0: TPC-B, 1: SELECT
662 * only, 2: skip update of branches and
665 static CState *state; /* status of clients */
667 struct timeval tv1; /* start up time */
668 struct timeval tv2; /* after establishing all connections to
670 struct timeval tv3; /* end time */
675 int nsocks; /* return from select(2) */
676 int maxsock; /* max socket number to be waited */
685 while ((c = getopt(argc, argv, "ih:nvp:dc:t:s:U:P:CNSl")) != -1)
714 nclients = atoi(optarg);
715 if (nclients <= 0 || nclients > MAXCLIENTS)
717 fprintf(stderr, "invalid number of clients: %d\n", nclients);
721 #ifdef RLIMIT_NOFILE /* most platform uses RLIMIT_NOFILE */
722 if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
724 #else /* but BSD doesn't ... */
725 if (getrlimit(RLIMIT_OFILE, &rlim) == -1)
727 #endif /* HAVE_RLIMIT_NOFILE */
728 fprintf(stderr, "getrlimit failed. reason: %s\n", strerror(errno));
731 if (rlim.rlim_cur <= (nclients + 2))
733 fprintf(stderr, "You need at least %d open files resource but you are only allowed to use %ld.\n", nclients + 2, (long) rlim.rlim_cur);
734 fprintf(stderr, "Use limit/ulimt to increase the limit before using pgbench.\n");
737 #endif /* #ifndef __CYGWIN__ */
746 fprintf(stderr, "invalid scaling factor: %d\n", tps);
751 nxacts = atoi(optarg);
754 fprintf(stderr, "invalid number of transactions: %d\n", nxacts);
775 dbName = argv[optind];
778 dbName = getenv("USER");
791 state = (CState *) malloc(sizeof(*state) * nclients);
792 memset(state, 0, sizeof(*state));
798 snprintf(logpath, 64, "pgbench_log.%d", getpid());
799 LOGFILE = fopen(logpath, "w");
803 fprintf(stderr, "Couldn't open logfile \"%s\": %s", logpath, strerror(errno));
810 printf("pghost: %s pgport: %s nclients: %d nxacts: %d dbName: %s\n",
811 pghost, pgport, nclients, nxacts, dbName);
814 /* opening connection... */
819 if (PQstatus(con) == CONNECTION_BAD)
821 fprintf(stderr, "Connection to database '%s' failed.\n", dbName);
822 fprintf(stderr, "%s", PQerrorMessage(con));
827 * get the scaling factor that should be same as count(*) from
830 res = PQexec(con, "select count(*) from branches");
831 if (PQresultStatus(res) != PGRES_TUPLES_OK)
833 fprintf(stderr, "%s", PQerrorMessage(con));
836 tps = atoi(PQgetvalue(res, 0, 0));
839 fprintf(stderr, "count(*) from branches invalid (%d)\n", tps);
846 fprintf(stderr, "starting vacuum...");
847 res = PQexec(con, "vacuum branches");
848 if (PQresultStatus(res) != PGRES_COMMAND_OK)
850 fprintf(stderr, "%s", PQerrorMessage(con));
855 res = PQexec(con, "vacuum tellers");
856 if (PQresultStatus(res) != PGRES_COMMAND_OK)
858 fprintf(stderr, "%s", PQerrorMessage(con));
863 res = PQexec(con, "delete from history");
864 if (PQresultStatus(res) != PGRES_COMMAND_OK)
866 fprintf(stderr, "%s", PQerrorMessage(con));
870 res = PQexec(con, "vacuum history");
871 if (PQresultStatus(res) != PGRES_COMMAND_OK)
873 fprintf(stderr, "%s", PQerrorMessage(con));
878 fprintf(stderr, "end.\n");
882 fprintf(stderr, "starting full vacuum...");
883 res = PQexec(con, "vacuum analyze accounts");
884 if (PQresultStatus(res) != PGRES_COMMAND_OK)
886 fprintf(stderr, "%s", PQerrorMessage(con));
890 fprintf(stderr, "end.\n");
895 /* set random seed */
896 gettimeofday(&tv1, 0);
897 srand((uint) tv1.tv_usec);
899 /* get start up time */
900 gettimeofday(&tv1, 0);
904 /* make connections to the database */
905 for (i = 0; i < nclients; i++)
908 if ((state[i].con = doConnect()) == NULL)
913 /* time after connections set up */
914 gettimeofday(&tv2, 0);
916 /* send start up queries in async manner */
917 for (i = 0; i < nclients; i++)
919 if (ttype == 0 || ttype == 2)
920 doOne(state, i, debug, ttype);
922 doSelectOnly(state, i, debug);
929 disconnect_all(state);
931 gettimeofday(&tv3, 0);
932 printResults(ttype, state, &tv1, &tv2, &tv3);
938 FD_ZERO(&input_mask);
941 for (i = 0; i < nclients; i++)
945 int sock = PQsocket(state[i].con);
949 fprintf(stderr, "Client %d: PQsocket failed\n", i);
950 disconnect_all(state);
953 FD_SET(sock, &input_mask);
959 if ((nsocks = select(maxsock + 1, &input_mask, (fd_set *) NULL,
960 (fd_set *) NULL, (struct timeval *) NULL)) < 0)
964 /* must be something wrong */
965 disconnect_all(state);
966 fprintf(stderr, "select failed: %s\n", strerror(errno));
969 else if (nsocks == 0)
971 fprintf(stderr, "select timeout\n");
972 for (i = 0; i < nclients; i++)
974 fprintf(stderr, "client %d:state %d cnt %d ecnt %d listen %d\n",
975 i, state[i].state, state[i].cnt, state[i].ecnt, state[i].listen);
980 /* ok, backend returns reply */
981 for (i = 0; i < nclients; i++)
983 if (state[i].con && FD_ISSET(PQsocket(state[i].con), &input_mask))
985 if (ttype == 0 || ttype == 2)
986 doOne(state, i, debug, ttype);
988 doSelectOnly(state, i, debug);