Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * pgbench.c
3 : : *
4 : : * A simple benchmark program for PostgreSQL
5 : : * Originally written by Tatsuo Ishii and enhanced by many contributors.
6 : : *
7 : : * src/bin/pgbench/pgbench.c
8 : : * Copyright (c) 2000-2025, PostgreSQL Global Development Group
9 : : * ALL RIGHTS RESERVED;
10 : : *
11 : : * Permission to use, copy, modify, and distribute this software and its
12 : : * documentation for any purpose, without fee, and without a written agreement
13 : : * is hereby granted, provided that the above copyright notice and this
14 : : * paragraph and the following two paragraphs appear in all copies.
15 : : *
16 : : * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
17 : : * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
18 : : * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
19 : : * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
20 : : * POSSIBILITY OF SUCH DAMAGE.
21 : : *
22 : : * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES,
23 : : * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
24 : : * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
25 : : * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
26 : : * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
27 : : *
28 : : */
29 : :
30 : : #if defined(WIN32) && FD_SETSIZE < 1024
31 : : #error FD_SETSIZE needs to have been increased
32 : : #endif
33 : :
34 : : #include "postgres_fe.h"
35 : :
36 : : #include <ctype.h>
37 : : #include <float.h>
38 : : #include <limits.h>
39 : : #include <math.h>
40 : : #include <signal.h>
41 : : #include <time.h>
42 : : #include <sys/time.h>
43 : : #include <sys/resource.h> /* for getrlimit */
44 : :
45 : : /* For testing, PGBENCH_USE_SELECT can be defined to force use of that code */
46 : : #if defined(HAVE_PPOLL) && !defined(PGBENCH_USE_SELECT)
47 : : #define POLL_USING_PPOLL
48 : : #ifdef HAVE_POLL_H
49 : : #include <poll.h>
50 : : #endif
51 : : #else /* no ppoll(), so use select() */
52 : : #define POLL_USING_SELECT
53 : : #include <sys/select.h>
54 : : #endif
55 : :
56 : : #include "catalog/pg_class_d.h"
57 : : #include "common/int.h"
58 : : #include "common/logging.h"
59 : : #include "common/pg_prng.h"
60 : : #include "common/string.h"
61 : : #include "common/username.h"
62 : : #include "fe_utils/cancel.h"
63 : : #include "fe_utils/conditional.h"
64 : : #include "fe_utils/option_utils.h"
65 : : #include "fe_utils/string_utils.h"
66 : : #include "getopt_long.h"
67 : : #include "libpq-fe.h"
68 : : #include "pgbench.h"
69 : : #include "port/pg_bitutils.h"
70 : : #include "portability/instr_time.h"
71 : :
72 : : /* X/Open (XSI) requires <math.h> to provide M_PI, but core POSIX does not */
73 : : #ifndef M_PI
74 : : #define M_PI 3.14159265358979323846
75 : : #endif
76 : :
77 : : #define ERRCODE_T_R_SERIALIZATION_FAILURE "40001"
78 : : #define ERRCODE_T_R_DEADLOCK_DETECTED "40P01"
79 : : #define ERRCODE_UNDEFINED_TABLE "42P01"
80 : :
81 : : /*
82 : : * Hashing constants
83 : : */
84 : : #define FNV_PRIME UINT64CONST(0x100000001b3)
85 : : #define FNV_OFFSET_BASIS UINT64CONST(0xcbf29ce484222325)
86 : : #define MM2_MUL UINT64CONST(0xc6a4a7935bd1e995)
87 : : #define MM2_MUL_TIMES_8 UINT64CONST(0x35253c9ade8f4ca8)
88 : : #define MM2_ROT 47
89 : :
90 : : /*
91 : : * Multi-platform socket set implementations
92 : : */
93 : :
94 : : #ifdef POLL_USING_PPOLL
95 : : #define SOCKET_WAIT_METHOD "ppoll"
96 : :
97 : : typedef struct socket_set
98 : : {
99 : : int maxfds; /* allocated length of pollfds[] array */
100 : : int curfds; /* number currently in use */
101 : : struct pollfd pollfds[FLEXIBLE_ARRAY_MEMBER];
102 : : } socket_set;
103 : :
104 : : #endif /* POLL_USING_PPOLL */
105 : :
106 : : #ifdef POLL_USING_SELECT
107 : : #define SOCKET_WAIT_METHOD "select"
108 : :
109 : : typedef struct socket_set
110 : : {
111 : : int maxfd; /* largest FD currently set in fds */
112 : : fd_set fds;
113 : : } socket_set;
114 : :
115 : : #endif /* POLL_USING_SELECT */
116 : :
117 : : /*
118 : : * Multi-platform thread implementations
119 : : */
120 : :
121 : : #ifdef WIN32
122 : : /* Use Windows threads */
123 : : #include <windows.h>
124 : : #define GETERRNO() (_dosmaperr(GetLastError()), errno)
125 : : #define THREAD_T HANDLE
126 : : #define THREAD_FUNC_RETURN_TYPE unsigned
127 : : #define THREAD_FUNC_RETURN return 0
128 : : #define THREAD_FUNC_CC __stdcall
129 : : #define THREAD_CREATE(handle, function, arg) \
130 : : ((*(handle) = (HANDLE) _beginthreadex(NULL, 0, (function), (arg), 0, NULL)) == 0 ? errno : 0)
131 : : #define THREAD_JOIN(handle) \
132 : : (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 ? \
133 : : GETERRNO() : CloseHandle(handle) ? 0 : GETERRNO())
134 : : #define THREAD_BARRIER_T SYNCHRONIZATION_BARRIER
135 : : #define THREAD_BARRIER_INIT(barrier, n) \
136 : : (InitializeSynchronizationBarrier((barrier), (n), 0) ? 0 : GETERRNO())
137 : : #define THREAD_BARRIER_WAIT(barrier) \
138 : : EnterSynchronizationBarrier((barrier), \
139 : : SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY)
140 : : #define THREAD_BARRIER_DESTROY(barrier)
141 : : #else
142 : : /* Use POSIX threads */
143 : : #include "port/pg_pthread.h"
144 : : #define THREAD_T pthread_t
145 : : #define THREAD_FUNC_RETURN_TYPE void *
146 : : #define THREAD_FUNC_RETURN return NULL
147 : : #define THREAD_FUNC_CC
148 : : #define THREAD_CREATE(handle, function, arg) \
149 : : pthread_create((handle), NULL, (function), (arg))
150 : : #define THREAD_JOIN(handle) \
151 : : pthread_join((handle), NULL)
152 : : #define THREAD_BARRIER_T pthread_barrier_t
153 : : #define THREAD_BARRIER_INIT(barrier, n) \
154 : : pthread_barrier_init((barrier), NULL, (n))
155 : : #define THREAD_BARRIER_WAIT(barrier) pthread_barrier_wait((barrier))
156 : : #define THREAD_BARRIER_DESTROY(barrier) pthread_barrier_destroy((barrier))
157 : : #endif
158 : :
159 : :
160 : : /********************************************************************
161 : : * some configurable parameters */
162 : :
163 : : #define DEFAULT_INIT_STEPS "dtgvp" /* default -I setting */
164 : : #define ALL_INIT_STEPS "dtgGvpf" /* all possible steps */
165 : :
166 : : #define LOG_STEP_SECONDS 5 /* seconds between log messages */
167 : : #define DEFAULT_NXACTS 10 /* default nxacts */
168 : :
169 : : #define MIN_GAUSSIAN_PARAM 2.0 /* minimum parameter for gauss */
170 : :
171 : : #define MIN_ZIPFIAN_PARAM 1.001 /* minimum parameter for zipfian */
172 : : #define MAX_ZIPFIAN_PARAM 1000.0 /* maximum parameter for zipfian */
173 : :
174 : : static int nxacts = 0; /* number of transactions per client */
175 : : static int duration = 0; /* duration in seconds */
176 : : static int64 end_time = 0; /* when to stop in micro seconds, under -T */
177 : :
178 : : /*
179 : : * scaling factor. for example, scale = 10 will make 1000000 tuples in
180 : : * pgbench_accounts table.
181 : : */
182 : : static int scale = 1;
183 : :
184 : : /*
185 : : * fillfactor. for example, fillfactor = 90 will use only 90 percent
186 : : * space during inserts and leave 10 percent free.
187 : : */
188 : : static int fillfactor = 100;
189 : :
190 : : /*
191 : : * use unlogged tables?
192 : : */
193 : : static bool unlogged_tables = false;
194 : :
195 : : /*
196 : : * log sampling rate (1.0 = log everything, 0.0 = option not given)
197 : : */
198 : : static double sample_rate = 0.0;
199 : :
200 : : /*
201 : : * When threads are throttled to a given rate limit, this is the target delay
202 : : * to reach that rate in usec. 0 is the default and means no throttling.
203 : : */
204 : : static double throttle_delay = 0;
205 : :
206 : : /*
207 : : * Transactions which take longer than this limit (in usec) are counted as
208 : : * late, and reported as such, although they are completed anyway. When
209 : : * throttling is enabled, execution time slots that are more than this late
210 : : * are skipped altogether, and counted separately.
211 : : */
212 : : static int64 latency_limit = 0;
213 : :
214 : : /*
215 : : * tablespace selection
216 : : */
217 : : static char *tablespace = NULL;
218 : : static char *index_tablespace = NULL;
219 : :
220 : : /*
221 : : * Number of "pgbench_accounts" partitions. 0 is the default and means no
222 : : * partitioning.
223 : : */
224 : : static int partitions = 0;
225 : :
226 : : /* partitioning strategy for "pgbench_accounts" */
227 : : typedef enum
228 : : {
229 : : PART_NONE, /* no partitioning */
230 : : PART_RANGE, /* range partitioning */
231 : : PART_HASH, /* hash partitioning */
232 : : } partition_method_t;
233 : :
234 : : static partition_method_t partition_method = PART_NONE;
235 : : static const char *const PARTITION_METHOD[] = {"none", "range", "hash"};
236 : :
237 : : /* random seed used to initialize base_random_sequence */
238 : : static int64 random_seed = -1;
239 : :
240 : : /*
241 : : * end of configurable parameters
242 : : *********************************************************************/
243 : :
244 : : #define nbranches 1 /* Makes little sense to change this. Change
245 : : * -s instead */
246 : : #define ntellers 10
247 : : #define naccounts 100000
248 : :
249 : : /*
250 : : * The scale factor at/beyond which 32bit integers are incapable of storing
251 : : * 64bit values.
252 : : *
253 : : * Although the actual threshold is 21474, we use 20000 because it is easier to
254 : : * document and remember, and isn't that far away from the real threshold.
255 : : */
256 : : #define SCALE_32BIT_THRESHOLD 20000
257 : :
258 : : static bool use_log; /* log transaction latencies to a file */
259 : : static bool use_quiet; /* quiet logging onto stderr */
260 : : static int agg_interval; /* log aggregates instead of individual
261 : : * transactions */
262 : : static bool per_script_stats = false; /* whether to collect stats per script */
263 : : static int progress = 0; /* thread progress report every this seconds */
264 : : static bool progress_timestamp = false; /* progress report with Unix time */
265 : : static int nclients = 1; /* number of clients */
266 : : static int nthreads = 1; /* number of threads */
267 : : static bool is_connect; /* establish connection for each transaction */
268 : : static bool report_per_command = false; /* report per-command latencies,
269 : : * retries after errors and failures
270 : : * (errors without retrying) */
271 : : static int main_pid; /* main process id used in log filename */
272 : :
273 : : /*
274 : : * There are different types of restrictions for deciding that the current
275 : : * transaction with a serialization/deadlock error can no longer be retried and
276 : : * should be reported as failed:
277 : : * - max_tries (--max-tries) can be used to limit the number of tries;
278 : : * - latency_limit (-L) can be used to limit the total time of tries;
279 : : * - duration (-T) can be used to limit the total benchmark time.
280 : : *
281 : : * They can be combined together, and you need to use at least one of them to
282 : : * retry the transactions with serialization/deadlock errors. If none of them is
283 : : * used, the default value of max_tries is 1 and such transactions will not be
284 : : * retried.
285 : : */
286 : :
287 : : /*
288 : : * We cannot retry a transaction after the serialization/deadlock error if its
289 : : * number of tries reaches this maximum; if its value is zero, it is not used.
290 : : */
291 : : static uint32 max_tries = 1;
292 : :
293 : : static bool failures_detailed = false; /* whether to group failures in
294 : : * reports or logs by basic types */
295 : :
296 : : static const char *pghost = NULL;
297 : : static const char *pgport = NULL;
298 : : static const char *username = NULL;
299 : : static const char *dbName = NULL;
300 : : static char *logfile_prefix = NULL;
301 : : static const char *progname;
302 : :
303 : : #define WSEP '@' /* weight separator */
304 : :
305 : : static volatile sig_atomic_t timer_exceeded = false; /* flag from signal
306 : : * handler */
307 : :
308 : : /*
309 : : * We don't want to allocate variables one by one; for efficiency, add a
310 : : * constant margin each time it overflows.
311 : : */
312 : : #define VARIABLES_ALLOC_MARGIN 8
313 : :
314 : : /*
315 : : * Variable definitions.
316 : : *
317 : : * If a variable only has a string value, "svalue" is that value, and value is
318 : : * "not set". If the value is known, "value" contains the value (in any
319 : : * variant).
320 : : *
321 : : * In this case "svalue" contains the string equivalent of the value, if we've
322 : : * had occasion to compute that, or NULL if we haven't.
323 : : */
324 : : typedef struct
325 : : {
326 : : char *name; /* variable's name */
327 : : char *svalue; /* its value in string form, if known */
328 : : PgBenchValue value; /* actual variable's value */
329 : : } Variable;
330 : :
331 : : /*
332 : : * Data structure for client variables.
333 : : */
334 : : typedef struct
335 : : {
336 : : Variable *vars; /* array of variable definitions */
337 : : int nvars; /* number of variables */
338 : :
339 : : /*
340 : : * The maximum number of variables that we can currently store in 'vars'
341 : : * without having to reallocate more space. We must always have max_vars
342 : : * >= nvars.
343 : : */
344 : : int max_vars;
345 : :
346 : : bool vars_sorted; /* are variables sorted by name? */
347 : : } Variables;
348 : :
349 : : #define MAX_SCRIPTS 128 /* max number of SQL scripts allowed */
350 : : #define SHELL_COMMAND_SIZE 256 /* maximum size allowed for shell command */
351 : :
352 : : /*
353 : : * Simple data structure to keep stats about something.
354 : : *
355 : : * XXX probably the first value should be kept and used as an offset for
356 : : * better numerical stability...
357 : : */
358 : : typedef struct SimpleStats
359 : : {
360 : : int64 count; /* how many values were encountered */
361 : : double min; /* the minimum seen */
362 : : double max; /* the maximum seen */
363 : : double sum; /* sum of values */
364 : : double sum2; /* sum of squared values */
365 : : } SimpleStats;
366 : :
367 : : /*
368 : : * The instr_time type is expensive when dealing with time arithmetic. Define
369 : : * a type to hold microseconds instead. Type int64 is good enough for about
370 : : * 584500 years.
371 : : */
372 : : typedef int64 pg_time_usec_t;
373 : :
374 : : /*
375 : : * Data structure to hold various statistics: per-thread and per-script stats
376 : : * are maintained and merged together.
377 : : */
378 : : typedef struct StatsData
379 : : {
380 : : pg_time_usec_t start_time; /* interval start time, for aggregates */
381 : :
382 : : /*----------
383 : : * Transactions are counted depending on their execution and outcome.
384 : : * First a transaction may have started or not: skipped transactions occur
385 : : * under --rate and --latency-limit when the client is too late to execute
386 : : * them. Secondly, a started transaction may ultimately succeed or fail,
387 : : * possibly after some retries when --max-tries is not one. Thus
388 : : *
389 : : * the number of all transactions =
390 : : * 'skipped' (it was too late to execute them) +
391 : : * 'cnt' (the number of successful transactions) +
392 : : * 'failed' (the number of failed transactions).
393 : : *
394 : : * A successful transaction can have several unsuccessful tries before a
395 : : * successful run. Thus
396 : : *
397 : : * 'cnt' (the number of successful transactions) =
398 : : * successfully retried transactions (they got a serialization or a
399 : : * deadlock error(s), but were
400 : : * successfully retried from the very
401 : : * beginning) +
402 : : * directly successful transactions (they were successfully completed on
403 : : * the first try).
404 : : *
405 : : * 'failed' (the number of failed transactions) =
406 : : * 'serialization_failures' (they got a serialization error and were not
407 : : * successfully retried) +
408 : : * 'deadlock_failures' (they got a deadlock error and were not
409 : : * successfully retried) +
410 : : * 'other_sql_failures' (they failed on the first try or after retries
411 : : * due to a SQL error other than serialization or
412 : : * deadlock; they are counted as a failed transaction
413 : : * only when --continue-on-error is specified).
414 : : *
415 : : * If the transaction was retried after a serialization or a deadlock
416 : : * error this does not guarantee that this retry was successful. Thus
417 : : *
418 : : * 'retries' (number of retries) =
419 : : * number of retries in all retried transactions =
420 : : * number of retries in (successfully retried transactions +
421 : : * failed transactions);
422 : : *
423 : : * 'retried' (number of all retried transactions) =
424 : : * successfully retried transactions +
425 : : * unsuccessful retried transactions.
426 : : *----------
427 : : */
428 : : int64 cnt; /* number of successful transactions, not
429 : : * including 'skipped' */
430 : : int64 skipped; /* number of transactions skipped under --rate
431 : : * and --latency-limit */
432 : : int64 retries; /* number of retries after a serialization or
433 : : * a deadlock error in all the transactions */
434 : : int64 retried; /* number of all transactions that were
435 : : * retried after a serialization or a deadlock
436 : : * error (perhaps the last try was
437 : : * unsuccessful) */
438 : : int64 serialization_failures; /* number of transactions that were
439 : : * not successfully retried after a
440 : : * serialization error */
441 : : int64 deadlock_failures; /* number of transactions that were not
442 : : * successfully retried after a deadlock
443 : : * error */
444 : : int64 other_sql_failures; /* number of failed transactions for
445 : : * reasons other than
446 : : * serialization/deadlock failure, which
447 : : * is counted if --continue-on-error is
448 : : * specified */
449 : : SimpleStats latency;
450 : : SimpleStats lag;
451 : : } StatsData;
452 : :
453 : : /*
454 : : * For displaying Unix epoch timestamps, as some time functions may have
455 : : * another reference.
456 : : */
457 : : static pg_time_usec_t epoch_shift;
458 : :
459 : : /*
460 : : * Error status for errors during script execution.
461 : : */
462 : : typedef enum EStatus
463 : : {
464 : : ESTATUS_NO_ERROR = 0,
465 : : ESTATUS_META_COMMAND_ERROR,
466 : : ESTATUS_CONN_ERROR,
467 : :
468 : : /* SQL errors */
469 : : ESTATUS_SERIALIZATION_ERROR,
470 : : ESTATUS_DEADLOCK_ERROR,
471 : : ESTATUS_OTHER_SQL_ERROR,
472 : : } EStatus;
473 : :
474 : : /*
475 : : * Transaction status at the end of a command.
476 : : */
477 : : typedef enum TStatus
478 : : {
479 : : TSTATUS_IDLE,
480 : : TSTATUS_IN_BLOCK,
481 : : TSTATUS_CONN_ERROR,
482 : : TSTATUS_OTHER_ERROR,
483 : : } TStatus;
484 : :
485 : : /* Various random sequences are initialized from this one. */
486 : : static pg_prng_state base_random_sequence;
487 : :
488 : : /* Synchronization barrier for start and connection */
489 : : static THREAD_BARRIER_T barrier;
490 : :
491 : : /*
492 : : * Connection state machine states.
493 : : */
494 : : typedef enum
495 : : {
496 : : /*
497 : : * The client must first choose a script to execute. Once chosen, it can
498 : : * either be throttled (state CSTATE_PREPARE_THROTTLE under --rate), start
499 : : * right away (state CSTATE_START_TX) or not start at all if the timer was
500 : : * exceeded (state CSTATE_FINISHED).
501 : : */
502 : : CSTATE_CHOOSE_SCRIPT,
503 : :
504 : : /*
505 : : * CSTATE_START_TX performs start-of-transaction processing. Establishes
506 : : * a new connection for the transaction in --connect mode, records the
507 : : * transaction start time, and proceed to the first command.
508 : : *
509 : : * Note: once a script is started, it will either error or run till its
510 : : * end, where it may be interrupted. It is not interrupted while running,
511 : : * so pgbench --time is to be understood as tx are allowed to start in
512 : : * that time, and will finish when their work is completed.
513 : : */
514 : : CSTATE_START_TX,
515 : :
516 : : /*
517 : : * In CSTATE_PREPARE_THROTTLE state, we calculate when to begin the next
518 : : * transaction, and advance to CSTATE_THROTTLE. CSTATE_THROTTLE state
519 : : * sleeps until that moment, then advances to CSTATE_START_TX, or
520 : : * CSTATE_FINISHED if the next transaction would start beyond the end of
521 : : * the run.
522 : : */
523 : : CSTATE_PREPARE_THROTTLE,
524 : : CSTATE_THROTTLE,
525 : :
526 : : /*
527 : : * We loop through these states, to process each command in the script:
528 : : *
529 : : * CSTATE_START_COMMAND starts the execution of a command. On a SQL
530 : : * command, the command is sent to the server, and we move to
531 : : * CSTATE_WAIT_RESULT state unless in pipeline mode. On a \sleep
532 : : * meta-command, the timer is set, and we enter the CSTATE_SLEEP state to
533 : : * wait for it to expire. Other meta-commands are executed immediately. If
534 : : * the command about to start is actually beyond the end of the script,
535 : : * advance to CSTATE_END_TX.
536 : : *
537 : : * CSTATE_WAIT_RESULT waits until we get a result set back from the server
538 : : * for the current command.
539 : : *
540 : : * CSTATE_SLEEP waits until the end of \sleep.
541 : : *
542 : : * CSTATE_END_COMMAND records the end-of-command timestamp, increments the
543 : : * command counter, and loops back to CSTATE_START_COMMAND state.
544 : : *
545 : : * CSTATE_SKIP_COMMAND is used by conditional branches which are not
546 : : * executed. It quickly skip commands that do not need any evaluation.
547 : : * This state can move forward several commands, till there is something
548 : : * to do or the end of the script.
549 : : */
550 : : CSTATE_START_COMMAND,
551 : : CSTATE_WAIT_RESULT,
552 : : CSTATE_SLEEP,
553 : : CSTATE_END_COMMAND,
554 : : CSTATE_SKIP_COMMAND,
555 : :
556 : : /*
557 : : * States for failed commands.
558 : : *
559 : : * If the SQL/meta command fails, in CSTATE_ERROR clean up after an error:
560 : : * (1) clear the conditional stack; (2) if we have an unterminated
561 : : * (possibly failed) transaction block, send the rollback command to the
562 : : * server and wait for the result in CSTATE_WAIT_ROLLBACK_RESULT. If
563 : : * something goes wrong with rolling back, go to CSTATE_ABORTED.
564 : : *
565 : : * But if everything is ok we are ready for future transactions: if this
566 : : * is a serialization or deadlock error and we can re-execute the
567 : : * transaction from the very beginning, go to CSTATE_RETRY; otherwise go
568 : : * to CSTATE_FAILURE.
569 : : *
570 : : * In CSTATE_RETRY report an error, set the same parameters for the
571 : : * transaction execution as in the previous tries and process the first
572 : : * transaction command in CSTATE_START_COMMAND.
573 : : *
574 : : * In CSTATE_FAILURE report a failure, set the parameters for the
575 : : * transaction execution as they were before the first run of this
576 : : * transaction (except for a random state) and go to CSTATE_END_TX to
577 : : * complete this transaction.
578 : : */
579 : : CSTATE_ERROR,
580 : : CSTATE_WAIT_ROLLBACK_RESULT,
581 : : CSTATE_RETRY,
582 : : CSTATE_FAILURE,
583 : :
584 : : /*
585 : : * CSTATE_END_TX performs end-of-transaction processing. It calculates
586 : : * latency, and logs the transaction. In --connect mode, it closes the
587 : : * current connection.
588 : : *
589 : : * Then either starts over in CSTATE_CHOOSE_SCRIPT, or enters
590 : : * CSTATE_FINISHED if we have no more work to do.
591 : : */
592 : : CSTATE_END_TX,
593 : :
594 : : /*
595 : : * Final states. CSTATE_ABORTED means that the script execution was
596 : : * aborted because a command failed, CSTATE_FINISHED means success.
597 : : */
598 : : CSTATE_ABORTED,
599 : : CSTATE_FINISHED,
600 : : } ConnectionStateEnum;
601 : :
602 : : /*
603 : : * Connection state.
604 : : */
605 : : typedef struct
606 : : {
607 : : PGconn *con; /* connection handle to DB */
608 : : int id; /* client No. */
609 : : ConnectionStateEnum state; /* state machine's current state. */
610 : : ConditionalStack cstack; /* enclosing conditionals state */
611 : :
612 : : /*
613 : : * Separate randomness for each client. This is used for random functions
614 : : * PGBENCH_RANDOM_* during the execution of the script.
615 : : */
616 : : pg_prng_state cs_func_rs;
617 : :
618 : : int use_file; /* index in sql_script for this client */
619 : : int command; /* command number in script */
620 : : int num_syncs; /* number of ongoing sync commands */
621 : :
622 : : /* client variables */
623 : : Variables variables;
624 : :
625 : : /* various times about current transaction in microseconds */
626 : : pg_time_usec_t txn_scheduled; /* scheduled start time of transaction */
627 : : pg_time_usec_t sleep_until; /* scheduled start time of next cmd */
628 : : pg_time_usec_t txn_begin; /* used for measuring schedule lag times */
629 : : pg_time_usec_t stmt_begin; /* used for measuring statement latencies */
630 : :
631 : : /* whether client prepared each command of each script */
632 : : bool **prepared;
633 : :
634 : : /*
635 : : * For processing failures and repeating transactions with serialization
636 : : * or deadlock errors:
637 : : */
638 : : EStatus estatus; /* the error status of the current transaction
639 : : * execution; this is ESTATUS_NO_ERROR if
640 : : * there were no errors */
641 : : pg_prng_state random_state; /* random state */
642 : : uint32 tries; /* how many times have we already tried the
643 : : * current transaction? */
644 : :
645 : : /* per client collected stats */
646 : : int64 cnt; /* client transaction count, for -t; skipped
647 : : * and failed transactions are also counted
648 : : * here */
649 : : } CState;
650 : :
651 : : /*
652 : : * Thread state
653 : : */
654 : : typedef struct
655 : : {
656 : : int tid; /* thread id */
657 : : THREAD_T thread; /* thread handle */
658 : : CState *state; /* array of CState */
659 : : int nstate; /* length of state[] */
660 : :
661 : : /*
662 : : * Separate randomness for each thread. Each thread option uses its own
663 : : * random state to make all of them independent of each other and
664 : : * therefore deterministic at the thread level.
665 : : */
666 : : pg_prng_state ts_choose_rs; /* random state for selecting a script */
667 : : pg_prng_state ts_throttle_rs; /* random state for transaction throttling */
668 : : pg_prng_state ts_sample_rs; /* random state for log sampling */
669 : :
670 : : int64 throttle_trigger; /* previous/next throttling (us) */
671 : : FILE *logfile; /* where to log, or NULL */
672 : :
673 : : /* per thread collected stats in microseconds */
674 : : pg_time_usec_t create_time; /* thread creation time */
675 : : pg_time_usec_t started_time; /* thread is running */
676 : : pg_time_usec_t bench_start; /* thread is benchmarking */
677 : : pg_time_usec_t conn_duration; /* cumulated connection and disconnection
678 : : * delays */
679 : :
680 : : StatsData stats;
681 : : int64 latency_late; /* count executed but late transactions */
682 : : } TState;
683 : :
684 : : /*
685 : : * queries read from files
686 : : */
687 : : #define SQL_COMMAND 1
688 : : #define META_COMMAND 2
689 : :
690 : : /*
691 : : * max number of backslash command arguments or SQL variables,
692 : : * including the command or SQL statement itself
693 : : */
694 : : #define MAX_ARGS 256
695 : :
696 : : typedef enum MetaCommand
697 : : {
698 : : META_NONE, /* not a known meta-command */
699 : : META_SET, /* \set */
700 : : META_SETSHELL, /* \setshell */
701 : : META_SHELL, /* \shell */
702 : : META_SLEEP, /* \sleep */
703 : : META_GSET, /* \gset */
704 : : META_ASET, /* \aset */
705 : : META_IF, /* \if */
706 : : META_ELIF, /* \elif */
707 : : META_ELSE, /* \else */
708 : : META_ENDIF, /* \endif */
709 : : META_STARTPIPELINE, /* \startpipeline */
710 : : META_SYNCPIPELINE, /* \syncpipeline */
711 : : META_ENDPIPELINE, /* \endpipeline */
712 : : } MetaCommand;
713 : :
714 : : typedef enum QueryMode
715 : : {
716 : : QUERY_SIMPLE, /* simple query */
717 : : QUERY_EXTENDED, /* extended query */
718 : : QUERY_PREPARED, /* extended query with prepared statements */
719 : : NUM_QUERYMODE
720 : : } QueryMode;
721 : :
722 : : static QueryMode querymode = QUERY_SIMPLE;
723 : : static const char *const QUERYMODE[] = {"simple", "extended", "prepared"};
724 : :
725 : : /*
726 : : * struct Command represents one command in a script.
727 : : *
728 : : * lines The raw, possibly multi-line command text. Variable substitution
729 : : * not applied.
730 : : * first_line A short, single-line extract of 'lines', for error reporting.
731 : : * type SQL_COMMAND or META_COMMAND
732 : : * meta The type of meta-command, with META_NONE/GSET/ASET if command
733 : : * is SQL.
734 : : * argc Number of arguments of the command, 0 if not yet processed.
735 : : * argv Command arguments, the first of which is the command or SQL
736 : : * string itself. For SQL commands, after post-processing
737 : : * argv[0] is the same as 'lines' with variables substituted.
738 : : * prepname The name that this command is prepared under, in prepare mode
739 : : * varprefix SQL commands terminated with \gset or \aset have this set
740 : : * to a non NULL value. If nonempty, it's used to prefix the
741 : : * variable name that receives the value.
742 : : * aset do gset on all possible queries of a combined query (\;).
743 : : * expr Parsed expression, if needed.
744 : : * stats Time spent in this command.
745 : : * retries Number of retries after a serialization or deadlock error in the
746 : : * current command.
747 : : * failures Number of errors in the current command that were not retried.
748 : : */
749 : : typedef struct Command
750 : : {
751 : : PQExpBufferData lines;
752 : : char *first_line;
753 : : int type;
754 : : MetaCommand meta;
755 : : int argc;
756 : : char *argv[MAX_ARGS];
757 : : char *prepname;
758 : : char *varprefix;
759 : : PgBenchExpr *expr;
760 : : SimpleStats stats;
761 : : int64 retries;
762 : : int64 failures;
763 : : } Command;
764 : :
765 : : typedef struct ParsedScript
766 : : {
767 : : const char *desc; /* script descriptor (eg, file name) */
768 : : int weight; /* selection weight */
769 : : Command **commands; /* NULL-terminated array of Commands */
770 : : StatsData stats; /* total time spent in script */
771 : : } ParsedScript;
772 : :
773 : : static ParsedScript sql_script[MAX_SCRIPTS]; /* SQL script files */
774 : : static int num_scripts; /* number of scripts in sql_script[] */
775 : : static int64 total_weight = 0;
776 : :
777 : : static bool verbose_errors = false; /* print verbose messages of all errors */
778 : :
779 : : static bool exit_on_abort = false; /* exit when any client is aborted */
780 : : static bool continue_on_error = false; /* continue after errors */
781 : :
782 : : /* Builtin test scripts */
783 : : typedef struct BuiltinScript
784 : : {
785 : : const char *name; /* very short name for -b ... */
786 : : const char *desc; /* short description */
787 : : const char *script; /* actual pgbench script */
788 : : } BuiltinScript;
789 : :
790 : : static const BuiltinScript builtin_script[] =
791 : : {
792 : : {
793 : : "tpcb-like",
794 : : "<builtin: TPC-B (sort of)>",
795 : : "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
796 : : "\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n"
797 : : "\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n"
798 : : "\\set delta random(-5000, 5000)\n"
799 : : "BEGIN;\n"
800 : : "UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n"
801 : : "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
802 : : "UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;\n"
803 : : "UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;\n"
804 : : "INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);\n"
805 : : "END;\n"
806 : : },
807 : : {
808 : : "simple-update",
809 : : "<builtin: simple update>",
810 : : "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
811 : : "\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n"
812 : : "\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n"
813 : : "\\set delta random(-5000, 5000)\n"
814 : : "BEGIN;\n"
815 : : "UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n"
816 : : "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
817 : : "INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);\n"
818 : : "END;\n"
819 : : },
820 : : {
821 : : "select-only",
822 : : "<builtin: select only>",
823 : : "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
824 : : "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
825 : : }
826 : : };
827 : :
828 : :
829 : : /* Function prototypes */
830 : : static void setNullValue(PgBenchValue *pv);
831 : : static void setBoolValue(PgBenchValue *pv, bool bval);
832 : : static void setIntValue(PgBenchValue *pv, int64 ival);
833 : : static void setDoubleValue(PgBenchValue *pv, double dval);
834 : : static bool evaluateExpr(CState *st, PgBenchExpr *expr,
835 : : PgBenchValue *retval);
836 : : static ConnectionStateEnum executeMetaCommand(CState *st, pg_time_usec_t *now);
837 : : static void doLog(TState *thread, CState *st,
838 : : StatsData *agg, bool skipped, double latency, double lag);
839 : : static void processXactStats(TState *thread, CState *st, pg_time_usec_t *now,
840 : : bool skipped, StatsData *agg);
841 : : static void addScript(const ParsedScript *script);
842 : : static THREAD_FUNC_RETURN_TYPE THREAD_FUNC_CC threadRun(void *arg);
843 : : static void finishCon(CState *st);
844 : : static void setalarm(int seconds);
845 : : static socket_set *alloc_socket_set(int count);
846 : : static void free_socket_set(socket_set *sa);
847 : : static void clear_socket_set(socket_set *sa);
848 : : static void add_socket_to_set(socket_set *sa, int fd, int idx);
849 : : static int wait_on_socket_set(socket_set *sa, int64 usecs);
850 : : static bool socket_has_input(socket_set *sa, int fd, int idx);
851 : :
852 : : /* callback used to build rows for COPY during data loading */
853 : : typedef void (*initRowMethod) (PQExpBufferData *sql, int64 curr);
854 : :
855 : : /* callback functions for our flex lexer */
856 : : static const PsqlScanCallbacks pgbench_callbacks = {
857 : : NULL, /* don't need get_variable functionality */
858 : : };
859 : :
860 : : static char
308 melanieplageman@gmai 861 :CBC 6 : get_table_relkind(PGconn *con, const char *table)
862 : : {
863 : : PGresult *res;
864 : : char *val;
865 : : char relkind;
866 : 6 : const char *params[1] = {table};
867 : 6 : const char *sql =
868 : : "SELECT relkind FROM pg_catalog.pg_class WHERE oid=$1::pg_catalog.regclass";
869 : :
870 : 6 : res = PQexecParams(con, sql, 1, NULL, params, NULL, NULL, 0);
871 [ - + ]: 6 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
872 : : {
308 melanieplageman@gmai 873 :UBC 0 : pg_log_error("query failed: %s", PQerrorMessage(con));
874 : 0 : pg_log_error_detail("Query was: %s", sql);
875 : 0 : exit(1);
876 : : }
308 melanieplageman@gmai 877 :CBC 6 : val = PQgetvalue(res, 0, 0);
878 [ - + ]: 6 : Assert(strlen(val) == 1);
879 : 6 : relkind = val[0];
880 : 6 : PQclear(res);
881 : :
882 : 6 : return relkind;
883 : : }
884 : :
885 : : static inline pg_time_usec_t
1742 tmunro@postgresql.or 886 : 11909 : pg_time_now(void)
887 : : {
888 : : instr_time now;
889 : :
890 : 11909 : INSTR_TIME_SET_CURRENT(now);
891 : :
892 : 11909 : return (pg_time_usec_t) INSTR_TIME_GET_MICROSEC(now);
893 : : }
894 : :
895 : : static inline void
896 : 10759 : pg_time_now_lazy(pg_time_usec_t *now)
897 : : {
898 [ + + ]: 10759 : if ((*now) == 0)
899 : 9737 : (*now) = pg_time_now();
900 : 10759 : }
901 : :
902 : : #define PG_TIME_GET_DOUBLE(t) (0.000001 * (t))
903 : :
904 : : static void
4913 rhaas@postgresql.org 905 : 1 : usage(void)
906 : : {
6136 peter_e@gmx.net 907 : 1 : printf("%s is a benchmarking tool for PostgreSQL.\n\n"
908 : : "Usage:\n"
909 : : " %s [OPTION]... [DBNAME]\n"
910 : : "\nInitialization options:\n"
911 : : " -i, --initialize invokes initialization mode\n"
912 : : " -I, --init-steps=[" ALL_INIT_STEPS "]+ (default \"" DEFAULT_INIT_STEPS "\")\n"
913 : : " run selected initialization steps, in the specified order\n"
914 : : " d: drop any existing pgbench tables\n"
915 : : " t: create the tables used by the standard pgbench scenario\n"
916 : : " g: generate data, client-side\n"
917 : : " G: generate data, server-side\n"
918 : : " v: invoke VACUUM on the standard tables\n"
919 : : " p: create primary key indexes on the standard tables\n"
920 : : " f: create foreign keys between the standard tables\n"
921 : : " -F, --fillfactor=NUM set fill factor\n"
922 : : " -n, --no-vacuum do not run VACUUM during initialization\n"
923 : : " -q, --quiet quiet logging (one message each 5 seconds)\n"
924 : : " -s, --scale=NUM scaling factor\n"
925 : : " --foreign-keys create foreign key constraints between tables\n"
926 : : " --index-tablespace=TABLESPACE\n"
927 : : " create indexes in the specified tablespace\n"
928 : : " --partition-method=(range|hash)\n"
929 : : " partition pgbench_accounts with this method (default: range)\n"
930 : : " --partitions=NUM partition pgbench_accounts into NUM parts (default: 0)\n"
931 : : " --tablespace=TABLESPACE create tables in the specified tablespace\n"
932 : : " --unlogged-tables create tables as unlogged tables\n"
933 : : "\nOptions to select what to run:\n"
934 : : " -b, --builtin=NAME[@W] add builtin script NAME weighted at W (default: 1)\n"
935 : : " (use \"-b list\" to list available scripts)\n"
936 : : " -f, --file=FILENAME[@W] add script FILENAME weighted at W (default: 1)\n"
937 : : " -N, --skip-some-updates skip updates of pgbench_tellers and pgbench_branches\n"
938 : : " (same as \"-b simple-update\")\n"
939 : : " -S, --select-only perform SELECT-only transactions\n"
940 : : " (same as \"-b select-only\")\n"
941 : : "\nBenchmarking options:\n"
942 : : " -c, --client=NUM number of concurrent database clients (default: 1)\n"
943 : : " -C, --connect establish new connection for each transaction\n"
944 : : " -D, --define=VARNAME=VALUE\n"
945 : : " define variable for use by custom script\n"
946 : : " -j, --jobs=NUM number of threads (default: 1)\n"
947 : : " -l, --log write transaction times to log file\n"
948 : : " -L, --latency-limit=NUM count transactions lasting more than NUM ms as late\n"
949 : : " -M, --protocol=simple|extended|prepared\n"
950 : : " protocol for submitting queries (default: simple)\n"
951 : : " -n, --no-vacuum do not run VACUUM before tests\n"
952 : : " -P, --progress=NUM show thread progress report every NUM seconds\n"
953 : : " -r, --report-per-command report latencies, failures, and retries per command\n"
954 : : " -R, --rate=NUM target rate in transactions per second\n"
955 : : " -s, --scale=NUM report this scale factor in output\n"
956 : : " -t, --transactions=NUM number of transactions each client runs (default: 10)\n"
957 : : " -T, --time=NUM duration of benchmark test in seconds\n"
958 : : " -v, --vacuum-all vacuum all four standard tables before tests\n"
959 : : " --aggregate-interval=NUM aggregate data over NUM seconds\n"
960 : : " --continue-on-error continue running after an SQL error\n"
961 : : " --exit-on-abort exit when any client is aborted\n"
962 : : " --failures-detailed report the failures grouped by basic types\n"
963 : : " --log-prefix=PREFIX prefix for transaction time log file\n"
964 : : " (default: \"pgbench_log\")\n"
965 : : " --max-tries=NUM max number of tries to run transaction (default: 1)\n"
966 : : " --progress-timestamp use Unix epoch timestamps for progress\n"
967 : : " --random-seed=SEED set random seed (\"time\", \"rand\", integer)\n"
968 : : " --sampling-rate=NUM fraction of transactions to log (e.g., 0.01 for 1%%)\n"
969 : : " --show-script=NAME show builtin script code, then exit\n"
970 : : " --verbose-errors print messages of all errors\n"
971 : : "\nCommon options:\n"
972 : : " --debug print debugging output\n"
973 : : " -d, --dbname=DBNAME database name to connect to\n"
974 : : " -h, --host=HOSTNAME database server host or socket directory\n"
975 : : " -p, --port=PORT database server port number\n"
976 : : " -U, --username=USERNAME connect as specified database user\n"
977 : : " -V, --version output version information, then exit\n"
978 : : " -?, --help show this help, then exit\n"
979 : : "\n"
980 : : "Report bugs to <%s>.\n"
981 : : "%s home page: <%s>\n",
982 : : progname, progname, PACKAGE_BUGREPORT, PACKAGE_NAME, PACKAGE_URL);
9467 ishii@postgresql.org 983 : 1 : }
984 : :
985 : : /*
986 : : * Return whether str matches "^\s*[-+]?[0-9]+$"
987 : : *
988 : : * This should agree with strtoint64() on what's accepted, ignoring overflows.
989 : : */
990 : : static bool
3550 rhaas@postgresql.org 991 : 533 : is_an_int(const char *str)
992 : : {
993 : 533 : const char *ptr = str;
994 : :
995 : : /* skip leading spaces */
996 [ + - - + ]: 533 : while (*ptr && isspace((unsigned char) *ptr))
3550 rhaas@postgresql.org 997 :UBC 0 : ptr++;
998 : :
999 : : /* skip sign */
3550 rhaas@postgresql.org 1000 [ + - + + ]:CBC 533 : if (*ptr == '+' || *ptr == '-')
1001 : 3 : ptr++;
1002 : :
1003 : : /* at least one digit */
1004 [ + - + + ]: 533 : if (*ptr && !isdigit((unsigned char) *ptr))
1005 : 2 : return false;
1006 : :
1007 : : /* eat all digits */
1008 [ + + + + ]: 1118 : while (*ptr && isdigit((unsigned char) *ptr))
1009 : 587 : ptr++;
1010 : :
1011 : : /* must have reached end of string */
1012 : 531 : return *ptr == '\0';
1013 : : }
1014 : :
1015 : :
1016 : : /*
1017 : : * strtoint64 -- convert a string to 64-bit integer
1018 : : *
1019 : : * The function returns whether the conversion worked, and if so
1020 : : * "*result" is set to the result.
1021 : : *
1022 : : * If not errorOK, an error message is also printed out on errors.
1023 : : */
1024 : : bool
2637 andres@anarazel.de 1025 : 1327 : strtoint64(const char *str, bool errorOK, int64 *result)
1026 : : {
1027 : : char *end;
1028 : :
25 heikki.linnakangas@i 1029 :GNC 1327 : errno = 0;
1030 : 1327 : *result = strtoi64(str, &end, 10);
1031 : :
1032 [ + + ]: 1327 : if (unlikely(errno == ERANGE))
1033 : : {
1034 [ - + ]: 1 : if (!errorOK)
25 heikki.linnakangas@i 1035 :UNC 0 : pg_log_error("value \"%s\" is out of range for type bigint", str);
25 heikki.linnakangas@i 1036 :GNC 1 : return false;
1037 : : }
1038 : :
1039 [ + - + - : 1326 : if (unlikely(errno != 0 || end == str || *end != '\0'))
- + - + ]
1040 : : {
25 heikki.linnakangas@i 1041 [ # # ]:UNC 0 : if (!errorOK)
1042 : 0 : pg_log_error("invalid input syntax for type bigint: \"%s\"", str);
1043 : 0 : return false;
1044 : : }
2637 andres@anarazel.de 1045 :CBC 1326 : return true;
1046 : : }
1047 : :
1048 : : /* convert string to double, detecting overflows/underflows */
1049 : : bool
1050 : 66 : strtodouble(const char *str, bool errorOK, double *dv)
1051 : : {
1052 : : char *end;
1053 : :
1054 : 66 : errno = 0;
1055 : 66 : *dv = strtod(str, &end);
1056 : :
25 heikki.linnakangas@i 1057 [ + + ]:GNC 66 : if (unlikely(errno == ERANGE))
1058 : : {
2637 andres@anarazel.de 1059 [ - + ]:CBC 2 : if (!errorOK)
2169 peter@eisentraut.org 1060 :UBC 0 : pg_log_error("value \"%s\" is out of range for type double", str);
2637 andres@anarazel.de 1061 :CBC 2 : return false;
1062 : : }
1063 : :
25 heikki.linnakangas@i 1064 [ + - + + :GNC 64 : if (unlikely(errno != 0 || end == str || *end != '\0'))
- + + + ]
1065 : : {
2637 andres@anarazel.de 1066 [ - + ]:CBC 2 : if (!errorOK)
2169 peter@eisentraut.org 1067 :UBC 0 : pg_log_error("invalid input syntax for type double: \"%s\"", str);
2637 andres@anarazel.de 1068 :CBC 2 : return false;
1069 : : }
1070 : 62 : return true;
1071 : : }
1072 : :
1073 : : /*
1074 : : * Initialize a prng state struct.
1075 : : *
1076 : : * We derive the seed from base_random_sequence, which must be set up already.
1077 : : */
1078 : : static void
1479 tgl@sss.pgh.pa.us 1079 : 397 : initRandomState(pg_prng_state *state)
1080 : : {
1081 : 397 : pg_prng_seed(state, pg_prng_uint64(&base_random_sequence));
2587 alvherre@alvh.no-ip. 1082 : 397 : }
1083 : :
1084 : :
1085 : : /*
1086 : : * random number generator: uniform distribution from min to max inclusive.
1087 : : *
1088 : : * Although the limits are expressed as int64, you can't generate the full
1089 : : * int64 range in one call, because the difference of the limits mustn't
1090 : : * overflow int64. This is not checked.
1091 : : */
1092 : : static int64
1479 tgl@sss.pgh.pa.us 1093 : 2865 : getrand(pg_prng_state *state, int64 min, int64 max)
1094 : : {
1095 : 2865 : return min + (int64) pg_prng_uint64_range(state, 0, max - min);
1096 : : }
1097 : :
1098 : : /*
1099 : : * random number generator: exponential distribution from min to max inclusive.
1100 : : * the parameter is so that the density of probability for the last cut-off max
1101 : : * value is exp(-parameter).
1102 : : */
1103 : : static int64
1104 : 3 : getExponentialRand(pg_prng_state *state, int64 min, int64 max,
1105 : : double parameter)
1106 : : {
1107 : : double cut,
1108 : : uniform,
1109 : : rand;
1110 : :
1111 : : /* abort if wrong parameter, but must really be checked beforehand */
3651 rhaas@postgresql.org 1112 [ - + ]: 3 : Assert(parameter > 0.0);
1113 : 3 : cut = exp(-parameter);
1114 : : /* pg_prng_double value in [0, 1), uniform in (0, 1] */
1479 tgl@sss.pgh.pa.us 1115 : 3 : uniform = 1.0 - pg_prng_double(state);
1116 : :
1117 : : /*
1118 : : * inner expression in (cut, 1] (if parameter > 0), rand in [0, 1)
1119 : : */
4157 rhaas@postgresql.org 1120 [ - + ]: 3 : Assert((1.0 - cut) != 0.0);
3651 1121 : 3 : rand = -log(cut + (1.0 - cut) * uniform) / parameter;
1122 : : /* return int64 random number within between min and max */
3860 bruce@momjian.us 1123 : 3 : return min + (int64) ((max - min + 1) * rand);
1124 : : }
1125 : :
1126 : : /* random number generator: gaussian distribution from min to max inclusive */
1127 : : static int64
1479 tgl@sss.pgh.pa.us 1128 : 3 : getGaussianRand(pg_prng_state *state, int64 min, int64 max,
1129 : : double parameter)
1130 : : {
1131 : : double stdev;
1132 : : double rand;
1133 : :
1134 : : /* abort if parameter is too low, but must really be checked beforehand */
3550 rhaas@postgresql.org 1135 [ + - ]: 3 : Assert(parameter >= MIN_GAUSSIAN_PARAM);
1136 : :
1137 : : /*
1138 : : * Get normally-distributed random number in the range -parameter <= stdev
1139 : : * < parameter.
1140 : : *
1141 : : * This loop is executed until the number is in the expected range.
1142 : : *
1143 : : * As the minimum parameter is 2.0, the probability of looping is low:
1144 : : * sqrt(-2 ln(r)) <= 2 => r >= e^{-2} ~ 0.135, then when taking the
1145 : : * average sinus multiplier as 2/pi, we have a 8.6% looping probability in
1146 : : * the worst case. For a parameter value of 5.0, the looping probability
1147 : : * is about e^{-5} * 2 / pi ~ 0.43%.
1148 : : */
1149 : : do
1150 : : {
1072 tgl@sss.pgh.pa.us 1151 : 3 : stdev = pg_prng_double_normal(state);
1152 : : }
3651 rhaas@postgresql.org 1153 [ - + - + ]: 3 : while (stdev < -parameter || stdev >= parameter);
1154 : :
1155 : : /* stdev is in [-parameter, parameter), normalization to [0,1) */
1156 : 3 : rand = (stdev + parameter) / (parameter * 2.0);
1157 : :
1158 : : /* return int64 random number within between min and max */
3860 bruce@momjian.us 1159 : 3 : return min + (int64) ((max - min + 1) * rand);
1160 : : }
1161 : :
1162 : : /*
1163 : : * random number generator: generate a value, such that the series of values
1164 : : * will approximate a Poisson distribution centered on the given value.
1165 : : *
1166 : : * Individual results are rounded to integers, though the center value need
1167 : : * not be one.
1168 : : */
1169 : : static int64
1479 tgl@sss.pgh.pa.us 1170 : 210 : getPoissonRand(pg_prng_state *state, double center)
1171 : : {
1172 : : /*
1173 : : * Use inverse transform sampling to generate a value > 0, such that the
1174 : : * expected (i.e. average) value is the given argument.
1175 : : */
1176 : : double uniform;
1177 : :
1178 : : /* pg_prng_double value in [0, 1), uniform in (0, 1] */
1179 : 210 : uniform = 1.0 - pg_prng_double(state);
1180 : :
2639 1181 : 210 : return (int64) (-log(uniform) * center + 0.5);
1182 : : }
1183 : :
1184 : : /*
1185 : : * Computing zipfian using rejection method, based on
1186 : : * "Non-Uniform Random Variate Generation",
1187 : : * Luc Devroye, p. 550-551, Springer 1986.
1188 : : *
1189 : : * This works for s > 1.0, but may perform badly for s very close to 1.0.
1190 : : */
1191 : : static int64
1479 1192 : 3 : computeIterativeZipfian(pg_prng_state *state, int64 n, double s)
1193 : : {
2924 teodor@sigaev.ru 1194 : 3 : double b = pow(2.0, s - 1.0);
1195 : : double x,
1196 : : t,
1197 : : u,
1198 : : v;
1199 : :
1200 : : /* Ensure n is sane */
2451 tgl@sss.pgh.pa.us 1201 [ - + ]: 3 : if (n <= 1)
2451 tgl@sss.pgh.pa.us 1202 :UBC 0 : return 1;
1203 : :
1204 : : while (true)
1205 : : {
1206 : : /* random variates */
1479 tgl@sss.pgh.pa.us 1207 :CBC 3 : u = pg_prng_double(state);
1208 : 3 : v = pg_prng_double(state);
1209 : :
2924 teodor@sigaev.ru 1210 : 3 : x = floor(pow(u, -1.0 / (s - 1.0)));
1211 : :
1212 : 3 : t = pow(1.0 + 1.0 / x, s - 1.0);
1213 : : /* reject if too large or out of bound */
1214 [ + - + - ]: 3 : if (v * x * (t - 1.0) / (b - 1.0) <= t / b && x <= n)
1215 : 3 : break;
1216 : : }
1217 : 3 : return (int64) x;
1218 : : }
1219 : :
1220 : : /* random number generator: zipfian distribution from min to max inclusive */
1221 : : static int64
1479 tgl@sss.pgh.pa.us 1222 : 3 : getZipfianRand(pg_prng_state *state, int64 min, int64 max, double s)
1223 : : {
2924 teodor@sigaev.ru 1224 : 3 : int64 n = max - min + 1;
1225 : :
1226 : : /* abort if parameter is invalid */
2451 tgl@sss.pgh.pa.us 1227 [ + - + - ]: 3 : Assert(MIN_ZIPFIAN_PARAM <= s && s <= MAX_ZIPFIAN_PARAM);
1228 : :
1479 1229 : 3 : return min - 1 + computeIterativeZipfian(state, n, s);
1230 : : }
1231 : :
1232 : : /*
1233 : : * FNV-1a hash function
1234 : : */
1235 : : static int64
2827 teodor@sigaev.ru 1236 : 1 : getHashFnv1a(int64 val, uint64 seed)
1237 : : {
1238 : : int64 result;
1239 : : int i;
1240 : :
1241 : 1 : result = FNV_OFFSET_BASIS ^ seed;
1242 [ + + ]: 9 : for (i = 0; i < 8; ++i)
1243 : : {
2791 tgl@sss.pgh.pa.us 1244 : 8 : int32 octet = val & 0xff;
1245 : :
2827 teodor@sigaev.ru 1246 : 8 : val = val >> 8;
1247 : 8 : result = result ^ octet;
1248 : 8 : result = result * FNV_PRIME;
1249 : : }
1250 : :
1251 : 1 : return result;
1252 : : }
1253 : :
1254 : : /*
1255 : : * Murmur2 hash function
1256 : : *
1257 : : * Based on original work of Austin Appleby
1258 : : * https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp
1259 : : */
1260 : : static int64
1261 : 5 : getHashMurmur2(int64 val, uint64 seed)
1262 : : {
2786 tgl@sss.pgh.pa.us 1263 : 5 : uint64 result = seed ^ MM2_MUL_TIMES_8; /* sizeof(int64) */
2791 1264 : 5 : uint64 k = (uint64) val;
1265 : :
2827 teodor@sigaev.ru 1266 : 5 : k *= MM2_MUL;
1267 : 5 : k ^= k >> MM2_ROT;
1268 : 5 : k *= MM2_MUL;
1269 : :
1270 : 5 : result ^= k;
1271 : 5 : result *= MM2_MUL;
1272 : :
1273 : 5 : result ^= result >> MM2_ROT;
1274 : 5 : result *= MM2_MUL;
1275 : 5 : result ^= result >> MM2_ROT;
1276 : :
1277 : 5 : return (int64) result;
1278 : : }
1279 : :
1280 : : /*
1281 : : * Pseudorandom permutation function
1282 : : *
1283 : : * For small sizes, this generates each of the (size!) possible permutations
1284 : : * of integers in the range [0, size) with roughly equal probability. Once
1285 : : * the size is larger than 20, the number of possible permutations exceeds the
1286 : : * number of distinct states of the internal pseudorandom number generator,
1287 : : * and so not all possible permutations can be generated, but the permutations
1288 : : * chosen should continue to give the appearance of being random.
1289 : : *
1290 : : * THIS FUNCTION IS NOT CRYPTOGRAPHICALLY SECURE.
1291 : : * DO NOT USE FOR SUCH PURPOSE.
1292 : : */
1293 : : static int64
1715 dean.a.rasheed@gmail 1294 : 45 : permute(const int64 val, const int64 isize, const int64 seed)
1295 : : {
1296 : : /* using a high-end PRNG is probably overkill */
1297 : : pg_prng_state state;
1298 : : uint64 size;
1299 : : uint64 v;
1300 : : int masklen;
1301 : : uint64 mask;
1302 : : int i;
1303 : :
1304 [ + + ]: 45 : if (isize < 2)
1305 : 1 : return 0; /* nothing to permute */
1306 : :
1307 : : /* Initialize prng state using the seed */
1479 tgl@sss.pgh.pa.us 1308 : 44 : pg_prng_seed(&state, (uint64) seed);
1309 : :
1310 : : /* Computations are performed on unsigned values */
1715 dean.a.rasheed@gmail 1311 : 44 : size = (uint64) isize;
1312 : 44 : v = (uint64) val % size;
1313 : :
1314 : : /* Mask to work modulo largest power of 2 less than or equal to size */
1315 : 44 : masklen = pg_leftmost_one_pos64(size);
1316 : 44 : mask = (((uint64) 1) << masklen) - 1;
1317 : :
1318 : : /*
1319 : : * Permute the input value by applying several rounds of pseudorandom
1320 : : * bijective transformations. The intention here is to distribute each
1321 : : * input uniformly randomly across the range, and separate adjacent inputs
1322 : : * approximately uniformly randomly from each other, leading to a fairly
1323 : : * random overall choice of permutation.
1324 : : *
1325 : : * To separate adjacent inputs, we multiply by a random number modulo
1326 : : * (mask + 1), which is a power of 2. For this to be a bijection, the
1327 : : * multiplier must be odd. Since this is known to lead to less randomness
1328 : : * in the lower bits, we also apply a rotation that shifts the topmost bit
1329 : : * into the least significant bit. In the special cases where size <= 3,
1330 : : * mask = 1 and each of these operations is actually a no-op, so we also
1331 : : * XOR the value with a different random number to inject additional
1332 : : * randomness. Since the size is generally not a power of 2, we apply
1333 : : * this bijection on overlapping upper and lower halves of the input.
1334 : : *
1335 : : * To distribute the inputs uniformly across the range, we then also apply
1336 : : * a random offset modulo the full range.
1337 : : *
1338 : : * Taken together, these operations resemble a modified linear
1339 : : * congruential generator, as is commonly used in pseudorandom number
1340 : : * generators. The number of rounds is fairly arbitrary, but six has been
1341 : : * found empirically to give a fairly good tradeoff between performance
1342 : : * and uniform randomness. For small sizes it selects each of the (size!)
1343 : : * possible permutations with roughly equal probability. For larger
1344 : : * sizes, not all permutations can be generated, but the intended random
1345 : : * spread is still produced.
1346 : : */
1347 [ + + ]: 308 : for (i = 0; i < 6; i++)
1348 : : {
1349 : : uint64 m,
1350 : : r,
1351 : : t;
1352 : :
1353 : : /* Random multiply (by an odd number), XOR and rotate of lower half */
1479 tgl@sss.pgh.pa.us 1354 : 264 : m = (pg_prng_uint64(&state) & mask) | 1;
1355 : 264 : r = pg_prng_uint64(&state) & mask;
1715 dean.a.rasheed@gmail 1356 [ + + ]: 264 : if (v <= mask)
1357 : : {
1358 : 219 : v = ((v * m) ^ r) & mask;
1359 : 219 : v = ((v << 1) & mask) | (v >> (masklen - 1));
1360 : : }
1361 : :
1362 : : /* Random multiply (by an odd number), XOR and rotate of upper half */
1479 tgl@sss.pgh.pa.us 1363 : 264 : m = (pg_prng_uint64(&state) & mask) | 1;
1364 : 264 : r = pg_prng_uint64(&state) & mask;
1715 dean.a.rasheed@gmail 1365 : 264 : t = size - 1 - v;
1366 [ + + ]: 264 : if (t <= mask)
1367 : : {
1368 : 235 : t = ((t * m) ^ r) & mask;
1369 : 235 : t = ((t << 1) & mask) | (t >> (masklen - 1));
1370 : 235 : v = size - 1 - t;
1371 : : }
1372 : :
1373 : : /* Random offset */
1479 tgl@sss.pgh.pa.us 1374 : 264 : r = pg_prng_uint64_range(&state, 0, size - 1);
1715 dean.a.rasheed@gmail 1375 : 264 : v = (v + r) % size;
1376 : : }
1377 : :
1378 : 44 : return (int64) v;
1379 : : }
1380 : :
1381 : : /*
1382 : : * Initialize the given SimpleStats struct to all zeroes
1383 : : */
1384 : : static void
3609 alvherre@alvh.no-ip. 1385 : 2057 : initSimpleStats(SimpleStats *ss)
1386 : : {
1387 : 2057 : memset(ss, 0, sizeof(SimpleStats));
1388 : 2057 : }
1389 : :
1390 : : /*
1391 : : * Accumulate one value into a SimpleStats struct.
1392 : : */
1393 : : static void
1394 : 9649 : addToSimpleStats(SimpleStats *ss, double val)
1395 : : {
1396 [ + + + + ]: 9649 : if (ss->count == 0 || val < ss->min)
1397 : 157 : ss->min = val;
1398 [ + + + + ]: 9649 : if (ss->count == 0 || val > ss->max)
1399 : 474 : ss->max = val;
1400 : 9649 : ss->count++;
1401 : 9649 : ss->sum += val;
1402 : 9649 : ss->sum2 += val * val;
1403 : 9649 : }
1404 : :
1405 : : /*
1406 : : * Merge two SimpleStats objects
1407 : : */
1408 : : static void
1409 : 174 : mergeSimpleStats(SimpleStats *acc, SimpleStats *ss)
1410 : : {
1411 [ - + - - ]: 174 : if (acc->count == 0 || ss->min < acc->min)
1412 : 174 : acc->min = ss->min;
1413 [ - + - - ]: 174 : if (acc->count == 0 || ss->max > acc->max)
1414 : 174 : acc->max = ss->max;
1415 : 174 : acc->count += ss->count;
1416 : 174 : acc->sum += ss->sum;
1417 : 174 : acc->sum2 += ss->sum2;
1418 : 174 : }
1419 : :
1420 : : /*
1421 : : * Initialize a StatsData struct to mostly zeroes, with its start time set to
1422 : : * the given value.
1423 : : */
1424 : : static void
1742 tmunro@postgresql.or 1425 : 550 : initStats(StatsData *sd, pg_time_usec_t start)
1426 : : {
1427 : 550 : sd->start_time = start;
3609 alvherre@alvh.no-ip. 1428 : 550 : sd->cnt = 0;
1429 : 550 : sd->skipped = 0;
1364 ishii@postgresql.org 1430 : 550 : sd->retries = 0;
1431 : 550 : sd->retried = 0;
1432 : 550 : sd->serialization_failures = 0;
1433 : 550 : sd->deadlock_failures = 0;
39 fujii@postgresql.org 1434 :GNC 550 : sd->other_sql_failures = 0;
3609 alvherre@alvh.no-ip. 1435 :CBC 550 : initSimpleStats(&sd->latency);
1436 : 550 : initSimpleStats(&sd->lag);
1437 : 550 : }
1438 : :
1439 : : /*
1440 : : * Accumulate one additional item into the given stats object.
1441 : : */
1442 : : static void
1364 ishii@postgresql.org 1443 : 9065 : accumStats(StatsData *stats, bool skipped, double lat, double lag,
1444 : : EStatus estatus, int64 tries)
1445 : : {
1446 : : /* Record the skipped transaction */
3609 alvherre@alvh.no-ip. 1447 [ + + ]: 9065 : if (skipped)
1448 : : {
1449 : : /* no latency to record on skipped transactions */
1450 : 9 : stats->skipped++;
1364 ishii@postgresql.org 1451 : 9 : return;
1452 : : }
1453 : :
1454 : : /*
1455 : : * Record the number of retries regardless of whether the transaction was
1456 : : * successful or failed.
1457 : : */
1458 [ + + ]: 9056 : if (tries > 1)
1459 : : {
1460 : 2 : stats->retries += (tries - 1);
1461 : 2 : stats->retried++;
1462 : : }
1463 : :
1464 [ + - - + : 9056 : switch (estatus)
- ]
1465 : : {
1466 : : /* Record the successful transaction */
1467 : 9047 : case ESTATUS_NO_ERROR:
1468 : 9047 : stats->cnt++;
1469 : :
1470 : 9047 : addToSimpleStats(&stats->latency, lat);
1471 : :
1472 : : /* and possibly the same for schedule lag */
1473 [ + + ]: 9047 : if (throttle_delay)
1474 : 201 : addToSimpleStats(&stats->lag, lag);
1475 : 9047 : break;
1476 : :
1477 : : /* Record the failed transaction */
1364 ishii@postgresql.org 1478 :UBC 0 : case ESTATUS_SERIALIZATION_ERROR:
1479 : 0 : stats->serialization_failures++;
1480 : 0 : break;
1481 : 0 : case ESTATUS_DEADLOCK_ERROR:
1482 : 0 : stats->deadlock_failures++;
1483 : 0 : break;
39 fujii@postgresql.org 1484 :GNC 9 : case ESTATUS_OTHER_SQL_ERROR:
1485 : 9 : stats->other_sql_failures++;
1486 : 9 : break;
1364 ishii@postgresql.org 1487 :UBC 0 : default:
1488 : : /* internal error which should never occur */
1348 tgl@sss.pgh.pa.us 1489 : 0 : pg_fatal("unexpected error status: %d", estatus);
1490 : : }
1491 : : }
1492 : :
1493 : : /* call PQexec() and exit() on failure */
1494 : : static void
6606 bruce@momjian.us 1495 :CBC 59 : executeStatement(PGconn *con, const char *sql)
1496 : : {
1497 : : PGresult *res;
1498 : :
6829 ishii@postgresql.org 1499 : 59 : res = PQexec(con, sql);
1500 [ - + ]: 59 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
1501 : : {
1348 tgl@sss.pgh.pa.us 1502 :UBC 0 : pg_log_error("query failed: %s", PQerrorMessage(con));
1503 : 0 : pg_log_error_detail("Query was: %s", sql);
6829 ishii@postgresql.org 1504 : 0 : exit(1);
1505 : : }
6829 ishii@postgresql.org 1506 :CBC 59 : PQclear(res);
1507 : 59 : }
1508 : :
1509 : : /* call PQexec() and complain, but without exiting, on failure */
1510 : : static void
3871 sfrost@snowman.net 1511 : 33 : tryExecuteStatement(PGconn *con, const char *sql)
1512 : : {
1513 : : PGresult *res;
1514 : :
1515 : 33 : res = PQexec(con, sql);
1516 [ - + ]: 33 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
1517 : : {
2169 peter@eisentraut.org 1518 :UBC 0 : pg_log_error("%s", PQerrorMessage(con));
1348 tgl@sss.pgh.pa.us 1519 : 0 : pg_log_error_detail("(ignoring this error and continuing anyway)");
1520 : : }
3871 sfrost@snowman.net 1521 :CBC 33 : PQclear(res);
1522 : 33 : }
1523 : :
1524 : : /* set up a connection to the backend */
1525 : : static PGconn *
7722 tgl@sss.pgh.pa.us 1526 : 324 : doConnect(void)
1527 : : {
1528 : : PGconn *conn;
1529 : : bool new_pass;
1530 : : static char *password = NULL;
1531 : :
1532 : : /*
1533 : : * Start the connection. Loop until we have a password if requested by
1534 : : * backend.
1535 : : */
1536 : : do
1537 : : {
1538 : : #define PARAMS_ARRAY_SIZE 7
1539 : :
1540 : : const char *keywords[PARAMS_ARRAY_SIZE];
1541 : : const char *values[PARAMS_ARRAY_SIZE];
1542 : :
4913 rhaas@postgresql.org 1543 : 324 : keywords[0] = "host";
1544 : 324 : values[0] = pghost;
1545 : 324 : keywords[1] = "port";
1546 : 324 : values[1] = pgport;
1547 : 324 : keywords[2] = "user";
1746 michael@paquier.xyz 1548 : 324 : values[2] = username;
4913 rhaas@postgresql.org 1549 : 324 : keywords[3] = "password";
1930 tgl@sss.pgh.pa.us 1550 : 324 : values[3] = password;
4913 rhaas@postgresql.org 1551 : 324 : keywords[4] = "dbname";
1552 : 324 : values[4] = dbName;
1553 : 324 : keywords[5] = "fallback_application_name";
1554 : 324 : values[5] = progname;
1555 : 324 : keywords[6] = NULL;
1556 : 324 : values[6] = NULL;
1557 : :
6580 tgl@sss.pgh.pa.us 1558 : 324 : new_pass = false;
1559 : :
4913 rhaas@postgresql.org 1560 : 324 : conn = PQconnectdbParams(keywords, values, true);
1561 : :
6580 tgl@sss.pgh.pa.us 1562 [ - + ]: 324 : if (!conn)
1563 : : {
2169 peter@eisentraut.org 1564 :UBC 0 : pg_log_error("connection to database \"%s\" failed", dbName);
6580 tgl@sss.pgh.pa.us 1565 : 0 : return NULL;
1566 : : }
1567 : :
6580 tgl@sss.pgh.pa.us 1568 [ + + - + ]:CBC 325 : if (PQstatus(conn) == CONNECTION_BAD &&
1569 : 1 : PQconnectionNeedsPassword(conn) &&
1930 tgl@sss.pgh.pa.us 1570 [ # # ]:UBC 0 : !password)
1571 : : {
6580 1572 : 0 : PQfinish(conn);
1930 1573 : 0 : password = simple_prompt("Password: ", false);
6580 1574 : 0 : new_pass = true;
1575 : : }
6580 tgl@sss.pgh.pa.us 1576 [ - + ]:CBC 324 : } while (new_pass);
1577 : :
1578 : : /* check to see that the backend connection was successfully made */
1579 [ + + ]: 324 : if (PQstatus(conn) == CONNECTION_BAD)
1580 : : {
1789 1581 : 1 : pg_log_error("%s", PQerrorMessage(conn));
6580 1582 : 1 : PQfinish(conn);
1583 : 1 : return NULL;
1584 : : }
1585 : :
1586 : 323 : return conn;
1587 : : }
1588 : :
1589 : : /* qsort comparator for Variable array */
1590 : : static int
3511 1591 : 53873 : compareVariableNames(const void *v1, const void *v2)
1592 : : {
7375 1593 : 107746 : return strcmp(((const Variable *) v1)->name,
1594 : 53873 : ((const Variable *) v2)->name);
1595 : : }
1596 : :
1597 : : /* Locate a variable by name; returns NULL if unknown */
1598 : : static Variable *
1364 ishii@postgresql.org 1599 : 8012 : lookupVariable(Variables *variables, char *name)
1600 : : {
1601 : : Variable key;
1602 : :
1603 : : /* On some versions of Solaris, bsearch of zero items dumps core */
1604 [ + + ]: 8012 : if (variables->nvars <= 0)
7375 tgl@sss.pgh.pa.us 1605 : 203 : return NULL;
1606 : :
1607 : : /* Sort if we have to */
1364 ishii@postgresql.org 1608 [ + + ]: 7809 : if (!variables->vars_sorted)
1609 : : {
1043 peter@eisentraut.org 1610 : 1022 : qsort(variables->vars, variables->nvars, sizeof(Variable),
1611 : : compareVariableNames);
1364 ishii@postgresql.org 1612 : 1022 : variables->vars_sorted = true;
1613 : : }
1614 : :
1615 : : /* Now we can search */
7367 tgl@sss.pgh.pa.us 1616 : 7809 : key.name = name;
1043 peter@eisentraut.org 1617 : 7809 : return (Variable *) bsearch(&key,
1618 : 7809 : variables->vars,
1364 ishii@postgresql.org 1619 : 7809 : variables->nvars,
1620 : : sizeof(Variable),
1621 : : compareVariableNames);
1622 : : }
1623 : :
1624 : : /* Get the value of a variable, in string form; returns NULL if unknown */
1625 : : static char *
1626 : 2458 : getVariable(Variables *variables, char *name)
1627 : : {
1628 : : Variable *var;
1629 : : char stringform[64];
1630 : :
1631 : 2458 : var = lookupVariable(variables, name);
3511 tgl@sss.pgh.pa.us 1632 [ + + ]: 2458 : if (var == NULL)
1633 : 4 : return NULL; /* not found */
1634 : :
2898 teodor@sigaev.ru 1635 [ + + ]: 2454 : if (var->svalue)
1636 : 848 : return var->svalue; /* we have it in string form */
1637 : :
1638 : : /* We need to produce a string equivalent of the value */
1639 [ - + ]: 1606 : Assert(var->value.type != PGBT_NO_VALUE);
1640 [ + + ]: 1606 : if (var->value.type == PGBT_NULL)
1641 : 1 : snprintf(stringform, sizeof(stringform), "NULL");
1642 [ + + ]: 1605 : else if (var->value.type == PGBT_BOOLEAN)
3511 tgl@sss.pgh.pa.us 1643 : 1 : snprintf(stringform, sizeof(stringform),
2898 teodor@sigaev.ru 1644 [ + - ]: 1 : "%s", var->value.u.bval ? "true" : "false");
1645 [ + + ]: 1604 : else if (var->value.type == PGBT_INT)
3511 tgl@sss.pgh.pa.us 1646 : 1602 : snprintf(stringform, sizeof(stringform),
1647 : : INT64_FORMAT, var->value.u.ival);
2898 teodor@sigaev.ru 1648 [ + - ]: 2 : else if (var->value.type == PGBT_DOUBLE)
1649 : 2 : snprintf(stringform, sizeof(stringform),
1650 : : "%.*g", DBL_DIG, var->value.u.dval);
1651 : : else /* internal error, unexpected type */
2898 teodor@sigaev.ru 1652 :UBC 0 : Assert(0);
2898 teodor@sigaev.ru 1653 :CBC 1606 : var->svalue = pg_strdup(stringform);
1654 : 1606 : return var->svalue;
1655 : : }
1656 : :
1657 : : /* Try to convert variable to a value; return false on failure */
1658 : : static bool
1659 : 2024 : makeVariableValue(Variable *var)
1660 : : {
1661 : : size_t slen;
1662 : :
1663 [ + + ]: 2024 : if (var->value.type != PGBT_NO_VALUE)
3511 tgl@sss.pgh.pa.us 1664 : 1488 : return true; /* no work */
1665 : :
2898 teodor@sigaev.ru 1666 : 536 : slen = strlen(var->svalue);
1667 : :
1668 [ - + ]: 536 : if (slen == 0)
1669 : : /* what should it do on ""? */
2898 teodor@sigaev.ru 1670 :UBC 0 : return false;
1671 : :
2898 teodor@sigaev.ru 1672 [ + + ]:CBC 536 : if (pg_strcasecmp(var->svalue, "null") == 0)
1673 : : {
1674 : 1 : setNullValue(&var->value);
1675 : : }
1676 : :
1677 : : /*
1678 : : * accept prefixes such as y, ye, n, no... but not for "o". 0/1 are
1679 : : * recognized later as an int, which is converted to bool if needed.
1680 : : */
1681 [ + + + - ]: 1069 : else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
1682 [ - + ]: 1068 : pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
1683 : 534 : pg_strcasecmp(var->svalue, "on") == 0)
1684 : : {
1685 : 1 : setBoolValue(&var->value, true);
1686 : : }
1687 [ + - + - ]: 1068 : else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
1688 [ + - ]: 1068 : pg_strncasecmp(var->svalue, "no", slen) == 0 ||
1689 [ + + ]: 1068 : pg_strcasecmp(var->svalue, "off") == 0 ||
1690 : 534 : pg_strcasecmp(var->svalue, "of") == 0)
1691 : : {
1692 : 1 : setBoolValue(&var->value, false);
1693 : : }
1694 [ + + ]: 533 : else if (is_an_int(var->svalue))
1695 : : {
1696 : : /* if it looks like an int, it must be an int without overflow */
1697 : : int64 iv;
1698 : :
2637 andres@anarazel.de 1699 [ - + ]: 530 : if (!strtoint64(var->svalue, false, &iv))
2637 andres@anarazel.de 1700 :UBC 0 : return false;
1701 : :
2637 andres@anarazel.de 1702 :CBC 530 : setIntValue(&var->value, iv);
1703 : : }
1704 : : else /* type should be double */
1705 : : {
1706 : : double dv;
1707 : :
1708 [ + + ]: 3 : if (!strtodouble(var->svalue, true, &dv))
1709 : : {
2169 peter@eisentraut.org 1710 : 2 : pg_log_error("malformed variable \"%s\" value: \"%s\"",
1711 : : var->name, var->svalue);
3511 tgl@sss.pgh.pa.us 1712 : 2 : return false;
1713 : : }
2898 teodor@sigaev.ru 1714 : 1 : setDoubleValue(&var->value, dv);
1715 : : }
3511 tgl@sss.pgh.pa.us 1716 : 534 : return true;
1717 : : }
1718 : :
1719 : : /*
1720 : : * Check whether a variable's name is allowed.
1721 : : *
1722 : : * We allow any non-ASCII character, as well as ASCII letters, digits, and
1723 : : * underscore.
1724 : : *
1725 : : * Keep this in sync with the definitions of variable name characters in
1726 : : * "src/fe_utils/psqlscan.l", "src/bin/psql/psqlscanslash.l" and
1727 : : * "src/bin/pgbench/exprscan.l". Also see parseVariable(), below.
1728 : : *
1729 : : * Note: this static function is copied from "src/bin/psql/variables.c"
1730 : : * but changed to disallow variable names starting with a digit.
1731 : : */
1732 : : static bool
3025 1733 : 1114 : valid_variable_name(const char *name)
1734 : : {
1735 : 1114 : const unsigned char *ptr = (const unsigned char *) name;
1736 : :
1737 : : /* Mustn't be zero-length */
1738 [ - + ]: 1114 : if (*ptr == '\0')
3025 tgl@sss.pgh.pa.us 1739 :UBC 0 : return false;
1740 : :
1741 : : /* must not start with [0-9] */
1798 tgl@sss.pgh.pa.us 1742 [ + - ]:CBC 1114 : if (IS_HIGHBIT_SET(*ptr) ||
1743 : 1114 : strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
1744 [ + + ]: 1114 : "_", *ptr) != NULL)
1745 : 1112 : ptr++;
1746 : : else
1747 : 2 : return false;
1748 : :
1749 : : /* remaining characters can include [0-9] */
3025 1750 [ + + ]: 7293 : while (*ptr)
1751 : : {
1752 [ + - ]: 6182 : if (IS_HIGHBIT_SET(*ptr) ||
1753 : 6182 : strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
1754 [ + + ]: 6182 : "_0123456789", *ptr) != NULL)
1755 : 6181 : ptr++;
1756 : : else
5823 itagaki.takahiro@gma 1757 : 1 : return false;
1758 : : }
1759 : :
3025 tgl@sss.pgh.pa.us 1760 : 1111 : return true;
1761 : : }
1762 : :
1763 : : /*
1764 : : * Make sure there is enough space for 'needed' more variable in the variables
1765 : : * array.
1766 : : */
1767 : : static void
1364 ishii@postgresql.org 1768 : 1111 : enlargeVariables(Variables *variables, int needed)
1769 : : {
1770 : : /* total number of variables required now */
1771 : 1111 : needed += variables->nvars;
1772 : :
1773 [ + + ]: 1111 : if (variables->max_vars < needed)
1774 : : {
1775 : 190 : variables->max_vars = needed + VARIABLES_ALLOC_MARGIN;
1776 : 190 : variables->vars = (Variable *)
1777 : 190 : pg_realloc(variables->vars, variables->max_vars * sizeof(Variable));
1778 : : }
1779 : 1111 : }
1780 : :
1781 : : /*
1782 : : * Lookup a variable by name, creating it if need be.
1783 : : * Caller is expected to assign a value to the variable.
1784 : : * Returns NULL on failure (bad name).
1785 : : */
1786 : : static Variable *
1787 : 3176 : lookupCreateVariable(Variables *variables, const char *context, char *name)
1788 : : {
1789 : : Variable *var;
1790 : :
1791 : 3176 : var = lookupVariable(variables, name);
7383 1792 [ + + ]: 3176 : if (var == NULL)
1793 : : {
1794 : : /*
1795 : : * Check for the name only when declaring a new variable to avoid
1796 : : * overhead.
1797 : : */
3025 tgl@sss.pgh.pa.us 1798 [ + + ]: 1114 : if (!valid_variable_name(name))
1799 : : {
2169 peter@eisentraut.org 1800 : 3 : pg_log_error("%s: invalid variable name: \"%s\"", context, name);
3511 tgl@sss.pgh.pa.us 1801 : 3 : return NULL;
1802 : : }
1803 : :
1804 : : /* Create variable at the end of the array */
1364 ishii@postgresql.org 1805 : 1111 : enlargeVariables(variables, 1);
1806 : :
1807 : 1111 : var = &(variables->vars[variables->nvars]);
1808 : :
4823 tgl@sss.pgh.pa.us 1809 : 1111 : var->name = pg_strdup(name);
2898 teodor@sigaev.ru 1810 : 1111 : var->svalue = NULL;
1811 : : /* caller is expected to initialize remaining fields */
1812 : :
1364 ishii@postgresql.org 1813 : 1111 : variables->nvars++;
1814 : : /* we don't re-sort the array till we have to */
1815 : 1111 : variables->vars_sorted = false;
1816 : : }
1817 : :
3511 tgl@sss.pgh.pa.us 1818 : 3173 : return var;
1819 : : }
1820 : :
1821 : : /* Assign a string value to a variable, creating it if need be */
1822 : : /* Returns false on failure (bad name) */
1823 : : static bool
1364 ishii@postgresql.org 1824 : 974 : putVariable(Variables *variables, const char *context, char *name,
1825 : : const char *value)
1826 : : {
1827 : : Variable *var;
1828 : : char *val;
1829 : :
1830 : 974 : var = lookupCreateVariable(variables, context, name);
3511 tgl@sss.pgh.pa.us 1831 [ + + ]: 974 : if (!var)
1832 : 2 : return false;
1833 : :
1834 : : /* dup then free, in case value is pointing at this variable */
1835 : 972 : val = pg_strdup(value);
1836 : :
1279 peter@eisentraut.org 1837 : 972 : free(var->svalue);
2898 teodor@sigaev.ru 1838 : 972 : var->svalue = val;
1839 : 972 : var->value.type = PGBT_NO_VALUE;
1840 : :
3511 tgl@sss.pgh.pa.us 1841 : 972 : return true;
1842 : : }
1843 : :
1844 : : /* Assign a value to a variable, creating it if need be */
1845 : : /* Returns false on failure (bad name) */
1846 : : static bool
1364 ishii@postgresql.org 1847 : 2202 : putVariableValue(Variables *variables, const char *context, char *name,
1848 : : const PgBenchValue *value)
1849 : : {
1850 : : Variable *var;
1851 : :
1852 : 2202 : var = lookupCreateVariable(variables, context, name);
3511 tgl@sss.pgh.pa.us 1853 [ + + ]: 2202 : if (!var)
1854 : 1 : return false;
1855 : :
1279 peter@eisentraut.org 1856 : 2201 : free(var->svalue);
2898 teodor@sigaev.ru 1857 : 2201 : var->svalue = NULL;
1858 : 2201 : var->value = *value;
1859 : :
7383 ishii@postgresql.org 1860 : 2201 : return true;
1861 : : }
1862 : :
1863 : : /* Assign an integer value to a variable, creating it if need be */
1864 : : /* Returns false on failure (bad name) */
1865 : : static bool
1364 1866 : 513 : putVariableInt(Variables *variables, const char *context, char *name,
1867 : : int64 value)
1868 : : {
1869 : : PgBenchValue val;
1870 : :
3511 tgl@sss.pgh.pa.us 1871 : 513 : setIntValue(&val, value);
1364 ishii@postgresql.org 1872 : 513 : return putVariableValue(variables, context, name, &val);
1873 : : }
1874 : :
1875 : : /*
1876 : : * Parse a possible variable reference (:varname).
1877 : : *
1878 : : * "sql" points at a colon. If what follows it looks like a valid
1879 : : * variable name, return a malloc'd string containing the variable name,
1880 : : * and set *eaten to the number of characters consumed (including the colon).
1881 : : * Otherwise, return NULL.
1882 : : */
1883 : : static char *
6481 1884 : 2402 : parseVariable(const char *sql, int *eaten)
1885 : : {
1798 tgl@sss.pgh.pa.us 1886 : 2402 : int i = 1; /* starting at 1 skips the colon */
1887 : : char *name;
1888 : :
1889 : : /* keep this logic in sync with valid_variable_name() */
1890 [ + - ]: 2402 : if (IS_HIGHBIT_SET(sql[i]) ||
1891 : 2402 : strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
1892 [ + + ]: 2402 : "_", sql[i]) != NULL)
1893 : 1056 : i++;
1894 : : else
1895 : 1346 : return NULL;
1896 : :
1897 [ - + ]: 4753 : while (IS_HIGHBIT_SET(sql[i]) ||
1898 : 4753 : strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
1899 [ + + ]: 4753 : "_0123456789", sql[i]) != NULL)
6481 ishii@postgresql.org 1900 : 3697 : i++;
1901 : :
4823 tgl@sss.pgh.pa.us 1902 : 1056 : name = pg_malloc(i);
6481 ishii@postgresql.org 1903 : 1056 : memcpy(name, &sql[1], i - 1);
1904 : 1056 : name[i - 1] = '\0';
1905 : :
1906 : 1056 : *eaten = i;
1907 : 1056 : return name;
1908 : : }
1909 : :
1910 : : static char *
1911 : 1055 : replaceVariable(char **sql, char *param, int len, char *value)
1912 : : {
6032 bruce@momjian.us 1913 : 1055 : int valueln = strlen(value);
1914 : :
6481 ishii@postgresql.org 1915 [ + + ]: 1055 : if (valueln > len)
1916 : : {
6032 bruce@momjian.us 1917 : 581 : size_t offset = param - *sql;
1918 : :
4823 tgl@sss.pgh.pa.us 1919 : 581 : *sql = pg_realloc(*sql, strlen(*sql) - len + valueln + 1);
6481 ishii@postgresql.org 1920 : 581 : param = *sql + offset;
1921 : : }
1922 : :
1923 [ + + ]: 1055 : if (valueln != len)
1924 : 1020 : memmove(param + valueln, param + len, strlen(param + len) + 1);
3979 tgl@sss.pgh.pa.us 1925 : 1055 : memcpy(param, value, valueln);
1926 : :
6481 ishii@postgresql.org 1927 : 1055 : return param + valueln;
1928 : : }
1929 : :
1930 : : static char *
1364 1931 : 8022 : assignVariables(Variables *variables, char *sql)
1932 : : {
1933 : : char *p,
1934 : : *name,
1935 : : *val;
1936 : :
6481 1937 : 8022 : p = sql;
1938 [ + + ]: 10127 : while ((p = strchr(p, ':')) != NULL)
1939 : : {
1940 : : int eaten;
1941 : :
1942 : 2105 : name = parseVariable(p, &eaten);
1943 [ + + ]: 2105 : if (name == NULL)
1944 : : {
6032 bruce@momjian.us 1945 [ + + ]: 3191 : while (*p == ':')
1946 : : {
1947 : 1861 : p++;
1948 : : }
7383 ishii@postgresql.org 1949 : 1330 : continue;
1950 : : }
1951 : :
1364 1952 : 775 : val = getVariable(variables, name);
7383 1953 : 775 : free(name);
1954 [ - + ]: 775 : if (val == NULL)
1955 : : {
6481 ishii@postgresql.org 1956 :UBC 0 : p++;
1957 : 0 : continue;
1958 : : }
1959 : :
5605 tgl@sss.pgh.pa.us 1960 :CBC 775 : p = replaceVariable(&sql, p, eaten, val);
1961 : : }
1962 : :
6481 ishii@postgresql.org 1963 : 8022 : return sql;
1964 : : }
1965 : :
1966 : : static void
1364 1967 : 2549 : getQueryParams(Variables *variables, const Command *command,
1968 : : const char **params)
1969 : : {
1970 : : int i;
1971 : :
6481 1972 [ + + ]: 4227 : for (i = 0; i < command->argc - 1; i++)
1364 1973 : 1678 : params[i] = getVariable(variables, command->argv[i + 1]);
6481 1974 : 2549 : }
1975 : :
1976 : : static char *
2898 teodor@sigaev.ru 1977 : 4 : valueTypeName(PgBenchValue *pval)
1978 : : {
1979 [ - + ]: 4 : if (pval->type == PGBT_NO_VALUE)
2898 teodor@sigaev.ru 1980 :UBC 0 : return "none";
2898 teodor@sigaev.ru 1981 [ - + ]:CBC 4 : else if (pval->type == PGBT_NULL)
2898 teodor@sigaev.ru 1982 :UBC 0 : return "null";
2898 teodor@sigaev.ru 1983 [ - + ]:CBC 4 : else if (pval->type == PGBT_INT)
2898 teodor@sigaev.ru 1984 :UBC 0 : return "int";
2898 teodor@sigaev.ru 1985 [ + + ]:CBC 4 : else if (pval->type == PGBT_DOUBLE)
1986 : 1 : return "double";
1987 [ + - ]: 3 : else if (pval->type == PGBT_BOOLEAN)
1988 : 3 : return "boolean";
1989 : : else
1990 : : {
1991 : : /* internal error, should never get there */
2898 teodor@sigaev.ru 1992 :UBC 0 : Assert(false);
1993 : : return NULL;
1994 : : }
1995 : : }
1996 : :
1997 : : /* get a value as a boolean, or tell if there is a problem */
1998 : : static bool
2898 teodor@sigaev.ru 1999 :CBC 108 : coerceToBool(PgBenchValue *pval, bool *bval)
2000 : : {
2001 [ + + ]: 108 : if (pval->type == PGBT_BOOLEAN)
2002 : : {
2003 : 107 : *bval = pval->u.bval;
2004 : 107 : return true;
2005 : : }
2006 : : else /* NULL, INT or DOUBLE */
2007 : : {
2169 peter@eisentraut.org 2008 : 1 : pg_log_error("cannot coerce %s to boolean", valueTypeName(pval));
2862 tgl@sss.pgh.pa.us 2009 : 1 : *bval = false; /* suppress uninitialized-variable warnings */
2898 teodor@sigaev.ru 2010 : 1 : return false;
2011 : : }
2012 : : }
2013 : :
2014 : : /*
2015 : : * Return true or false from an expression for conditional purposes.
2016 : : * Non zero numerical values are true, zero and NULL are false.
2017 : : */
2018 : : static bool
2019 : 548 : valueTruth(PgBenchValue *pval)
2020 : : {
2021 [ + + + + : 548 : switch (pval->type)
- ]
2022 : : {
2023 : 1 : case PGBT_NULL:
2024 : 1 : return false;
2025 : 36 : case PGBT_BOOLEAN:
2026 : 36 : return pval->u.bval;
2027 : 510 : case PGBT_INT:
2028 : 510 : return pval->u.ival != 0;
2029 : 1 : case PGBT_DOUBLE:
2030 : 1 : return pval->u.dval != 0.0;
2898 teodor@sigaev.ru 2031 :UBC 0 : default:
2032 : : /* internal error, unexpected type */
2033 : 0 : Assert(0);
2034 : : return false;
2035 : : }
2036 : : }
2037 : :
2038 : : /* get a value as an int, tell if there is a problem */
2039 : : static bool
3550 rhaas@postgresql.org 2040 :CBC 6582 : coerceToInt(PgBenchValue *pval, int64 *ival)
2041 : : {
2042 [ + + ]: 6582 : if (pval->type == PGBT_INT)
2043 : : {
2044 : 6578 : *ival = pval->u.ival;
2045 : 6578 : return true;
2046 : : }
2898 teodor@sigaev.ru 2047 [ + + ]: 4 : else if (pval->type == PGBT_DOUBLE)
2048 : : {
2231 tgl@sss.pgh.pa.us 2049 : 2 : double dval = rint(pval->u.dval);
2050 : :
2051 [ + - + - : 2 : if (isnan(dval) || !FLOAT8_FITS_IN_INT64(dval))
+ + ]
2052 : : {
2169 peter@eisentraut.org 2053 : 1 : pg_log_error("double to int overflow for %f", dval);
3550 rhaas@postgresql.org 2054 : 1 : return false;
2055 : : }
2056 : 1 : *ival = (int64) dval;
2057 : 1 : return true;
2058 : : }
2059 : : else /* BOOLEAN or NULL */
2060 : : {
2169 peter@eisentraut.org 2061 : 2 : pg_log_error("cannot coerce %s to int", valueTypeName(pval));
2898 teodor@sigaev.ru 2062 : 2 : return false;
2063 : : }
2064 : : }
2065 : :
2066 : : /* get a value as a double, or tell if there is a problem */
2067 : : static bool
3550 rhaas@postgresql.org 2068 : 104 : coerceToDouble(PgBenchValue *pval, double *dval)
2069 : : {
2070 [ + + ]: 104 : if (pval->type == PGBT_DOUBLE)
2071 : : {
2072 : 73 : *dval = pval->u.dval;
2073 : 73 : return true;
2074 : : }
2898 teodor@sigaev.ru 2075 [ + + ]: 31 : else if (pval->type == PGBT_INT)
2076 : : {
3550 rhaas@postgresql.org 2077 : 30 : *dval = (double) pval->u.ival;
2078 : 30 : return true;
2079 : : }
2080 : : else /* BOOLEAN or NULL */
2081 : : {
2169 peter@eisentraut.org 2082 : 1 : pg_log_error("cannot coerce %s to double", valueTypeName(pval));
2898 teodor@sigaev.ru 2083 : 1 : return false;
2084 : : }
2085 : : }
2086 : :
2087 : : /* assign a null value */
2088 : : static void
2089 : 4 : setNullValue(PgBenchValue *pv)
2090 : : {
2091 : 4 : pv->type = PGBT_NULL;
2092 : 4 : pv->u.ival = 0;
2093 : 4 : }
2094 : :
2095 : : /* assign a boolean value */
2096 : : static void
2097 : 144 : setBoolValue(PgBenchValue *pv, bool bval)
2098 : : {
2099 : 144 : pv->type = PGBT_BOOLEAN;
2100 : 144 : pv->u.bval = bval;
3550 rhaas@postgresql.org 2101 : 144 : }
2102 : :
2103 : : /* assign an integer value */
2104 : : static void
2105 : 4237 : setIntValue(PgBenchValue *pv, int64 ival)
2106 : : {
2107 : 4237 : pv->type = PGBT_INT;
2108 : 4237 : pv->u.ival = ival;
2109 : 4237 : }
2110 : :
2111 : : /* assign a double value */
2112 : : static void
2113 : 39 : setDoubleValue(PgBenchValue *pv, double dval)
2114 : : {
2115 : 39 : pv->type = PGBT_DOUBLE;
2116 : 39 : pv->u.dval = dval;
2117 : 39 : }
2118 : :
2119 : : static bool
2791 tgl@sss.pgh.pa.us 2120 : 3562 : isLazyFunc(PgBenchFunction func)
2121 : : {
2898 teodor@sigaev.ru 2122 [ + + + + : 3562 : return func == PGBENCH_AND || func == PGBENCH_OR || func == PGBENCH_CASE;
+ + ]
2123 : : }
2124 : :
2125 : : /* lazy evaluation of some functions */
2126 : : static bool
2449 tgl@sss.pgh.pa.us 2127 : 65 : evalLazyFunc(CState *st,
2128 : : PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
2129 : : {
2130 : : PgBenchValue a1,
2131 : : a2;
2132 : : bool ba1,
2133 : : ba2;
2134 : :
2898 teodor@sigaev.ru 2135 [ + - + - : 65 : Assert(isLazyFunc(func) && args != NULL && args->next != NULL);
+ - ]
2136 : :
2137 : : /* args points to first condition */
2449 tgl@sss.pgh.pa.us 2138 [ + + ]: 65 : if (!evaluateExpr(st, args->expr, &a1))
2898 teodor@sigaev.ru 2139 : 1 : return false;
2140 : :
2141 : : /* second condition for AND/OR and corresponding branch for CASE */
2142 : 64 : args = args->next;
2143 : :
2144 [ + + + - ]: 64 : switch (func)
2145 : : {
2791 tgl@sss.pgh.pa.us 2146 : 44 : case PGBENCH_AND:
2147 [ - + ]: 44 : if (a1.type == PGBT_NULL)
2148 : : {
2791 tgl@sss.pgh.pa.us 2149 :UBC 0 : setNullValue(retval);
2150 : 0 : return true;
2151 : : }
2152 : :
2791 tgl@sss.pgh.pa.us 2153 [ - + ]:CBC 44 : if (!coerceToBool(&a1, &ba1))
2791 tgl@sss.pgh.pa.us 2154 :UBC 0 : return false;
2155 : :
2791 tgl@sss.pgh.pa.us 2156 [ + + ]:CBC 44 : if (!ba1)
2157 : : {
2158 : 3 : setBoolValue(retval, false);
2159 : 3 : return true;
2160 : : }
2161 : :
2449 2162 [ - + ]: 41 : if (!evaluateExpr(st, args->expr, &a2))
2791 tgl@sss.pgh.pa.us 2163 :UBC 0 : return false;
2164 : :
2791 tgl@sss.pgh.pa.us 2165 [ - + ]:CBC 41 : if (a2.type == PGBT_NULL)
2166 : : {
2791 tgl@sss.pgh.pa.us 2167 :UBC 0 : setNullValue(retval);
2168 : 0 : return true;
2169 : : }
2791 tgl@sss.pgh.pa.us 2170 [ - + ]:CBC 41 : else if (!coerceToBool(&a2, &ba2))
2791 tgl@sss.pgh.pa.us 2171 :UBC 0 : return false;
2172 : : else
2173 : : {
2791 tgl@sss.pgh.pa.us 2174 :CBC 41 : setBoolValue(retval, ba2);
2175 : 41 : return true;
2176 : : }
2177 : :
2178 : : return true;
2179 : :
2180 : 4 : case PGBENCH_OR:
2181 : :
2182 [ - + ]: 4 : if (a1.type == PGBT_NULL)
2183 : : {
2791 tgl@sss.pgh.pa.us 2184 :UBC 0 : setNullValue(retval);
2185 : 0 : return true;
2186 : : }
2187 : :
2791 tgl@sss.pgh.pa.us 2188 [ - + ]:CBC 4 : if (!coerceToBool(&a1, &ba1))
2791 tgl@sss.pgh.pa.us 2189 :UBC 0 : return false;
2190 : :
2791 tgl@sss.pgh.pa.us 2191 [ + + ]:CBC 4 : if (ba1)
2192 : : {
2193 : 1 : setBoolValue(retval, true);
2194 : 1 : return true;
2195 : : }
2196 : :
2449 2197 [ - + ]: 3 : if (!evaluateExpr(st, args->expr, &a2))
2791 tgl@sss.pgh.pa.us 2198 :UBC 0 : return false;
2199 : :
2791 tgl@sss.pgh.pa.us 2200 [ - + ]:CBC 3 : if (a2.type == PGBT_NULL)
2201 : : {
2791 tgl@sss.pgh.pa.us 2202 :UBC 0 : setNullValue(retval);
2203 : 0 : return true;
2204 : : }
2791 tgl@sss.pgh.pa.us 2205 [ - + ]:CBC 3 : else if (!coerceToBool(&a2, &ba2))
2791 tgl@sss.pgh.pa.us 2206 :UBC 0 : return false;
2207 : : else
2208 : : {
2791 tgl@sss.pgh.pa.us 2209 :CBC 3 : setBoolValue(retval, ba2);
2210 : 3 : return true;
2211 : : }
2212 : :
2213 : 16 : case PGBENCH_CASE:
2214 : : /* when true, execute branch */
2215 [ + + ]: 16 : if (valueTruth(&a1))
2449 2216 : 11 : return evaluateExpr(st, args->expr, retval);
2217 : :
2218 : : /* now args contains next condition or final else expression */
2791 2219 : 5 : args = args->next;
2220 : :
2221 : : /* final else case? */
2222 [ + + ]: 5 : if (args->next == NULL)
2449 2223 : 3 : return evaluateExpr(st, args->expr, retval);
2224 : :
2225 : : /* no, another when, proceed */
2226 : 2 : return evalLazyFunc(st, PGBENCH_CASE, args, retval);
2227 : :
2791 tgl@sss.pgh.pa.us 2228 :UBC 0 : default:
2229 : : /* internal error, cannot get here */
2230 : 0 : Assert(0);
2231 : : break;
2232 : : }
2233 : : return false;
2234 : : }
2235 : :
2236 : : /* maximum number of function arguments */
2237 : : #define MAX_FARGS 16
2238 : :
2239 : : /*
2240 : : * Recursive evaluation of standard functions,
2241 : : * which do not require lazy evaluation.
2242 : : */
2243 : : static bool
2449 tgl@sss.pgh.pa.us 2244 :CBC 3434 : evalStandardFunc(CState *st,
2245 : : PgBenchFunction func, PgBenchExprLink *args,
2246 : : PgBenchValue *retval)
2247 : : {
2248 : : /* evaluate all function arguments */
2791 2249 : 3434 : int nargs = 0;
271 peter@eisentraut.org 2250 : 3434 : PgBenchValue vargs[MAX_FARGS] = {0};
3577 rhaas@postgresql.org 2251 : 3434 : PgBenchExprLink *l = args;
2791 tgl@sss.pgh.pa.us 2252 : 3434 : bool has_null = false;
2253 : :
3577 rhaas@postgresql.org 2254 [ + + + + ]: 10265 : for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
2255 : : {
2449 tgl@sss.pgh.pa.us 2256 [ + + ]: 6833 : if (!evaluateExpr(st, l->expr, &vargs[nargs]))
3577 rhaas@postgresql.org 2257 : 2 : return false;
2898 teodor@sigaev.ru 2258 : 6831 : has_null |= vargs[nargs].type == PGBT_NULL;
2259 : : }
2260 : :
3577 rhaas@postgresql.org 2261 [ + + ]: 3432 : if (l != NULL)
2262 : : {
2169 peter@eisentraut.org 2263 : 1 : pg_log_error("too many function arguments, maximum is %d", MAX_FARGS);
3577 rhaas@postgresql.org 2264 : 1 : return false;
2265 : : }
2266 : :
2267 : : /* NULL arguments */
2898 teodor@sigaev.ru 2268 [ + + + + : 3431 : if (has_null && func != PGBENCH_IS && func != PGBENCH_DEBUG)
+ + ]
2269 : : {
2270 : 3 : setNullValue(retval);
2271 : 3 : return true;
2272 : : }
2273 : :
2274 : : /* then evaluate function */
3577 rhaas@postgresql.org 2275 [ + + + + : 3428 : switch (func)
+ + + + +
+ + + + +
- ]
2276 : : {
2277 : : /* overloaded operators */
2278 : 1698 : case PGBENCH_ADD:
2279 : : case PGBENCH_SUB:
2280 : : case PGBENCH_MUL:
2281 : : case PGBENCH_DIV:
2282 : : case PGBENCH_MOD:
2283 : : case PGBENCH_EQ:
2284 : : case PGBENCH_NE:
2285 : : case PGBENCH_LE:
2286 : : case PGBENCH_LT:
2287 : : {
3477 2288 : 1698 : PgBenchValue *lval = &vargs[0],
2289 : 1698 : *rval = &vargs[1];
2290 : :
3577 2291 [ - + ]: 1698 : Assert(nargs == 2);
2292 : :
2293 : : /* overloaded type management, double if some double */
3550 2294 [ + + ]: 1698 : if ((lval->type == PGBT_DOUBLE ||
2295 [ + + + - ]: 1698 : rval->type == PGBT_DOUBLE) && func != PGBENCH_MOD)
2296 : : {
2297 : : double ld,
2298 : : rd;
2299 : :
2300 [ + - ]: 31 : if (!coerceToDouble(lval, &ld) ||
2301 [ - + ]: 31 : !coerceToDouble(rval, &rd))
2302 : 31 : return false;
2303 : :
2304 [ + + + + : 31 : switch (func)
+ + + +
- ]
2305 : : {
2306 : 1 : case PGBENCH_ADD:
2307 : 1 : setDoubleValue(retval, ld + rd);
2308 : 1 : return true;
2309 : :
2310 : 10 : case PGBENCH_SUB:
2311 : 10 : setDoubleValue(retval, ld - rd);
2312 : 10 : return true;
2313 : :
2314 : 8 : case PGBENCH_MUL:
2315 : 8 : setDoubleValue(retval, ld * rd);
2316 : 8 : return true;
2317 : :
2318 : 2 : case PGBENCH_DIV:
2319 : 2 : setDoubleValue(retval, ld / rd);
2320 : 2 : return true;
2321 : :
2898 teodor@sigaev.ru 2322 : 4 : case PGBENCH_EQ:
2323 : 4 : setBoolValue(retval, ld == rd);
2324 : 4 : return true;
2325 : :
2326 : 2 : case PGBENCH_NE:
2327 : 2 : setBoolValue(retval, ld != rd);
2328 : 2 : return true;
2329 : :
2330 : 2 : case PGBENCH_LE:
2331 : 2 : setBoolValue(retval, ld <= rd);
2332 : 2 : return true;
2333 : :
2334 : 2 : case PGBENCH_LT:
2335 : 2 : setBoolValue(retval, ld < rd);
2336 : 2 : return true;
2337 : :
3550 rhaas@postgresql.org 2338 :UBC 0 : default:
2339 : : /* cannot get here */
2340 : 0 : Assert(0);
2341 : : }
2342 : : }
2343 : : else /* we have integer operands, or % */
2344 : : {
2345 : : int64 li,
2346 : : ri,
2347 : : res;
2348 : :
3550 rhaas@postgresql.org 2349 [ + + ]:CBC 1667 : if (!coerceToInt(lval, &li) ||
2350 [ - + ]: 1666 : !coerceToInt(rval, &ri))
2351 : 1667 : return false;
2352 : :
2353 [ + + + + : 1666 : switch (func)
+ + + +
- ]
2354 : : {
2355 : 44 : case PGBENCH_ADD:
2637 andres@anarazel.de 2356 [ + + ]: 44 : if (pg_add_s64_overflow(li, ri, &res))
2357 : : {
2169 peter@eisentraut.org 2358 : 1 : pg_log_error("bigint add out of range");
2637 andres@anarazel.de 2359 : 1 : return false;
2360 : : }
2361 : 43 : setIntValue(retval, res);
3550 rhaas@postgresql.org 2362 : 43 : return true;
2363 : :
2364 : 149 : case PGBENCH_SUB:
2637 andres@anarazel.de 2365 [ + + ]: 149 : if (pg_sub_s64_overflow(li, ri, &res))
2366 : : {
2169 peter@eisentraut.org 2367 : 1 : pg_log_error("bigint sub out of range");
2637 andres@anarazel.de 2368 : 1 : return false;
2369 : : }
2370 : 148 : setIntValue(retval, res);
3550 rhaas@postgresql.org 2371 : 148 : return true;
2372 : :
2373 : 1404 : case PGBENCH_MUL:
2637 andres@anarazel.de 2374 [ + + ]: 1404 : if (pg_mul_s64_overflow(li, ri, &res))
2375 : : {
2169 peter@eisentraut.org 2376 : 1 : pg_log_error("bigint mul out of range");
2637 andres@anarazel.de 2377 : 1 : return false;
2378 : : }
2379 : 1403 : setIntValue(retval, res);
3550 rhaas@postgresql.org 2380 : 1403 : return true;
2381 : :
2898 teodor@sigaev.ru 2382 : 37 : case PGBENCH_EQ:
2383 : 37 : setBoolValue(retval, li == ri);
2384 : 37 : return true;
2385 : :
2386 : 5 : case PGBENCH_NE:
2387 : 5 : setBoolValue(retval, li != ri);
2388 : 5 : return true;
2389 : :
2390 : 5 : case PGBENCH_LE:
2391 : 5 : setBoolValue(retval, li <= ri);
2392 : 5 : return true;
2393 : :
2394 : 12 : case PGBENCH_LT:
2395 : 12 : setBoolValue(retval, li < ri);
2396 : 12 : return true;
2397 : :
3550 rhaas@postgresql.org 2398 : 10 : case PGBENCH_DIV:
2399 : : case PGBENCH_MOD:
2400 [ + + ]: 10 : if (ri == 0)
2401 : : {
2169 peter@eisentraut.org 2402 : 2 : pg_log_error("division by zero");
3550 rhaas@postgresql.org 2403 : 2 : return false;
2404 : : }
2405 : : /* special handling of -1 divisor */
2406 [ + + ]: 8 : if (ri == -1)
2407 : : {
2408 [ + + ]: 3 : if (func == PGBENCH_DIV)
2409 : : {
2410 : : /* overflow check (needed for INT64_MIN) */
2411 [ + + ]: 2 : if (li == PG_INT64_MIN)
2412 : : {
2169 peter@eisentraut.org 2413 : 1 : pg_log_error("bigint div out of range");
3550 rhaas@postgresql.org 2414 : 1 : return false;
2415 : : }
2416 : : else
3477 2417 : 1 : setIntValue(retval, -li);
2418 : : }
2419 : : else
3550 2420 : 1 : setIntValue(retval, 0);
2421 : 2 : return true;
2422 : : }
2423 : : /* else divisor is not -1 */
2424 [ + + ]: 5 : if (func == PGBENCH_DIV)
2425 : 2 : setIntValue(retval, li / ri);
2426 : : else /* func == PGBENCH_MOD */
2427 : 3 : setIntValue(retval, li % ri);
2428 : :
3577 2429 : 5 : return true;
2430 : :
3550 rhaas@postgresql.org 2431 :UBC 0 : default:
2432 : : /* cannot get here */
2433 : 0 : Assert(0);
2434 : : }
2435 : : }
2436 : :
2437 : : Assert(0);
2438 : : return false; /* NOTREACHED */
2439 : : }
2440 : :
2441 : : /* integer bitwise operators */
2898 teodor@sigaev.ru 2442 :CBC 14 : case PGBENCH_BITAND:
2443 : : case PGBENCH_BITOR:
2444 : : case PGBENCH_BITXOR:
2445 : : case PGBENCH_LSHIFT:
2446 : : case PGBENCH_RSHIFT:
2447 : : {
2448 : : int64 li,
2449 : : ri;
2450 : :
2451 [ + - - + ]: 14 : if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
2898 teodor@sigaev.ru 2452 :UBC 0 : return false;
2453 : :
2898 teodor@sigaev.ru 2454 [ + + ]:CBC 14 : if (func == PGBENCH_BITAND)
2455 : 1 : setIntValue(retval, li & ri);
2456 [ + + ]: 13 : else if (func == PGBENCH_BITOR)
2457 : 2 : setIntValue(retval, li | ri);
2458 [ + + ]: 11 : else if (func == PGBENCH_BITXOR)
2459 : 3 : setIntValue(retval, li ^ ri);
2460 [ + + ]: 8 : else if (func == PGBENCH_LSHIFT)
2461 : 7 : setIntValue(retval, li << ri);
2462 [ + - ]: 1 : else if (func == PGBENCH_RSHIFT)
2463 : 1 : setIntValue(retval, li >> ri);
2464 : : else /* cannot get here */
2898 teodor@sigaev.ru 2465 :UBC 0 : Assert(0);
2466 : :
2898 teodor@sigaev.ru 2467 :CBC 14 : return true;
2468 : : }
2469 : :
2470 : : /* logical operators */
2471 : 16 : case PGBENCH_NOT:
2472 : : {
2473 : : bool b;
2474 : :
2475 [ + + ]: 16 : if (!coerceToBool(&vargs[0], &b))
2476 : 1 : return false;
2477 : :
2478 : 15 : setBoolValue(retval, !b);
2479 : 15 : return true;
2480 : : }
2481 : :
2482 : : /* no arguments */
3550 rhaas@postgresql.org 2483 : 1 : case PGBENCH_PI:
2484 : 1 : setDoubleValue(retval, M_PI);
2485 : 1 : return true;
2486 : :
2487 : : /* 1 overloaded argument */
3577 2488 : 2 : case PGBENCH_ABS:
2489 : : {
3550 2490 : 2 : PgBenchValue *varg = &vargs[0];
2491 : :
3577 2492 [ - + ]: 2 : Assert(nargs == 1);
2493 : :
3550 2494 [ + + ]: 2 : if (varg->type == PGBT_INT)
2495 : : {
3477 2496 : 1 : int64 i = varg->u.ival;
2497 : :
3550 2498 : 1 : setIntValue(retval, i < 0 ? -i : i);
2499 : : }
2500 : : else
2501 : : {
3477 2502 : 1 : double d = varg->u.dval;
2503 : :
3550 2504 [ - + ]: 1 : Assert(varg->type == PGBT_DOUBLE);
3477 2505 [ + - ]: 1 : setDoubleValue(retval, d < 0.0 ? -d : d);
2506 : : }
2507 : :
3577 2508 : 2 : return true;
2509 : : }
2510 : :
2511 : 84 : case PGBENCH_DEBUG:
2512 : : {
3550 2513 : 84 : PgBenchValue *varg = &vargs[0];
2514 : :
2515 [ - + ]: 84 : Assert(nargs == 1);
2516 : :
3477 2517 : 84 : fprintf(stderr, "debug(script=%d,command=%d): ",
3368 heikki.linnakangas@i 2518 : 84 : st->use_file, st->command + 1);
2519 : :
2898 teodor@sigaev.ru 2520 [ + + ]: 84 : if (varg->type == PGBT_NULL)
2521 : 2 : fprintf(stderr, "null\n");
2522 [ + + ]: 82 : else if (varg->type == PGBT_BOOLEAN)
2523 [ + + ]: 19 : fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
2524 [ + + ]: 63 : else if (varg->type == PGBT_INT)
3477 rhaas@postgresql.org 2525 : 47 : fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
2898 teodor@sigaev.ru 2526 [ + - ]: 16 : else if (varg->type == PGBT_DOUBLE)
3511 tgl@sss.pgh.pa.us 2527 : 16 : fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
2528 : : else /* internal error, unexpected type */
2898 teodor@sigaev.ru 2529 :UBC 0 : Assert(0);
2530 : :
3550 rhaas@postgresql.org 2531 :CBC 84 : *retval = *varg;
2532 : :
2533 : 84 : return true;
2534 : : }
2535 : :
2536 : : /* 1 double argument */
2537 : 5 : case PGBENCH_DOUBLE:
2538 : : case PGBENCH_SQRT:
2539 : : case PGBENCH_LN:
2540 : : case PGBENCH_EXP:
2541 : : {
2542 : : double dval;
2543 : :
3577 2544 [ - + ]: 5 : Assert(nargs == 1);
2545 : :
3550 2546 [ + + ]: 5 : if (!coerceToDouble(&vargs[0], &dval))
2547 : 1 : return false;
2548 : :
2549 [ + + ]: 4 : if (func == PGBENCH_SQRT)
2550 : 1 : dval = sqrt(dval);
2898 teodor@sigaev.ru 2551 [ + + ]: 3 : else if (func == PGBENCH_LN)
2552 : 1 : dval = log(dval);
2553 [ + + ]: 2 : else if (func == PGBENCH_EXP)
2554 : 1 : dval = exp(dval);
2555 : : /* else is cast: do nothing */
2556 : :
3550 rhaas@postgresql.org 2557 : 4 : setDoubleValue(retval, dval);
2558 : 4 : return true;
2559 : : }
2560 : :
2561 : : /* 1 int argument */
2562 : 2 : case PGBENCH_INT:
2563 : : {
2564 : : int64 ival;
2565 : :
2566 [ - + ]: 2 : Assert(nargs == 1);
2567 : :
2568 [ + + ]: 2 : if (!coerceToInt(&vargs[0], &ival))
2569 : 1 : return false;
2570 : :
2571 : 1 : setIntValue(retval, ival);
3577 2572 : 1 : return true;
2573 : : }
2574 : :
2575 : : /* variable number of arguments */
3512 tgl@sss.pgh.pa.us 2576 : 4 : case PGBENCH_LEAST:
2577 : : case PGBENCH_GREATEST:
2578 : : {
2579 : : bool havedouble;
2580 : : int i;
2581 : :
2582 [ - + ]: 4 : Assert(nargs >= 1);
2583 : :
2584 : : /* need double result if any input is double */
2585 : 4 : havedouble = false;
2586 [ + + ]: 14 : for (i = 0; i < nargs; i++)
2587 : : {
2588 [ + + ]: 12 : if (vargs[i].type == PGBT_DOUBLE)
2589 : : {
2590 : 2 : havedouble = true;
2591 : 2 : break;
2592 : : }
2593 : : }
2594 [ + + ]: 4 : if (havedouble)
2595 : : {
2596 : : double extremum;
2597 : :
2598 [ - + ]: 2 : if (!coerceToDouble(&vargs[0], &extremum))
3550 rhaas@postgresql.org 2599 :UBC 0 : return false;
3512 tgl@sss.pgh.pa.us 2600 [ + + ]:CBC 6 : for (i = 1; i < nargs; i++)
2601 : : {
2602 : : double dval;
2603 : :
2604 [ - + ]: 4 : if (!coerceToDouble(&vargs[i], &dval))
3512 tgl@sss.pgh.pa.us 2605 :UBC 0 : return false;
3512 tgl@sss.pgh.pa.us 2606 [ + + ]:CBC 4 : if (func == PGBENCH_LEAST)
2607 [ + - ]: 2 : extremum = Min(extremum, dval);
2608 : : else
2609 [ + - ]: 2 : extremum = Max(extremum, dval);
2610 : : }
2611 : 2 : setDoubleValue(retval, extremum);
2612 : : }
2613 : : else
2614 : : {
2615 : : int64 extremum;
2616 : :
2617 [ - + ]: 2 : if (!coerceToInt(&vargs[0], &extremum))
3512 tgl@sss.pgh.pa.us 2618 :UBC 0 : return false;
3512 tgl@sss.pgh.pa.us 2619 [ + + ]:CBC 8 : for (i = 1; i < nargs; i++)
2620 : : {
2621 : : int64 ival;
2622 : :
2623 [ - + ]: 6 : if (!coerceToInt(&vargs[i], &ival))
3512 tgl@sss.pgh.pa.us 2624 :UBC 0 : return false;
3512 tgl@sss.pgh.pa.us 2625 [ + + ]:CBC 6 : if (func == PGBENCH_LEAST)
2626 : 3 : extremum = Min(extremum, ival);
2627 : : else
2628 : 3 : extremum = Max(extremum, ival);
2629 : : }
2630 : 2 : setIntValue(retval, extremum);
2631 : : }
3577 rhaas@postgresql.org 2632 : 4 : return true;
2633 : : }
2634 : :
2635 : : /* random functions */
3550 2636 : 1531 : case PGBENCH_RANDOM:
2637 : : case PGBENCH_RANDOM_EXPONENTIAL:
2638 : : case PGBENCH_RANDOM_GAUSSIAN:
2639 : : case PGBENCH_RANDOM_ZIPFIAN:
2640 : : {
2641 : : int64 imin,
2642 : : imax,
2643 : : delta;
2644 : :
3477 2645 [ - + ]: 1531 : Assert(nargs >= 2);
2646 : :
2647 [ + + ]: 1531 : if (!coerceToInt(&vargs[0], &imin) ||
2648 [ - + ]: 1530 : !coerceToInt(&vargs[1], &imax))
3550 2649 : 1 : return false;
2650 : :
2651 : : /* check random range */
1632 tgl@sss.pgh.pa.us 2652 [ + + ]: 1530 : if (unlikely(imin > imax))
2653 : : {
2169 peter@eisentraut.org 2654 : 1 : pg_log_error("empty range given to random");
3477 rhaas@postgresql.org 2655 : 1 : return false;
2656 : : }
1632 tgl@sss.pgh.pa.us 2657 [ + + - + : 1529 : else if (unlikely(pg_sub_s64_overflow(imax, imin, &delta) ||
+ + ]
2658 : : pg_add_s64_overflow(delta, 1, &delta)))
2659 : : {
2660 : : /* prevent int overflows in random functions */
2169 peter@eisentraut.org 2661 : 1 : pg_log_error("random range is too large");
3477 rhaas@postgresql.org 2662 : 1 : return false;
2663 : : }
2664 : :
2665 [ + + ]: 1528 : if (func == PGBENCH_RANDOM)
2666 : : {
2667 [ - + ]: 1515 : Assert(nargs == 2);
2587 alvherre@alvh.no-ip. 2668 : 1515 : setIntValue(retval, getrand(&st->cs_func_rs, imin, imax));
2669 : : }
2670 : : else /* gaussian & exponential */
2671 : : {
2672 : : double param;
2673 : :
3477 rhaas@postgresql.org 2674 [ - + ]: 13 : Assert(nargs == 3);
2675 : :
2676 [ - + ]: 13 : if (!coerceToDouble(&vargs[2], ¶m))
3550 2677 : 4 : return false;
2678 : :
3477 2679 [ + + ]: 13 : if (func == PGBENCH_RANDOM_GAUSSIAN)
2680 : : {
2681 [ + + ]: 4 : if (param < MIN_GAUSSIAN_PARAM)
2682 : : {
2169 peter@eisentraut.org 2683 : 1 : pg_log_error("gaussian parameter must be at least %f (not %f)",
2684 : : MIN_GAUSSIAN_PARAM, param);
3477 rhaas@postgresql.org 2685 : 1 : return false;
2686 : : }
2687 : :
2688 : 3 : setIntValue(retval,
2689 : : getGaussianRand(&st->cs_func_rs,
2690 : : imin, imax, param));
2691 : : }
2924 teodor@sigaev.ru 2692 [ + + ]: 9 : else if (func == PGBENCH_RANDOM_ZIPFIAN)
2693 : : {
2451 tgl@sss.pgh.pa.us 2694 [ + + + + ]: 5 : if (param < MIN_ZIPFIAN_PARAM || param > MAX_ZIPFIAN_PARAM)
2695 : : {
2169 peter@eisentraut.org 2696 : 2 : pg_log_error("zipfian parameter must be in range [%.3f, %.0f] (not %f)",
2697 : : MIN_ZIPFIAN_PARAM, MAX_ZIPFIAN_PARAM, param);
2924 teodor@sigaev.ru 2698 : 2 : return false;
2699 : : }
2700 : :
2701 : 3 : setIntValue(retval,
2702 : : getZipfianRand(&st->cs_func_rs, imin, imax, param));
2703 : : }
2704 : : else /* exponential */
2705 : : {
3477 rhaas@postgresql.org 2706 [ + + ]: 4 : if (param <= 0.0)
2707 : : {
2169 peter@eisentraut.org 2708 : 1 : pg_log_error("exponential parameter must be greater than zero (not %f)",
2709 : : param);
3477 rhaas@postgresql.org 2710 : 1 : return false;
2711 : : }
2712 : :
2713 : 3 : setIntValue(retval,
2714 : : getExponentialRand(&st->cs_func_rs,
2715 : : imin, imax, param));
2716 : : }
2717 : : }
2718 : :
2719 : 1524 : return true;
2720 : : }
2721 : :
2911 2722 : 9 : case PGBENCH_POW:
2723 : : {
2724 : 9 : PgBenchValue *lval = &vargs[0];
2725 : 9 : PgBenchValue *rval = &vargs[1];
2726 : : double ld,
2727 : : rd;
2728 : :
2729 [ - + ]: 9 : Assert(nargs == 2);
2730 : :
2731 [ + - ]: 9 : if (!coerceToDouble(lval, &ld) ||
2732 [ - + ]: 9 : !coerceToDouble(rval, &rd))
2911 rhaas@postgresql.org 2733 :UBC 0 : return false;
2734 : :
2911 rhaas@postgresql.org 2735 :CBC 9 : setDoubleValue(retval, pow(ld, rd));
2736 : :
2737 : 9 : return true;
2738 : : }
2739 : :
2898 teodor@sigaev.ru 2740 : 10 : case PGBENCH_IS:
2741 : : {
2742 [ - + ]: 10 : Assert(nargs == 2);
2743 : :
2744 : : /*
2745 : : * note: this simple implementation is more permissive than
2746 : : * SQL
2747 : : */
2748 : 10 : setBoolValue(retval,
2749 [ + + ]: 15 : vargs[0].type == vargs[1].type &&
2750 [ + - ]: 5 : vargs[0].u.bval == vargs[1].u.bval);
2751 : 10 : return true;
2752 : : }
2753 : :
2754 : : /* hashing */
2827 2755 : 6 : case PGBENCH_HASH_FNV1A:
2756 : : case PGBENCH_HASH_MURMUR2:
2757 : : {
2758 : : int64 val,
2759 : : seed;
2760 : :
2761 [ - + ]: 6 : Assert(nargs == 2);
2762 : :
2763 [ + - ]: 6 : if (!coerceToInt(&vargs[0], &val) ||
2764 [ - + ]: 6 : !coerceToInt(&vargs[1], &seed))
2827 teodor@sigaev.ru 2765 :UBC 0 : return false;
2766 : :
2827 teodor@sigaev.ru 2767 [ + + ]:CBC 6 : if (func == PGBENCH_HASH_MURMUR2)
2768 : 5 : setIntValue(retval, getHashMurmur2(val, seed));
2769 [ + - ]: 1 : else if (func == PGBENCH_HASH_FNV1A)
2770 : 1 : setIntValue(retval, getHashFnv1a(val, seed));
2771 : : else
2772 : : /* cannot get here */
2827 teodor@sigaev.ru 2773 :UBC 0 : Assert(0);
2774 : :
2827 teodor@sigaev.ru 2775 :CBC 6 : return true;
2776 : : }
2777 : :
1715 dean.a.rasheed@gmail 2778 : 46 : case PGBENCH_PERMUTE:
2779 : : {
2780 : : int64 val,
2781 : : size,
2782 : : seed;
2783 : :
2784 [ - + ]: 46 : Assert(nargs == 3);
2785 : :
2786 [ + - ]: 46 : if (!coerceToInt(&vargs[0], &val) ||
2787 [ + - ]: 46 : !coerceToInt(&vargs[1], &size) ||
2788 [ - + ]: 46 : !coerceToInt(&vargs[2], &seed))
1715 dean.a.rasheed@gmail 2789 :UBC 0 : return false;
2790 : :
1715 dean.a.rasheed@gmail 2791 [ + + ]:CBC 46 : if (size <= 0)
2792 : : {
2793 : 1 : pg_log_error("permute size parameter must be greater than zero");
2794 : 1 : return false;
2795 : : }
2796 : :
2797 : 45 : setIntValue(retval, permute(val, size, seed));
2798 : 45 : return true;
2799 : : }
2800 : :
3577 rhaas@postgresql.org 2801 :UBC 0 : default:
2802 : : /* cannot get here */
3550 2803 : 0 : Assert(0);
2804 : : /* dead code to avoid a compiler warning */
2805 : : return false;
2806 : : }
2807 : : }
2808 : :
2809 : : /* evaluate some function */
2810 : : static bool
2449 tgl@sss.pgh.pa.us 2811 :CBC 3497 : evalFunc(CState *st,
2812 : : PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
2813 : : {
2898 teodor@sigaev.ru 2814 [ + + ]: 3497 : if (isLazyFunc(func))
2449 tgl@sss.pgh.pa.us 2815 : 63 : return evalLazyFunc(st, func, args, retval);
2816 : : else
2817 : 3434 : return evalStandardFunc(st, func, args, retval);
2818 : : }
2819 : :
2820 : : /*
2821 : : * Recursive evaluation of an expression in a pgbench script
2822 : : * using the current state of variables.
2823 : : * Returns whether the evaluation was ok,
2824 : : * the value itself is returned through the retval pointer.
2825 : : */
2826 : : static bool
2827 : 9200 : evaluateExpr(CState *st, PgBenchExpr *expr, PgBenchValue *retval)
2828 : : {
3577 rhaas@postgresql.org 2829 [ + + + - ]: 9200 : switch (expr->etype)
2830 : : {
3550 2831 : 3677 : case ENODE_CONSTANT:
2832 : : {
2833 : 3677 : *retval = expr->u.constant;
3577 2834 : 3677 : return true;
2835 : : }
2836 : :
2837 : 2026 : case ENODE_VARIABLE:
2838 : : {
2839 : : Variable *var;
2840 : :
1364 ishii@postgresql.org 2841 [ + + ]: 2026 : if ((var = lookupVariable(&st->variables, expr->u.variable.varname)) == NULL)
2842 : : {
2169 peter@eisentraut.org 2843 : 2 : pg_log_error("undefined variable \"%s\"", expr->u.variable.varname);
3577 rhaas@postgresql.org 2844 : 2 : return false;
2845 : : }
2846 : :
2898 teodor@sigaev.ru 2847 [ + + ]: 2024 : if (!makeVariableValue(var))
3511 tgl@sss.pgh.pa.us 2848 : 2 : return false;
2849 : :
2898 teodor@sigaev.ru 2850 : 2022 : *retval = var->value;
3577 rhaas@postgresql.org 2851 : 2022 : return true;
2852 : : }
2853 : :
2854 : 3497 : case ENODE_FUNCTION:
2449 tgl@sss.pgh.pa.us 2855 : 3497 : return evalFunc(st,
2856 : : expr->u.function.function,
2857 : : expr->u.function.args,
2858 : : retval);
2859 : :
3942 rhaas@postgresql.org 2860 :UBC 0 : default:
2861 : : /* internal error which should never occur */
1348 tgl@sss.pgh.pa.us 2862 : 0 : pg_fatal("unexpected enode type in evaluation: %d", expr->etype);
2863 : : }
2864 : : }
2865 : :
2866 : : /*
2867 : : * Convert command name to meta-command enum identifier
2868 : : */
2869 : : static MetaCommand
2966 tgl@sss.pgh.pa.us 2870 :CBC 532 : getMetaCommand(const char *cmd)
2871 : : {
2872 : : MetaCommand mc;
2873 : :
2874 [ - + ]: 532 : if (cmd == NULL)
2966 tgl@sss.pgh.pa.us 2875 :UBC 0 : mc = META_NONE;
2966 tgl@sss.pgh.pa.us 2876 [ + + ]:CBC 532 : else if (pg_strcasecmp(cmd, "set") == 0)
2877 : 363 : mc = META_SET;
2878 [ + + ]: 169 : else if (pg_strcasecmp(cmd, "setshell") == 0)
2879 : 4 : mc = META_SETSHELL;
2880 [ + + ]: 165 : else if (pg_strcasecmp(cmd, "shell") == 0)
2881 : 5 : mc = META_SHELL;
2882 [ + + ]: 160 : else if (pg_strcasecmp(cmd, "sleep") == 0)
2883 : 9 : mc = META_SLEEP;
2826 teodor@sigaev.ru 2884 [ + + ]: 151 : else if (pg_strcasecmp(cmd, "if") == 0)
2885 : 24 : mc = META_IF;
2886 [ + + ]: 127 : else if (pg_strcasecmp(cmd, "elif") == 0)
2887 : 13 : mc = META_ELIF;
2888 [ + + ]: 114 : else if (pg_strcasecmp(cmd, "else") == 0)
2889 : 14 : mc = META_ELSE;
2890 [ + + ]: 100 : else if (pg_strcasecmp(cmd, "endif") == 0)
2891 : 21 : mc = META_ENDIF;
2532 alvherre@alvh.no-ip. 2892 [ + + ]: 79 : else if (pg_strcasecmp(cmd, "gset") == 0)
2893 : 32 : mc = META_GSET;
2083 michael@paquier.xyz 2894 [ + + ]: 47 : else if (pg_strcasecmp(cmd, "aset") == 0)
2895 : 3 : mc = META_ASET;
1737 alvherre@alvh.no-ip. 2896 [ + + ]: 44 : else if (pg_strcasecmp(cmd, "startpipeline") == 0)
2897 : 21 : mc = META_STARTPIPELINE;
692 michael@paquier.xyz 2898 [ + + ]: 23 : else if (pg_strcasecmp(cmd, "syncpipeline") == 0)
2899 : 5 : mc = META_SYNCPIPELINE;
1737 alvherre@alvh.no-ip. 2900 [ + + ]: 18 : else if (pg_strcasecmp(cmd, "endpipeline") == 0)
2901 : 17 : mc = META_ENDPIPELINE;
2902 : : else
2966 tgl@sss.pgh.pa.us 2903 : 1 : mc = META_NONE;
2904 : 532 : return mc;
2905 : : }
2906 : :
2907 : : /*
2908 : : * Run a shell command. The result is assigned to the variable if not NULL.
2909 : : * Return true if succeeded, or false on error.
2910 : : */
2911 : : static bool
1364 ishii@postgresql.org 2912 : 6 : runShellCommand(Variables *variables, char *variable, char **argv, int argc)
2913 : : {
2914 : : char command[SHELL_COMMAND_SIZE];
2915 : : int i,
5772 bruce@momjian.us 2916 : 6 : len = 0;
2917 : : FILE *fp;
2918 : : char res[64];
2919 : : char *endptr;
2920 : : int retval;
2921 : :
2922 : : /*----------
2923 : : * Join arguments with whitespace separators. Arguments starting with
2924 : : * exactly one colon are treated as variables:
2925 : : * name - append a string "name"
2926 : : * :var - append a variable named 'var'
2927 : : * ::name - append a string ":name"
2928 : : *----------
2929 : : */
5845 itagaki.takahiro@gma 2930 [ + + ]: 17 : for (i = 0; i < argc; i++)
2931 : : {
2932 : : char *arg;
2933 : : int arglen;
2934 : :
2935 [ + + ]: 12 : if (argv[i][0] != ':')
2936 : : {
5772 bruce@momjian.us 2937 : 9 : arg = argv[i]; /* a string literal */
2938 : : }
5845 itagaki.takahiro@gma 2939 [ + + ]: 3 : else if (argv[i][1] == ':')
2940 : : {
2941 : 1 : arg = argv[i] + 1; /* a string literal starting with colons */
2942 : : }
1364 ishii@postgresql.org 2943 [ + + ]: 2 : else if ((arg = getVariable(variables, argv[i] + 1)) == NULL)
2944 : : {
2169 peter@eisentraut.org 2945 : 1 : pg_log_error("%s: undefined variable \"%s\"", argv[0], argv[i]);
5845 itagaki.takahiro@gma 2946 : 1 : return false;
2947 : : }
2948 : :
2949 : 11 : arglen = strlen(arg);
2950 [ - + ]: 11 : if (len + arglen + (i > 0 ? 1 : 0) >= SHELL_COMMAND_SIZE - 1)
2951 : : {
2169 peter@eisentraut.org 2952 :UBC 0 : pg_log_error("%s: shell command is too long", argv[0]);
5845 itagaki.takahiro@gma 2953 : 0 : return false;
2954 : : }
2955 : :
5845 itagaki.takahiro@gma 2956 [ + + ]:CBC 11 : if (i > 0)
2957 : 5 : command[len++] = ' ';
2958 : 11 : memcpy(command + len, arg, arglen);
2959 : 11 : len += arglen;
2960 : : }
2961 : :
2962 : 5 : command[len] = '\0';
2963 : :
1205 tgl@sss.pgh.pa.us 2964 : 5 : fflush(NULL); /* needed before either system() or popen() */
2965 : :
2966 : : /* Fast path for non-assignment case */
5845 itagaki.takahiro@gma 2967 [ + + ]: 5 : if (variable == NULL)
2968 : : {
2969 [ + + ]: 2 : if (system(command))
2970 : : {
2971 [ + - ]: 1 : if (!timer_exceeded)
2169 peter@eisentraut.org 2972 : 1 : pg_log_error("%s: could not launch shell command", argv[0]);
5845 itagaki.takahiro@gma 2973 : 1 : return false;
2974 : : }
2975 : 1 : return true;
2976 : : }
2977 : :
2978 : : /* Execute the command with pipe and read the standard output. */
2979 [ - + ]: 3 : if ((fp = popen(command, "r")) == NULL)
2980 : : {
2169 peter@eisentraut.org 2981 :UBC 0 : pg_log_error("%s: could not launch shell command", argv[0]);
5845 itagaki.takahiro@gma 2982 : 0 : return false;
2983 : : }
5845 itagaki.takahiro@gma 2984 [ + + ]:CBC 3 : if (fgets(res, sizeof(res), fp) == NULL)
2985 : : {
2986 [ + - ]: 1 : if (!timer_exceeded)
2169 peter@eisentraut.org 2987 : 1 : pg_log_error("%s: could not read result of shell command", argv[0]);
4018 tgl@sss.pgh.pa.us 2988 : 1 : (void) pclose(fp);
5845 itagaki.takahiro@gma 2989 : 1 : return false;
2990 : : }
2991 [ - + ]: 2 : if (pclose(fp) < 0)
2992 : : {
1127 peter@eisentraut.org 2993 :UBC 0 : pg_log_error("%s: could not run shell command: %m", argv[0]);
5845 itagaki.takahiro@gma 2994 : 0 : return false;
2995 : : }
2996 : :
2997 : : /* Check whether the result is an integer and assign it to the variable */
5845 itagaki.takahiro@gma 2998 :CBC 2 : retval = (int) strtol(res, &endptr, 10);
2999 [ + + + + ]: 3 : while (*endptr != '\0' && isspace((unsigned char) *endptr))
3000 : 1 : endptr++;
3001 [ + - + + ]: 2 : if (*res == '\0' || *endptr != '\0')
3002 : : {
2169 peter@eisentraut.org 3003 : 1 : pg_log_error("%s: shell command must return an integer (not \"%s\")", argv[0], res);
5845 itagaki.takahiro@gma 3004 : 1 : return false;
3005 : : }
1364 ishii@postgresql.org 3006 [ - + ]: 1 : if (!putVariableInt(variables, "setshell", variable, retval))
5845 itagaki.takahiro@gma 3007 :UBC 0 : return false;
3008 : :
2169 peter@eisentraut.org 3009 [ - + ]:CBC 1 : pg_log_debug("%s: shell parameter name: \"%s\", value: \"%s\"", argv[0], argv[1], res);
3010 : :
5845 itagaki.takahiro@gma 3011 : 1 : return true;
3012 : : }
3013 : :
3014 : : /*
3015 : : * Report the abortion of the client when processing SQL commands.
3016 : : */
3017 : : static void
2826 teodor@sigaev.ru 3018 : 32 : commandFailed(CState *st, const char *cmd, const char *message)
3019 : : {
2169 peter@eisentraut.org 3020 : 32 : pg_log_error("client %d aborted in command %d (%s) of script %d; %s",
3021 : : st->id, st->command, cmd, st->use_file, message);
5979 ishii@postgresql.org 3022 : 32 : }
3023 : :
3024 : : /*
3025 : : * Report the error in the command while the script is executing.
3026 : : */
3027 : : static void
1364 3028 : 2 : commandError(CState *st, const char *message)
3029 : : {
3030 : : /*
3031 : : * Errors should only be detected during an SQL command or the
3032 : : * \endpipeline meta command. Any other case triggers an assertion
3033 : : * failure.
3034 : : */
81 fujii@postgresql.org 3035 [ - + - - ]: 2 : Assert(sql_script[st->use_file].commands[st->command]->type == SQL_COMMAND ||
3036 : : sql_script[st->use_file].commands[st->command]->meta == META_ENDPIPELINE);
3037 : :
1364 ishii@postgresql.org 3038 : 2 : pg_log_info("client %d got an error in command %d (SQL) of script %d; %s",
3039 : : st->id, st->command, st->use_file, message);
3040 : 2 : }
3041 : :
3042 : : /* return a script number with a weighted choice. */
3043 : : static int
3611 alvherre@alvh.no-ip. 3044 : 7760 : chooseScript(TState *thread)
3045 : : {
3559 3046 : 7760 : int i = 0;
3047 : : int64 w;
3048 : :
3611 3049 [ + + ]: 7760 : if (num_scripts == 1)
3050 : 6410 : return 0;
3051 : :
2587 3052 : 1350 : w = getrand(&thread->ts_choose_rs, 0, total_weight - 1);
3053 : : do
3054 : : {
3559 3055 : 3188 : w -= sql_script[i++].weight;
3056 [ + + ]: 3188 : } while (w >= 0);
3057 : :
3058 : 1350 : return i - 1;
3059 : : }
3060 : :
3061 : : /*
3062 : : * Allocate space for CState->prepared: we need one boolean for each command
3063 : : * of each script.
3064 : : */
3065 : : static void
936 3066 : 34 : allocCStatePrepared(CState *st)
3067 : : {
3068 [ - + ]: 34 : Assert(st->prepared == NULL);
3069 : :
3070 : 34 : st->prepared = pg_malloc(sizeof(bool *) * num_scripts);
3071 [ + + ]: 74 : for (int i = 0; i < num_scripts; i++)
3072 : : {
3073 : 40 : ParsedScript *script = &sql_script[i];
3074 : : int numcmds;
3075 : :
3076 [ + + ]: 170 : for (numcmds = 0; script->commands[numcmds] != NULL; numcmds++)
3077 : : ;
3078 : 40 : st->prepared[i] = pg_malloc0(sizeof(bool) * numcmds);
3079 : : }
3080 : 34 : }
3081 : :
3082 : : /*
3083 : : * Prepare the SQL command from st->use_file at command_num.
3084 : : */
3085 : : static void
1029 3086 : 2014 : prepareCommand(CState *st, int command_num)
3087 : : {
3088 : 2014 : Command *command = sql_script[st->use_file].commands[command_num];
3089 : :
3090 : : /* No prepare for non-SQL commands */
3091 [ - + ]: 2014 : if (command->type != SQL_COMMAND)
1029 alvherre@alvh.no-ip. 3092 :UBC 0 : return;
3093 : :
1029 alvherre@alvh.no-ip. 3094 [ + + ]:CBC 2014 : if (!st->prepared)
936 3095 : 29 : allocCStatePrepared(st);
3096 : :
1029 3097 [ + + ]: 2014 : if (!st->prepared[st->use_file][command_num])
3098 : : {
3099 : : PGresult *res;
3100 : :
3101 [ + + ]: 109 : pg_log_debug("client %d preparing %s", st->id, command->prepname);
3102 : 109 : res = PQprepare(st->con, command->prepname,
3103 : 109 : command->argv[0], command->argc - 1, NULL);
3104 [ + + ]: 109 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
3105 : 1 : pg_log_error("%s", PQerrorMessage(st->con));
3106 : 109 : PQclear(res);
3107 : 109 : st->prepared[st->use_file][command_num] = true;
3108 : : }
3109 : : }
3110 : :
3111 : : /*
3112 : : * Prepare all the commands in the script that come after the \startpipeline
3113 : : * that's at position st->command, and the first \endpipeline we find.
3114 : : *
3115 : : * This sets the ->prepared flag for each relevant command as well as the
3116 : : * \startpipeline itself, but doesn't move the st->command counter.
3117 : : */
3118 : : static void
3119 : 42 : prepareCommandsInPipeline(CState *st)
3120 : : {
3121 : : int j;
3122 : 42 : Command **commands = sql_script[st->use_file].commands;
3123 : :
3124 [ + - + - ]: 42 : Assert(commands[st->command]->type == META_COMMAND &&
3125 : : commands[st->command]->meta == META_STARTPIPELINE);
3126 : :
936 3127 [ + + ]: 42 : if (!st->prepared)
3128 : 5 : allocCStatePrepared(st);
3129 : :
3130 : : /*
3131 : : * We set the 'prepared' flag on the \startpipeline itself to flag that we
3132 : : * don't need to do this next time without calling prepareCommand(), even
3133 : : * though we don't actually prepare this command.
3134 : : */
3135 [ + + ]: 42 : if (st->prepared[st->use_file][st->command])
1029 3136 : 36 : return;
3137 : :
3138 [ + - ]: 64 : for (j = st->command + 1; commands[j] != NULL; j++)
3139 : : {
3140 [ + + ]: 64 : if (commands[j]->type == META_COMMAND &&
3141 [ + - ]: 6 : commands[j]->meta == META_ENDPIPELINE)
3142 : 6 : break;
3143 : :
3144 : 58 : prepareCommand(st, j);
3145 : : }
3146 : :
3147 : 6 : st->prepared[st->use_file][st->command] = true;
3148 : : }
3149 : :
3150 : : /* Send a SQL command, using the chosen querymode */
3151 : : static bool
3368 heikki.linnakangas@i 3152 : 10571 : sendCommand(CState *st, Command *command)
3153 : : {
3154 : : int r;
3155 : :
3156 [ + + ]: 10571 : if (querymode == QUERY_SIMPLE)
3157 : : {
3158 : : char *sql;
3159 : :
3160 : 8022 : sql = pg_strdup(command->argv[0]);
1364 ishii@postgresql.org 3161 : 8022 : sql = assignVariables(&st->variables, sql);
3162 : :
2169 peter@eisentraut.org 3163 [ + + ]: 8022 : pg_log_debug("client %d sending %s", st->id, sql);
3368 heikki.linnakangas@i 3164 : 8022 : r = PQsendQuery(st->con, sql);
3165 : 8022 : free(sql);
3166 : : }
3167 [ + + ]: 2549 : else if (querymode == QUERY_EXTENDED)
3168 : : {
3169 : 593 : const char *sql = command->argv[0];
3170 : : const char *params[MAX_ARGS];
3171 : :
1364 ishii@postgresql.org 3172 : 593 : getQueryParams(&st->variables, command, params);
3173 : :
2169 peter@eisentraut.org 3174 [ - + ]: 593 : pg_log_debug("client %d sending %s", st->id, sql);
3368 heikki.linnakangas@i 3175 : 593 : r = PQsendQueryParams(st->con, sql, command->argc - 1,
3176 : : NULL, params, NULL, NULL, 0);
3177 : : }
3178 [ + - ]: 1956 : else if (querymode == QUERY_PREPARED)
3179 : : {
3180 : : const char *params[MAX_ARGS];
3181 : :
1029 alvherre@alvh.no-ip. 3182 : 1956 : prepareCommand(st, st->command);
1364 ishii@postgresql.org 3183 : 1956 : getQueryParams(&st->variables, command, params);
3184 : :
1029 alvherre@alvh.no-ip. 3185 [ + + ]: 1956 : pg_log_debug("client %d sending %s", st->id, command->prepname);
3186 : 1956 : r = PQsendQueryPrepared(st->con, command->prepname, command->argc - 1,
3187 : : params, NULL, NULL, 0);
3188 : : }
3189 : : else /* unknown sql mode */
3368 heikki.linnakangas@i 3190 :UBC 0 : r = 0;
3191 : :
3368 heikki.linnakangas@i 3192 [ - + ]:CBC 10571 : if (r == 0)
3193 : : {
2169 peter@eisentraut.org 3194 [ # # ]:UBC 0 : pg_log_debug("client %d could not send %s", st->id, command->argv[0]);
3368 heikki.linnakangas@i 3195 : 0 : return false;
3196 : : }
3197 : : else
3368 heikki.linnakangas@i 3198 :CBC 10571 : return true;
3199 : : }
3200 : :
3201 : : /*
3202 : : * Read and discard all available results from the connection.
3203 : : */
3204 : : static void
39 fujii@postgresql.org 3205 :GNC 50 : discardAvailableResults(CState *st)
3206 : : {
3207 : 50 : PGresult *res = NULL;
3208 : :
3209 : : for (;;)
3210 : : {
3211 : 62 : res = PQgetResult(st->con);
3212 : :
3213 : : /*
3214 : : * Read and discard results until PQgetResult() returns NULL (no more
3215 : : * results) or a connection failure is detected. If the pipeline
3216 : : * status is PQ_PIPELINE_ABORTED, more results may still be available
3217 : : * even after PQgetResult() returns NULL, so continue reading in that
3218 : : * case.
3219 : : */
3220 [ + + + + : 74 : if ((res == NULL && PQpipelineStatus(st->con) != PQ_PIPELINE_ABORTED) ||
+ - ]
3221 : 12 : PQstatus(st->con) == CONNECTION_BAD)
3222 : : break;
3223 : :
3224 : 12 : PQclear(res);
3225 : : }
3226 : 50 : PQclear(res);
3227 : 50 : }
3228 : :
3229 : : /*
3230 : : * Determine the error status based on the connection status and error code.
3231 : : */
3232 : : static EStatus
3233 : 22 : getSQLErrorStatus(CState *st, const char *sqlState)
3234 : : {
3235 : 22 : discardAvailableResults(st);
3236 [ - + ]: 22 : if (PQstatus(st->con) == CONNECTION_BAD)
39 fujii@postgresql.org 3237 :UNC 0 : return ESTATUS_CONN_ERROR;
3238 : :
1364 ishii@postgresql.org 3239 [ + - ]:CBC 22 : if (sqlState != NULL)
3240 : : {
3241 [ + + ]: 22 : if (strcmp(sqlState, ERRCODE_T_R_SERIALIZATION_FAILURE) == 0)
3242 : 1 : return ESTATUS_SERIALIZATION_ERROR;
3243 [ + + ]: 21 : else if (strcmp(sqlState, ERRCODE_T_R_DEADLOCK_DETECTED) == 0)
3244 : 1 : return ESTATUS_DEADLOCK_ERROR;
3245 : : }
3246 : :
3247 : 20 : return ESTATUS_OTHER_SQL_ERROR;
3248 : : }
3249 : :
3250 : : /*
3251 : : * Returns true if this type of error can be retried.
3252 : : */
3253 : : static bool
3254 : 61 : canRetryError(EStatus estatus)
3255 : : {
3256 [ + + + + ]: 61 : return (estatus == ESTATUS_SERIALIZATION_ERROR ||
3257 : : estatus == ESTATUS_DEADLOCK_ERROR);
3258 : : }
3259 : :
3260 : : /*
3261 : : * Returns true if --continue-on-error is specified and this error allows
3262 : : * processing to continue.
3263 : : */
3264 : : static bool
39 fujii@postgresql.org 3265 :GNC 46 : canContinueOnError(EStatus estatus)
3266 : : {
3267 [ + + + - ]: 46 : return (continue_on_error &&
3268 : : estatus == ESTATUS_OTHER_SQL_ERROR);
3269 : : }
3270 : :
3271 : : /*
3272 : : * Process query response from the backend.
3273 : : *
3274 : : * If varprefix is not NULL, it's the variable name prefix where to store
3275 : : * the results of the *last* command (META_GSET) or *all* commands
3276 : : * (META_ASET).
3277 : : *
3278 : : * Returns true if everything is A-OK, false if any error occurs.
3279 : : */
3280 : : static bool
2083 michael@paquier.xyz 3281 :CBC 10616 : readCommandResponse(CState *st, MetaCommand meta, char *varprefix)
3282 : : {
3283 : : PGresult *res;
3284 : : PGresult *next_res;
2532 alvherre@alvh.no-ip. 3285 : 10616 : int qrynum = 0;
3286 : :
3287 : : /*
3288 : : * varprefix should be set only with \gset or \aset, and \endpipeline and
3289 : : * SQL commands do not need it.
3290 : : */
2083 michael@paquier.xyz 3291 [ + + + - : 10616 : Assert((meta == META_NONE && varprefix == NULL) ||
+ + + - +
+ + - +
- ]
3292 : : ((meta == META_ENDPIPELINE) && varprefix == NULL) ||
3293 : : ((meta == META_GSET || meta == META_ASET) && varprefix != NULL));
3294 : :
2458 alvherre@alvh.no-ip. 3295 : 10616 : res = PQgetResult(st->con);
3296 : :
3297 [ + + ]: 21210 : while (res != NULL)
3298 : : {
3299 : : bool is_last;
3300 : :
3301 : : /* peek at the next result to know whether the current is last */
2443 3302 : 10622 : next_res = PQgetResult(st->con);
2442 3303 : 10622 : is_last = (next_res == NULL);
3304 : :
2532 3305 [ + + + + : 10622 : switch (PQresultStatus(res))
+ - ]
3306 : : {
3307 : 8210 : case PGRES_COMMAND_OK: /* non-SELECT commands */
3308 : : case PGRES_EMPTY_QUERY: /* may be used for testing no-op overhead */
2083 michael@paquier.xyz 3309 [ + - + + ]: 8210 : if (is_last && meta == META_GSET)
3310 : : {
2169 peter@eisentraut.org 3311 : 1 : pg_log_error("client %d script %d command %d query %d: expected one row, got %d",
3312 : : st->id, st->use_file, st->command, qrynum, 0);
1364 ishii@postgresql.org 3313 : 1 : st->estatus = ESTATUS_META_COMMAND_ERROR;
2443 alvherre@alvh.no-ip. 3314 : 1 : goto error;
3315 : : }
2532 3316 : 8209 : break;
3317 : :
3318 : 2335 : case PGRES_TUPLES_OK:
2083 michael@paquier.xyz 3319 [ + + + + : 2335 : if ((is_last && meta == META_GSET) || meta == META_ASET)
+ + ]
3320 : : {
3321 : 542 : int ntuples = PQntuples(res);
3322 : :
3323 [ + + + + ]: 542 : if (meta == META_GSET && ntuples != 1)
3324 : : {
3325 : : /* under \gset, report the error */
2169 peter@eisentraut.org 3326 : 2 : pg_log_error("client %d script %d command %d query %d: expected one row, got %d",
3327 : : st->id, st->use_file, st->command, qrynum, PQntuples(res));
1364 ishii@postgresql.org 3328 : 2 : st->estatus = ESTATUS_META_COMMAND_ERROR;
2443 alvherre@alvh.no-ip. 3329 : 2 : goto error;
3330 : : }
2083 michael@paquier.xyz 3331 [ + + + + ]: 540 : else if (meta == META_ASET && ntuples <= 0)
3332 : : {
3333 : : /* coldly skip empty result under \aset */
3334 : 1 : break;
3335 : : }
3336 : :
3337 : : /* store results into variables */
2532 alvherre@alvh.no-ip. 3338 [ + + ]: 1078 : for (int fld = 0; fld < PQnfields(res); fld++)
3339 : : {
3340 : 541 : char *varname = PQfname(res, fld);
3341 : :
3342 : : /* allocate varname only if necessary, freed below */
2458 3343 [ + + ]: 541 : if (*varprefix != '\0')
3344 : 1 : varname = psprintf("%s%s", varprefix, varname);
3345 : :
3346 : : /* store last row result as a string */
1364 ishii@postgresql.org 3347 [ + + + + ]: 541 : if (!putVariable(&st->variables, meta == META_ASET ? "aset" : "gset", varname,
2083 michael@paquier.xyz 3348 : 541 : PQgetvalue(res, ntuples - 1, fld)))
3349 : : {
3350 : : /* internal error */
2169 peter@eisentraut.org 3351 : 2 : pg_log_error("client %d script %d command %d query %d: error storing into variable %s",
3352 : : st->id, st->use_file, st->command, qrynum, varname);
1364 ishii@postgresql.org 3353 : 2 : st->estatus = ESTATUS_META_COMMAND_ERROR;
2443 alvherre@alvh.no-ip. 3354 : 2 : goto error;
3355 : : }
3356 : :
2458 3357 [ + + ]: 539 : if (*varprefix != '\0')
2532 3358 : 1 : pg_free(varname);
3359 : : }
3360 : : }
3361 : : /* otherwise the result is simply thrown away by PQclear below */
3362 : 2330 : break;
3363 : :
1737 3364 : 54 : case PGRES_PIPELINE_SYNC:
692 michael@paquier.xyz 3365 [ - + ]: 54 : pg_log_debug("client %d pipeline ending, ongoing syncs: %d",
3366 : : st->id, st->num_syncs);
3367 : 54 : st->num_syncs--;
3368 [ + + - + ]: 54 : if (st->num_syncs == 0 && PQexitPipelineMode(st->con) != 1)
1737 alvherre@alvh.no-ip. 3369 :UBC 0 : pg_log_error("client %d failed to exit pipeline mode: %s", st->id,
3370 : : PQresultErrorMessage(res));
1737 alvherre@alvh.no-ip. 3371 :CBC 54 : break;
3372 : :
74 michael@paquier.xyz 3373 : 1 : case PGRES_COPY_IN:
3374 : : case PGRES_COPY_OUT:
3375 : : case PGRES_COPY_BOTH:
3376 : 1 : pg_log_error("COPY is not supported in pgbench, aborting");
3377 : :
3378 : : /*
3379 : : * We need to exit the copy state. Otherwise, PQgetResult()
3380 : : * will always return an empty PGresult as an effect of
3381 : : * getCopyResult(), leading to an infinite loop in the error
3382 : : * cleanup done below.
3383 : : */
3384 : 1 : PQendcopy(st->con);
3385 : 1 : goto error;
3386 : :
1364 ishii@postgresql.org 3387 : 22 : case PGRES_NONFATAL_ERROR:
3388 : : case PGRES_FATAL_ERROR:
39 fujii@postgresql.org 3389 :GNC 22 : st->estatus = getSQLErrorStatus(st, PQresultErrorField(res,
3390 : : PG_DIAG_SQLSTATE));
3391 [ + + + + ]: 22 : if (canRetryError(st->estatus) || canContinueOnError(st->estatus))
3392 : : {
1364 ishii@postgresql.org 3393 [ + + ]:CBC 11 : if (verbose_errors)
77 fujii@postgresql.org 3394 : 2 : commandError(st, PQresultErrorMessage(res));
1364 ishii@postgresql.org 3395 : 11 : goto error;
3396 : : }
3397 : : /* fall through */
3398 : :
3399 : : default:
3400 : : /* anything else is unexpected */
2169 peter@eisentraut.org 3401 : 11 : pg_log_error("client %d script %d aborted in command %d query %d: %s",
3402 : : st->id, st->use_file, st->command, qrynum,
3403 : : PQresultErrorMessage(res));
2443 alvherre@alvh.no-ip. 3404 : 11 : goto error;
3405 : : }
3406 : :
2532 3407 : 10594 : PQclear(res);
3408 : 10594 : qrynum++;
2458 3409 : 10594 : res = next_res;
3410 : : }
3411 : :
2532 3412 [ - + ]: 10588 : if (qrynum == 0)
3413 : : {
2169 peter@eisentraut.org 3414 :UBC 0 : pg_log_error("client %d command %d: no results", st->id, st->command);
2532 alvherre@alvh.no-ip. 3415 : 0 : return false;
3416 : : }
3417 : :
2532 alvherre@alvh.no-ip. 3418 :CBC 10588 : return true;
3419 : :
2443 3420 : 28 : error:
3421 : 28 : PQclear(res);
3422 : 28 : PQclear(next_res);
39 fujii@postgresql.org 3423 :GNC 28 : discardAvailableResults(st);
3424 : :
2443 alvherre@alvh.no-ip. 3425 :CBC 28 : return false;
3426 : : }
3427 : :
3428 : : /*
3429 : : * Parse the argument to a \sleep command, and return the requested amount
3430 : : * of delay, in microseconds. Returns true on success, false on error.
3431 : : */
3432 : : static bool
1364 ishii@postgresql.org 3433 : 6 : evaluateSleep(Variables *variables, int argc, char **argv, int *usecs)
3434 : : {
3435 : : char *var;
3436 : : int usec;
3437 : :
3368 heikki.linnakangas@i 3438 [ + + ]: 6 : if (*argv[1] == ':')
3439 : : {
1364 ishii@postgresql.org 3440 [ + + ]: 3 : if ((var = getVariable(variables, argv[1] + 1)) == NULL)
3441 : : {
2169 peter@eisentraut.org 3442 : 1 : pg_log_error("%s: undefined variable \"%s\"", argv[0], argv[1] + 1);
3368 heikki.linnakangas@i 3443 : 1 : return false;
3444 : : }
3445 : :
3446 : 2 : usec = atoi(var);
3447 : :
3448 : : /* Raise an error if the value of a variable is not a number */
1730 fujii@postgresql.org 3449 [ + + - + ]: 2 : if (usec == 0 && !isdigit((unsigned char) *var))
3450 : : {
1730 fujii@postgresql.org 3451 :UBC 0 : pg_log_error("%s: invalid sleep time \"%s\" for variable \"%s\"",
3452 : : argv[0], var, argv[1] + 1);
3453 : 0 : return false;
3454 : : }
3455 : : }
3456 : : else
3368 heikki.linnakangas@i 3457 :CBC 3 : usec = atoi(argv[1]);
3458 : :
3459 [ + + ]: 5 : if (argc > 2)
3460 : : {
3461 [ + + ]: 4 : if (pg_strcasecmp(argv[2], "ms") == 0)
3462 : 2 : usec *= 1000;
3463 [ + + ]: 2 : else if (pg_strcasecmp(argv[2], "s") == 0)
3464 : 1 : usec *= 1000000;
3465 : : }
3466 : : else
3467 : 1 : usec *= 1000000;
3468 : :
3469 : 5 : *usecs = usec;
3470 : 5 : return true;
3471 : : }
3472 : :
3473 : :
3474 : : /*
3475 : : * Returns true if the error can be retried.
3476 : : */
3477 : : static bool
1364 ishii@postgresql.org 3478 : 11 : doRetry(CState *st, pg_time_usec_t *now)
3479 : : {
3480 [ - + ]: 11 : Assert(st->estatus != ESTATUS_NO_ERROR);
3481 : :
3482 : : /* We can only retry serialization or deadlock errors. */
3483 [ + + ]: 11 : if (!canRetryError(st->estatus))
1364 ishii@postgresql.org 3484 :GBC 9 : return false;
3485 : :
3486 : : /*
3487 : : * We must have at least one option to limit the retrying of transactions
3488 : : * that got an error.
3489 : : */
1364 ishii@postgresql.org 3490 [ - + - - :CBC 2 : Assert(max_tries || latency_limit || duration > 0);
- - ]
3491 : :
3492 : : /*
3493 : : * We cannot retry the error if we have reached the maximum number of
3494 : : * tries.
3495 : : */
3496 [ + - - + ]: 2 : if (max_tries && st->tries >= max_tries)
1364 ishii@postgresql.org 3497 :UBC 0 : return false;
3498 : :
3499 : : /*
3500 : : * We cannot retry the error if we spent too much time on this
3501 : : * transaction.
3502 : : */
1364 ishii@postgresql.org 3503 [ - + ]:CBC 2 : if (latency_limit)
3504 : : {
1364 ishii@postgresql.org 3505 :UBC 0 : pg_time_now_lazy(now);
3506 [ # # ]: 0 : if (*now - st->txn_scheduled > latency_limit)
3507 : 0 : return false;
3508 : : }
3509 : :
3510 : : /*
3511 : : * We cannot retry the error if the benchmark duration is over.
3512 : : */
1364 ishii@postgresql.org 3513 [ - + ]:CBC 2 : if (timer_exceeded)
1364 ishii@postgresql.org 3514 :UBC 0 : return false;
3515 : :
3516 : : /* OK */
1364 ishii@postgresql.org 3517 :CBC 2 : return true;
3518 : : }
3519 : :
3520 : : /*
3521 : : * Read and discard results until the last sync point.
3522 : : */
3523 : : static int
1364 ishii@postgresql.org 3524 :UBC 0 : discardUntilSync(CState *st)
3525 : : {
135 fujii@postgresql.org 3526 : 0 : bool received_sync = false;
3527 : :
3528 : : /*
3529 : : * Send a Sync message to ensure at least one PGRES_PIPELINE_SYNC is
3530 : : * received and to avoid an infinite loop, since all earlier ones may have
3531 : : * already been received.
3532 : : */
1364 ishii@postgresql.org 3533 [ # # ]: 0 : if (!PQpipelineSync(st->con))
3534 : : {
3535 : 0 : pg_log_error("client %d aborted: failed to send a pipeline sync",
3536 : : st->id);
3537 : 0 : return 0;
3538 : : }
3539 : :
3540 : : /*
3541 : : * Continue reading results until the last sync point, i.e., until
3542 : : * reaching null just after PGRES_PIPELINE_SYNC.
3543 : : */
3544 : : for (;;)
3545 : 0 : {
1314 tgl@sss.pgh.pa.us 3546 : 0 : PGresult *res = PQgetResult(st->con);
3547 : :
32 fujii@postgresql.org 3548 [ # # ]: 0 : if (PQstatus(st->con) == CONNECTION_BAD)
3549 : : {
3550 : 0 : pg_log_error("client %d aborted while rolling back the transaction after an error; perhaps the backend died while processing",
3551 : : st->id);
3552 : 0 : PQclear(res);
3553 : 0 : return 0;
3554 : : }
3555 : :
1364 ishii@postgresql.org 3556 [ # # ]: 0 : if (PQresultStatus(res) == PGRES_PIPELINE_SYNC)
135 fujii@postgresql.org 3557 : 0 : received_sync = true;
32 3558 [ # # # # ]: 0 : else if (received_sync && res == NULL)
3559 : : {
3560 : : /*
3561 : : * Reset ongoing sync count to 0 since all PGRES_PIPELINE_SYNC
3562 : : * results have been discarded.
3563 : : */
135 3564 : 0 : st->num_syncs = 0;
1364 ishii@postgresql.org 3565 : 0 : break;
3566 : : }
3567 : : else
3568 : : {
3569 : : /*
3570 : : * If a PGRES_PIPELINE_SYNC is followed by something other than
3571 : : * PGRES_PIPELINE_SYNC or NULL, another PGRES_PIPELINE_SYNC will
3572 : : * appear later. Reset received_sync to false to wait for it.
3573 : : */
32 fujii@postgresql.org 3574 : 0 : received_sync = false;
3575 : : }
1364 ishii@postgresql.org 3576 : 0 : PQclear(res);
3577 : : }
3578 : :
3579 : : /* exit pipeline */
3580 [ # # ]: 0 : if (PQexitPipelineMode(st->con) != 1)
3581 : : {
3582 : 0 : pg_log_error("client %d aborted: failed to exit pipeline mode for rolling back the failed transaction",
3583 : : st->id);
3584 : 0 : return 0;
3585 : : }
3586 : 0 : return 1;
3587 : : }
3588 : :
3589 : : /*
3590 : : * Get the transaction status at the end of a command especially for
3591 : : * checking if we are in a (failed) transaction block.
3592 : : */
3593 : : static TStatus
1364 ishii@postgresql.org 3594 :CBC 7717 : getTransactionStatus(PGconn *con)
3595 : : {
3596 : : PGTransactionStatusType tx_status;
3597 : :
3598 : 7717 : tx_status = PQtransactionStatus(con);
3599 [ + + - - ]: 7717 : switch (tx_status)
3600 : : {
3601 : 7715 : case PQTRANS_IDLE:
3602 : 7715 : return TSTATUS_IDLE;
3603 : 2 : case PQTRANS_INTRANS:
3604 : : case PQTRANS_INERROR:
3605 : 2 : return TSTATUS_IN_BLOCK;
1364 ishii@postgresql.org 3606 :UBC 0 : case PQTRANS_UNKNOWN:
3607 : : /* PQTRANS_UNKNOWN is expected given a broken connection */
3608 [ # # ]: 0 : if (PQstatus(con) == CONNECTION_BAD)
3609 : 0 : return TSTATUS_CONN_ERROR;
3610 : : /* fall through */
3611 : : case PQTRANS_ACTIVE:
3612 : : default:
3613 : :
3614 : : /*
3615 : : * We cannot find out whether we are in a transaction block or
3616 : : * not. Internal error which should never occur.
3617 : : */
3618 : 0 : pg_log_error("unexpected transaction status %d", tx_status);
3619 : 0 : return TSTATUS_OTHER_ERROR;
3620 : : }
3621 : :
3622 : : /* not reached */
3623 : : Assert(false);
3624 : : return TSTATUS_OTHER_ERROR;
3625 : : }
3626 : :
3627 : : /*
3628 : : * Print verbose messages of an error
3629 : : */
3630 : : static void
1364 ishii@postgresql.org 3631 :CBC 2 : printVerboseErrorMessages(CState *st, pg_time_usec_t *now, bool is_retry)
3632 : : {
3633 : : static PQExpBuffer buf = NULL;
3634 : :
3635 [ + - ]: 2 : if (buf == NULL)
3636 : 2 : buf = createPQExpBuffer();
3637 : : else
1364 ishii@postgresql.org 3638 :UBC 0 : resetPQExpBuffer(buf);
3639 : :
1364 ishii@postgresql.org 3640 :CBC 2 : printfPQExpBuffer(buf, "client %d ", st->id);
1197 drowley@postgresql.o 3641 [ + - ]: 2 : appendPQExpBufferStr(buf, (is_retry ?
3642 : : "repeats the transaction after the error" :
3643 : : "ends the failed transaction"));
1336 peter@eisentraut.org 3644 : 2 : appendPQExpBuffer(buf, " (try %u", st->tries);
3645 : :
3646 : : /* Print max_tries if it is not unlimited. */
1364 ishii@postgresql.org 3647 [ + - ]: 2 : if (max_tries)
1336 peter@eisentraut.org 3648 : 2 : appendPQExpBuffer(buf, "/%u", max_tries);
3649 : :
3650 : : /*
3651 : : * If the latency limit is used, print a percentage of the current
3652 : : * transaction latency from the latency limit.
3653 : : */
1364 ishii@postgresql.org 3654 [ - + ]: 2 : if (latency_limit)
3655 : : {
1364 ishii@postgresql.org 3656 :UBC 0 : pg_time_now_lazy(now);
3657 : 0 : appendPQExpBuffer(buf, ", %.3f%% of the maximum time of tries was used",
3658 : 0 : (100.0 * (*now - st->txn_scheduled) / latency_limit));
3659 : : }
1197 drowley@postgresql.o 3660 :CBC 2 : appendPQExpBufferStr(buf, ")\n");
3661 : :
1364 ishii@postgresql.org 3662 : 2 : pg_log_info("%s", buf->data);
3663 : 2 : }
3664 : :
3665 : : /*
3666 : : * Advance the state machine of a connection.
3667 : : */
3668 : : static void
2582 alvherre@alvh.no-ip. 3669 : 17775 : advanceConnectionState(TState *thread, CState *st, StatsData *agg)
3670 : : {
3671 : :
3672 : : /*
3673 : : * gettimeofday() isn't free, so we get the current timestamp lazily the
3674 : : * first time it's needed, and reuse the same value throughout this
3675 : : * function after that. This also ensures that e.g. the calculated
3676 : : * latency reported in the log file and in the totals are the same. Zero
3677 : : * means "not set yet". Reset "now" when we execute shell commands or
3678 : : * expressions, which might take a non-negligible amount of time, though.
3679 : : */
1742 tmunro@postgresql.or 3680 : 17775 : pg_time_usec_t now = 0;
3681 : :
3682 : : /*
3683 : : * Loop in the state machine, until we have to wait for a result from the
3684 : : * server or have to sleep for throttling or \sleep.
3685 : : *
3686 : : * Note: In the switch-statement below, 'break' will loop back here,
3687 : : * meaning "continue in the state machine". Return is used to return to
3688 : : * the caller, giving the thread the opportunity to advance another
3689 : : * client.
3690 : : */
3691 : : for (;;)
3368 heikki.linnakangas@i 3692 : 60769 : {
3693 : : Command *command;
3694 : :
3695 [ + + + + : 78544 : switch (st->state)
+ + + + +
+ + + + +
+ - ]
3696 : : {
3697 : : /* Select transaction (script) to run. */
3698 : 7760 : case CSTATE_CHOOSE_SCRIPT:
3699 : 7760 : st->use_file = chooseScript(thread);
2582 alvherre@alvh.no-ip. 3700 [ - + ]: 7760 : Assert(conditional_stack_empty(st->cstack));
3701 : :
3702 : : /* reset transaction variables to default values */
1364 ishii@postgresql.org 3703 : 7760 : st->estatus = ESTATUS_NO_ERROR;
3704 : 7760 : st->tries = 1;
3705 : :
2169 peter@eisentraut.org 3706 [ + + ]: 7760 : pg_log_debug("client %d executing script \"%s\"",
3707 : : st->id, sql_script[st->use_file].desc);
3708 : :
3709 : : /*
3710 : : * If time is over, we're done; otherwise, get ready to start
3711 : : * a new transaction, or to get throttled if that's requested.
3712 : : */
2582 alvherre@alvh.no-ip. 3713 [ + - ]: 15520 : st->state = timer_exceeded ? CSTATE_FINISHED :
3714 [ + + ]: 7760 : throttle_delay > 0 ? CSTATE_PREPARE_THROTTLE : CSTATE_START_TX;
3715 : 7760 : break;
3716 : :
3717 : : /* Start new transaction (script) */
3718 : 7759 : case CSTATE_START_TX:
1742 tmunro@postgresql.or 3719 : 7759 : pg_time_now_lazy(&now);
3720 : :
3721 : : /* establish connection if needed, i.e. under --connect */
2582 alvherre@alvh.no-ip. 3722 [ + + ]: 7759 : if (st->con == NULL)
3723 : : {
1742 tmunro@postgresql.or 3724 : 110 : pg_time_usec_t start = now;
3725 : :
2582 alvherre@alvh.no-ip. 3726 [ - + ]: 110 : if ((st->con = doConnect()) == NULL)
3727 : : {
3728 : : /*
3729 : : * as the bench is already running, we do not abort
3730 : : * the process
3731 : : */
2169 peter@eisentraut.org 3732 :UBC 0 : pg_log_error("client %d aborted while establishing connection", st->id);
2582 alvherre@alvh.no-ip. 3733 : 0 : st->state = CSTATE_ABORTED;
3734 : 0 : break;
3735 : : }
3736 : :
3737 : : /* reset now after connection */
1742 tmunro@postgresql.or 3738 :CBC 110 : now = pg_time_now();
3739 : :
3740 : 110 : thread->conn_duration += now - start;
3741 : :
3742 : : /* Reset session-local state */
1029 alvherre@alvh.no-ip. 3743 : 110 : pg_free(st->prepared);
3744 : 110 : st->prepared = NULL;
3745 : : }
3746 : :
3747 : : /*
3748 : : * It is the first try to run this transaction. Remember the
3749 : : * random state: maybe it will get an error and we will need
3750 : : * to run it again.
3751 : : */
1364 ishii@postgresql.org 3752 : 7759 : st->random_state = st->cs_func_rs;
3753 : :
3754 : : /* record transaction start time */
2582 alvherre@alvh.no-ip. 3755 : 7759 : st->txn_begin = now;
3756 : :
3757 : : /*
3758 : : * When not throttling, this is also the transaction's
3759 : : * scheduled start time.
3760 : : */
3761 [ + + ]: 7759 : if (!throttle_delay)
1742 tmunro@postgresql.or 3762 : 7558 : st->txn_scheduled = now;
3763 : :
3764 : : /* Begin with the first command */
2582 alvherre@alvh.no-ip. 3765 : 7759 : st->state = CSTATE_START_COMMAND;
3766 : 7759 : st->command = 0;
3368 heikki.linnakangas@i 3767 : 7759 : break;
3768 : :
3769 : : /*
3770 : : * Handle throttling once per transaction by sleeping.
3771 : : */
2582 alvherre@alvh.no-ip. 3772 : 210 : case CSTATE_PREPARE_THROTTLE:
3773 : :
3774 : : /*
3775 : : * Generate a delay such that the series of delays will
3776 : : * approximate a Poisson distribution centered on the
3777 : : * throttle_delay time.
3778 : : *
3779 : : * If transactions are too slow or a given wait is shorter
3780 : : * than a transaction, the next transaction will start right
3781 : : * away.
3782 : : */
3368 heikki.linnakangas@i 3783 [ - + ]: 210 : Assert(throttle_delay > 0);
3784 : :
2582 alvherre@alvh.no-ip. 3785 : 210 : thread->throttle_trigger +=
3786 : 210 : getPoissonRand(&thread->ts_throttle_rs, throttle_delay);
3368 heikki.linnakangas@i 3787 : 210 : st->txn_scheduled = thread->throttle_trigger;
3788 : :
3789 : : /*
3790 : : * If --latency-limit is used, and this slot is already late
3791 : : * so that the transaction will miss the latency limit even if
3792 : : * it completed immediately, skip this time slot and loop to
3793 : : * reschedule.
3794 : : */
3795 [ + - ]: 210 : if (latency_limit)
3796 : : {
1742 tmunro@postgresql.or 3797 : 210 : pg_time_now_lazy(&now);
3798 : :
1558 fujii@postgresql.org 3799 [ + + ]: 210 : if (thread->throttle_trigger < now - latency_limit)
3800 : : {
3368 heikki.linnakangas@i 3801 : 9 : processXactStats(thread, st, &now, true, agg);
3802 : :
3803 : : /*
3804 : : * Finish client if -T or -t was exceeded.
3805 : : *
3806 : : * Stop counting skipped transactions under -T as soon
3807 : : * as the timer is exceeded. Because otherwise it can
3808 : : * take a very long time to count all of them
3809 : : * especially when quite a lot of them happen with
3810 : : * unrealistically high rate setting in -R, which
3811 : : * would prevent pgbench from ending immediately.
3812 : : * Because of this behavior, note that there is no
3813 : : * guarantee that all skipped transactions are counted
3814 : : * under -T though there is under -t. This is OK in
3815 : : * practice because it's very unlikely to happen with
3816 : : * realistic setting.
3817 : : */
1558 fujii@postgresql.org 3818 [ + - + - : 9 : if (timer_exceeded || (nxacts > 0 && st->cnt >= nxacts))
+ + ]
3819 : 1 : st->state = CSTATE_FINISHED;
3820 : :
3821 : : /* Go back to top of loop with CSTATE_PREPARE_THROTTLE */
3025 tgl@sss.pgh.pa.us 3822 : 9 : break;
3823 : : }
3824 : : }
3825 : :
3826 : : /*
3827 : : * stop client if next transaction is beyond pgbench end of
3828 : : * execution; otherwise, throttle it.
3829 : : */
2582 alvherre@alvh.no-ip. 3830 [ # # ]:UBC 0 : st->state = end_time > 0 && st->txn_scheduled > end_time ?
2582 alvherre@alvh.no-ip. 3831 [ - + ]:CBC 201 : CSTATE_FINISHED : CSTATE_THROTTLE;
3368 heikki.linnakangas@i 3832 : 201 : break;
3833 : :
3834 : : /*
3835 : : * Wait until it's time to start next transaction.
3836 : : */
2582 alvherre@alvh.no-ip. 3837 : 201 : case CSTATE_THROTTLE:
1742 tmunro@postgresql.or 3838 : 201 : pg_time_now_lazy(&now);
3839 : :
3840 [ - + ]: 201 : if (now < st->txn_scheduled)
2582 alvherre@alvh.no-ip. 3841 :UBC 0 : return; /* still sleeping, nothing to do here */
3842 : :
3843 : : /* done sleeping, but don't start transaction if we're done */
2582 alvherre@alvh.no-ip. 3844 [ - + ]:CBC 201 : st->state = timer_exceeded ? CSTATE_FINISHED : CSTATE_START_TX;
3368 heikki.linnakangas@i 3845 : 201 : break;
3846 : :
3847 : : /*
3848 : : * Send a command to server (or execute a meta-command)
3849 : : */
3850 : 20696 : case CSTATE_START_COMMAND:
2456 alvherre@alvh.no-ip. 3851 : 20696 : command = sql_script[st->use_file].commands[st->command];
3852 : :
3853 : : /*
3854 : : * Transition to script end processing if done, but close up
3855 : : * shop if a pipeline is open at this point.
3856 : : */
3857 [ + + ]: 20696 : if (command == NULL)
3858 : : {
694 3859 [ + + ]: 7700 : if (PQpipelineStatus(st->con) == PQ_PIPELINE_OFF)
3860 : 7697 : st->state = CSTATE_END_TX;
3861 : : else
3862 : : {
3863 : 3 : pg_log_error("client %d aborted: end of script reached with pipeline open",
3864 : : st->id);
3865 : 3 : st->state = CSTATE_ABORTED;
3866 : : }
3867 : :
3368 heikki.linnakangas@i 3868 : 7700 : break;
3869 : : }
3870 : :
3871 : : /* record begin time of next command, and initiate it */
2582 alvherre@alvh.no-ip. 3872 [ + + ]: 12996 : if (report_per_command)
3873 : : {
1742 tmunro@postgresql.or 3874 : 401 : pg_time_now_lazy(&now);
3368 heikki.linnakangas@i 3875 : 401 : st->stmt_begin = now;
3876 : : }
3877 : :
3878 : : /* Execute the command */
2456 alvherre@alvh.no-ip. 3879 [ + + ]: 12996 : if (command->type == SQL_COMMAND)
3880 : : {
3881 : : /* disallow \aset and \gset in pipeline mode */
1737 3882 [ + + ]: 10572 : if (PQpipelineStatus(st->con) != PQ_PIPELINE_OFF)
3883 : : {
3884 [ + + ]: 524 : if (command->meta == META_GSET)
3885 : : {
3886 : 1 : commandFailed(st, "gset", "\\gset is not allowed in pipeline mode");
3887 : 1 : st->state = CSTATE_ABORTED;
3888 : 1 : break;
3889 : : }
3890 [ - + ]: 523 : else if (command->meta == META_ASET)
3891 : : {
1737 alvherre@alvh.no-ip. 3892 :UBC 0 : commandFailed(st, "aset", "\\aset is not allowed in pipeline mode");
3893 : 0 : st->state = CSTATE_ABORTED;
3894 : 0 : break;
3895 : : }
3896 : : }
3897 : :
2456 alvherre@alvh.no-ip. 3898 [ - + ]:CBC 10571 : if (!sendCommand(st, command))
3899 : : {
2456 alvherre@alvh.no-ip. 3900 :UBC 0 : commandFailed(st, "SQL", "SQL command send failed");
3901 : 0 : st->state = CSTATE_ABORTED;
3902 : : }
3903 : : else
3904 : : {
3905 : : /* Wait for results, unless in pipeline mode */
1737 alvherre@alvh.no-ip. 3906 [ + + ]:CBC 10571 : if (PQpipelineStatus(st->con) == PQ_PIPELINE_OFF)
3907 : 10048 : st->state = CSTATE_WAIT_RESULT;
3908 : : else
3909 : 523 : st->state = CSTATE_END_COMMAND;
3910 : : }
3911 : : }
2456 3912 [ + - ]: 2424 : else if (command->type == META_COMMAND)
3913 : : {
3914 : : /*-----
3915 : : * Possible state changes when executing meta commands:
3916 : : * - on errors CSTATE_ABORTED
3917 : : * - on sleep CSTATE_SLEEP
3918 : : * - else CSTATE_END_COMMAND
3919 : : */
2449 tgl@sss.pgh.pa.us 3920 : 2424 : st->state = executeMetaCommand(st, &now);
1364 ishii@postgresql.org 3921 [ + + ]: 2424 : if (st->state == CSTATE_ABORTED)
3922 : 31 : st->estatus = ESTATUS_META_COMMAND_ERROR;
3923 : : }
3924 : :
3925 : : /*
3926 : : * We're now waiting for an SQL command to complete, or
3927 : : * finished processing a metacommand, or need to sleep, or
3928 : : * something bad happened.
3929 : : */
2582 alvherre@alvh.no-ip. 3930 [ + + + + : 12995 : Assert(st->state == CSTATE_WAIT_RESULT ||
+ + - + ]
3931 : : st->state == CSTATE_END_COMMAND ||
3932 : : st->state == CSTATE_SLEEP ||
3933 : : st->state == CSTATE_ABORTED);
2826 teodor@sigaev.ru 3934 : 12995 : break;
3935 : :
3936 : : /*
3937 : : * non executed conditional branch
3938 : : */
3939 : 490 : case CSTATE_SKIP_COMMAND:
3940 [ - + ]: 490 : Assert(!conditional_active(st->cstack));
3941 : : /* quickly skip commands until something to do... */
3942 : : while (true)
3943 : : {
3944 : 2710 : command = sql_script[st->use_file].commands[st->command];
3945 : :
3946 : : /* cannot reach end of script in that state */
3947 [ - + ]: 2710 : Assert(command != NULL);
3948 : :
3949 : : /*
3950 : : * if this is conditional related, update conditional
3951 : : * state
3952 : : */
3953 [ + + ]: 2710 : if (command->type == META_COMMAND &&
3954 [ + + ]: 520 : (command->meta == META_IF ||
3955 [ + + ]: 517 : command->meta == META_ELIF ||
3956 [ + + ]: 509 : command->meta == META_ELSE ||
3957 [ + + ]: 502 : command->meta == META_ENDIF))
3958 : : {
3959 [ + + - ]: 1006 : switch (conditional_stack_peek(st->cstack))
3960 : : {
2791 tgl@sss.pgh.pa.us 3961 : 488 : case IFSTATE_FALSE:
366 3962 [ + + ]: 488 : if (command->meta == META_IF)
3963 : : {
3964 : : /* nested if in skipped branch - ignore */
3965 : 2 : conditional_stack_push(st->cstack,
3966 : : IFSTATE_IGNORED);
3967 : 2 : st->command++;
3968 : : }
3969 [ + + ]: 486 : else if (command->meta == META_ELIF)
3970 : : {
3971 : : /* we must evaluate the condition */
2791 3972 : 5 : st->state = CSTATE_START_COMMAND;
3973 : : }
3974 [ + + ]: 481 : else if (command->meta == META_ELSE)
3975 : : {
3976 : : /* we must execute next command */
2582 alvherre@alvh.no-ip. 3977 : 3 : conditional_stack_poke(st->cstack,
3978 : : IFSTATE_ELSE_TRUE);
2826 teodor@sigaev.ru 3979 : 3 : st->state = CSTATE_START_COMMAND;
2791 tgl@sss.pgh.pa.us 3980 : 3 : st->command++;
3981 : : }
3982 [ + - ]: 478 : else if (command->meta == META_ENDIF)
3983 : : {
3984 [ - + ]: 478 : Assert(!conditional_stack_empty(st->cstack));
3985 : 478 : conditional_stack_pop(st->cstack);
3986 [ + - ]: 478 : if (conditional_active(st->cstack))
3987 : 478 : st->state = CSTATE_START_COMMAND;
3988 : : /* else state remains CSTATE_SKIP_COMMAND */
3989 : 478 : st->command++;
3990 : : }
3991 : 488 : break;
3992 : :
3993 : 15 : case IFSTATE_IGNORED:
3994 : : case IFSTATE_ELSE_FALSE:
3995 [ + + ]: 15 : if (command->meta == META_IF)
2456 alvherre@alvh.no-ip. 3996 : 1 : conditional_stack_push(st->cstack,
3997 : : IFSTATE_IGNORED);
2791 tgl@sss.pgh.pa.us 3998 [ + + ]: 14 : else if (command->meta == META_ENDIF)
3999 : : {
4000 [ - + ]: 7 : Assert(!conditional_stack_empty(st->cstack));
4001 : 7 : conditional_stack_pop(st->cstack);
4002 [ + + ]: 7 : if (conditional_active(st->cstack))
4003 : 4 : st->state = CSTATE_START_COMMAND;
4004 : : }
4005 : : /* could detect "else" & "elif" after "else" */
2826 teodor@sigaev.ru 4006 : 15 : st->command++;
2791 tgl@sss.pgh.pa.us 4007 : 15 : break;
4008 : :
2791 tgl@sss.pgh.pa.us 4009 :UBC 0 : case IFSTATE_NONE:
4010 : : case IFSTATE_TRUE:
4011 : : case IFSTATE_ELSE_TRUE:
4012 : : default:
4013 : :
4014 : : /*
4015 : : * inconsistent if inactive, unreachable dead
4016 : : * code
4017 : : */
4018 : 0 : Assert(false);
4019 : : }
4020 : : }
4021 : : else
4022 : : {
4023 : : /* skip and consider next */
2826 teodor@sigaev.ru 4024 :CBC 2207 : st->command++;
4025 : : }
4026 : :
4027 [ + + ]: 2710 : if (st->state != CSTATE_SKIP_COMMAND)
4028 : : /* out of quick skip command loop */
4029 : 490 : break;
4030 : : }
3368 heikki.linnakangas@i 4031 : 490 : break;
4032 : :
4033 : : /*
4034 : : * Wait for the current SQL command to complete
4035 : : */
4036 : 20628 : case CSTATE_WAIT_RESULT:
2169 peter@eisentraut.org 4037 [ + + ]: 20628 : pg_log_debug("client %d receiving", st->id);
4038 : :
4039 : : /*
4040 : : * Only check for new network data if we processed all data
4041 : : * fetched prior. Otherwise we end up doing a syscall for each
4042 : : * individual pipelined query, which has a measurable
4043 : : * performance impact.
4044 : : */
1595 andres@anarazel.de 4045 [ + + - + ]: 20628 : if (PQisBusy(st->con) && !PQconsumeInput(st->con))
4046 : : {
4047 : : /* there's something wrong */
2826 teodor@sigaev.ru 4048 :UBC 0 : commandFailed(st, "SQL", "perhaps the backend died while processing");
3368 heikki.linnakangas@i 4049 : 0 : st->state = CSTATE_ABORTED;
4050 : 0 : break;
4051 : : }
3368 heikki.linnakangas@i 4052 [ + + ]:CBC 20628 : if (PQisBusy(st->con))
4053 : 10012 : return; /* don't have the whole result yet */
4054 : :
4055 : : /* store or discard the query results */
2083 michael@paquier.xyz 4056 [ + + ]: 10616 : if (readCommandResponse(st,
4057 : 10616 : sql_script[st->use_file].commands[st->command]->meta,
4058 : 10616 : sql_script[st->use_file].commands[st->command]->varprefix))
4059 : : {
4060 : : /*
4061 : : * outside of pipeline mode: stop reading results.
4062 : : * pipeline mode: continue reading results until an
4063 : : * end-of-pipeline response.
4064 : : */
1737 alvherre@alvh.no-ip. 4065 [ + + ]: 10588 : if (PQpipelineStatus(st->con) != PQ_PIPELINE_ON)
4066 : 10073 : st->state = CSTATE_END_COMMAND;
4067 : : }
39 fujii@postgresql.org 4068 [ + + + + ]:GNC 28 : else if (canRetryError(st->estatus) || canContinueOnError(st->estatus))
1364 ishii@postgresql.org 4069 :CBC 11 : st->state = CSTATE_ERROR;
4070 : : else
2532 alvherre@alvh.no-ip. 4071 : 17 : st->state = CSTATE_ABORTED;
3368 heikki.linnakangas@i 4072 : 10616 : break;
4073 : :
4074 : : /*
4075 : : * Wait until sleep is done. This state is entered after a
4076 : : * \sleep metacommand. The behavior is similar to
4077 : : * CSTATE_THROTTLE, but proceeds to CSTATE_START_COMMAND
4078 : : * instead of CSTATE_START_TX.
4079 : : */
4080 : 8 : case CSTATE_SLEEP:
1742 tmunro@postgresql.or 4081 : 8 : pg_time_now_lazy(&now);
4082 [ + + ]: 8 : if (now < st->sleep_until)
2582 alvherre@alvh.no-ip. 4083 : 3 : return; /* still sleeping, nothing to do here */
4084 : : /* Else done sleeping. */
3368 heikki.linnakangas@i 4085 : 5 : st->state = CSTATE_END_COMMAND;
4086 : 5 : break;
4087 : :
4088 : : /*
4089 : : * End of command: record stats and proceed to next command.
4090 : : */
4091 : 12935 : case CSTATE_END_COMMAND:
4092 : :
4093 : : /*
4094 : : * command completed: accumulate per-command execution times
4095 : : * in thread-local data structure, if per-command latencies
4096 : : * are requested.
4097 : : */
2582 alvherre@alvh.no-ip. 4098 [ + + ]: 12935 : if (report_per_command)
4099 : : {
1742 tmunro@postgresql.or 4100 : 401 : pg_time_now_lazy(&now);
4101 : :
2532 alvherre@alvh.no-ip. 4102 : 401 : command = sql_script[st->use_file].commands[st->command];
4103 : : /* XXX could use a mutex here, but we choose not to */
3368 heikki.linnakangas@i 4104 : 401 : addToSimpleStats(&command->stats,
1742 tmunro@postgresql.or 4105 : 401 : PG_TIME_GET_DOUBLE(now - st->stmt_begin));
4106 : : }
4107 : :
4108 : : /* Go ahead with next command, to be executed or skipped */
3368 heikki.linnakangas@i 4109 : 12935 : st->command++;
2826 teodor@sigaev.ru 4110 : 12935 : st->state = conditional_active(st->cstack) ?
4111 [ + + ]: 12935 : CSTATE_START_COMMAND : CSTATE_SKIP_COMMAND;
3368 heikki.linnakangas@i 4112 : 12935 : break;
4113 : :
4114 : : /*
4115 : : * Clean up after an error.
4116 : : */
1364 ishii@postgresql.org 4117 : 11 : case CSTATE_ERROR:
4118 : : {
4119 : : TStatus tstatus;
4120 : :
4121 [ - + ]: 11 : Assert(st->estatus != ESTATUS_NO_ERROR);
4122 : :
4123 : : /* Clear the conditional stack */
4124 : 11 : conditional_stack_reset(st->cstack);
4125 : :
4126 : : /* Read and discard until a sync point in pipeline mode */
4127 [ - + ]: 11 : if (PQpipelineStatus(st->con) != PQ_PIPELINE_OFF)
4128 : : {
1364 ishii@postgresql.org 4129 [ # # ]:UBC 0 : if (!discardUntilSync(st))
4130 : : {
4131 : 0 : st->state = CSTATE_ABORTED;
4132 : 0 : break;
4133 : : }
4134 : : }
4135 : :
4136 : : /*
4137 : : * Check if we have a (failed) transaction block or not,
4138 : : * and roll it back if any.
4139 : : */
1364 ishii@postgresql.org 4140 :CBC 11 : tstatus = getTransactionStatus(st->con);
4141 [ + + ]: 11 : if (tstatus == TSTATUS_IN_BLOCK)
4142 : : {
4143 : : /* Try to rollback a (failed) transaction block. */
4144 [ - + ]: 1 : if (!PQsendQuery(st->con, "ROLLBACK"))
4145 : : {
1364 ishii@postgresql.org 4146 :UBC 0 : pg_log_error("client %d aborted: failed to send sql command for rolling back the failed transaction",
4147 : : st->id);
4148 : 0 : st->state = CSTATE_ABORTED;
4149 : : }
4150 : : else
1364 ishii@postgresql.org 4151 :CBC 1 : st->state = CSTATE_WAIT_ROLLBACK_RESULT;
4152 : : }
4153 [ + - ]: 10 : else if (tstatus == TSTATUS_IDLE)
4154 : : {
4155 : : /*
4156 : : * If time is over, we're done; otherwise, check if we
4157 : : * can retry the error.
4158 : : */
4159 [ + - + + ]: 20 : st->state = timer_exceeded ? CSTATE_FINISHED :
4160 : 10 : doRetry(st, &now) ? CSTATE_RETRY : CSTATE_FAILURE;
4161 : : }
4162 : : else
4163 : : {
1364 ishii@postgresql.org 4164 [ # # ]:UBC 0 : if (tstatus == TSTATUS_CONN_ERROR)
4165 : 0 : pg_log_error("perhaps the backend died while processing");
4166 : :
4167 : 0 : pg_log_error("client %d aborted while receiving the transaction status", st->id);
4168 : 0 : st->state = CSTATE_ABORTED;
4169 : : }
1364 ishii@postgresql.org 4170 :CBC 11 : break;
4171 : : }
4172 : :
4173 : : /*
4174 : : * Wait for the rollback command to complete
4175 : : */
4176 : 2 : case CSTATE_WAIT_ROLLBACK_RESULT:
4177 : : {
4178 : : PGresult *res;
4179 : :
4180 [ + - ]: 2 : pg_log_debug("client %d receiving", st->id);
4181 [ - + ]: 2 : if (!PQconsumeInput(st->con))
4182 : : {
1364 ishii@postgresql.org 4183 :UBC 0 : pg_log_error("client %d aborted while rolling back the transaction after an error; perhaps the backend died while processing",
4184 : : st->id);
4185 : 0 : st->state = CSTATE_ABORTED;
4186 : 0 : break;
4187 : : }
1364 ishii@postgresql.org 4188 [ + + ]:CBC 2 : if (PQisBusy(st->con))
1314 tgl@sss.pgh.pa.us 4189 : 1 : return; /* don't have the whole result yet */
4190 : :
4191 : : /*
4192 : : * Read and discard the query result;
4193 : : */
1364 ishii@postgresql.org 4194 : 1 : res = PQgetResult(st->con);
4195 [ + - ]: 1 : switch (PQresultStatus(res))
4196 : : {
4197 : 1 : case PGRES_COMMAND_OK:
4198 : : /* OK */
4199 : 1 : PQclear(res);
4200 : : /* null must be returned */
4201 : 1 : res = PQgetResult(st->con);
4202 [ - + ]: 1 : Assert(res == NULL);
4203 : :
4204 : : /*
4205 : : * If time is over, we're done; otherwise, check
4206 : : * if we can retry the error.
4207 : : */
4208 [ + - + - ]: 2 : st->state = timer_exceeded ? CSTATE_FINISHED :
4209 : 1 : doRetry(st, &now) ? CSTATE_RETRY : CSTATE_FAILURE;
4210 : 1 : break;
1364 ishii@postgresql.org 4211 :UBC 0 : default:
4212 : 0 : pg_log_error("client %d aborted while rolling back the transaction after an error; %s",
4213 : : st->id, PQerrorMessage(st->con));
4214 : 0 : PQclear(res);
4215 : 0 : st->state = CSTATE_ABORTED;
4216 : 0 : break;
4217 : : }
3368 heikki.linnakangas@i 4218 :CBC 1 : break;
4219 : : }
4220 : :
4221 : : /*
4222 : : * Retry the transaction after an error.
4223 : : */
1364 ishii@postgresql.org 4224 : 2 : case CSTATE_RETRY:
4225 : 2 : command = sql_script[st->use_file].commands[st->command];
4226 : :
4227 : : /*
4228 : : * Inform that the transaction will be retried after the
4229 : : * error.
4230 : : */
4231 [ + - ]: 2 : if (verbose_errors)
4232 : 2 : printVerboseErrorMessages(st, &now, true);
4233 : :
4234 : : /* Count tries and retries */
4235 : 2 : st->tries++;
4236 : 2 : command->retries++;
4237 : :
4238 : : /*
4239 : : * Reset the random state as they were at the beginning of the
4240 : : * transaction.
4241 : : */
4242 : 2 : st->cs_func_rs = st->random_state;
4243 : :
4244 : : /* Process the first transaction command. */
4245 : 2 : st->command = 0;
4246 : 2 : st->estatus = ESTATUS_NO_ERROR;
4247 : 2 : st->state = CSTATE_START_COMMAND;
4248 : 2 : break;
4249 : :
4250 : : /*
4251 : : * Record a failed transaction.
4252 : : */
1364 ishii@postgresql.org 4253 :GBC 9 : case CSTATE_FAILURE:
4254 : 9 : command = sql_script[st->use_file].commands[st->command];
4255 : :
4256 : : /* Accumulate the failure. */
4257 : 9 : command->failures++;
4258 : :
4259 : : /*
4260 : : * Inform that the failed transaction will not be retried.
4261 : : */
4262 [ - + ]: 9 : if (verbose_errors)
1364 ishii@postgresql.org 4263 :UBC 0 : printVerboseErrorMessages(st, &now, false);
4264 : :
4265 : : /* End the failed transaction. */
1364 ishii@postgresql.org 4266 :GBC 9 : st->state = CSTATE_END_TX;
4267 : 9 : break;
4268 : :
4269 : : /*
4270 : : * End of transaction (end of script, really).
4271 : : */
1364 ishii@postgresql.org 4272 :CBC 7706 : case CSTATE_END_TX:
4273 : : {
4274 : : TStatus tstatus;
4275 : :
4276 : : /* transaction finished: calculate latency and do log */
4277 : 7706 : processXactStats(thread, st, &now, false, agg);
4278 : :
4279 : : /*
4280 : : * missing \endif... cannot happen if CheckConditional was
4281 : : * okay
4282 : : */
4283 [ - + ]: 7706 : Assert(conditional_stack_empty(st->cstack));
4284 : :
4285 : : /*
4286 : : * We must complete all the transaction blocks that were
4287 : : * started in this script.
4288 : : */
4289 : 7706 : tstatus = getTransactionStatus(st->con);
4290 [ + + ]: 7706 : if (tstatus == TSTATUS_IN_BLOCK)
4291 : : {
4292 : 1 : pg_log_error("client %d aborted: end of script reached without completing the last transaction",
4293 : : st->id);
4294 : 1 : st->state = CSTATE_ABORTED;
4295 : 1 : break;
4296 : : }
4297 [ - + ]: 7705 : else if (tstatus != TSTATUS_IDLE)
4298 : : {
1364 ishii@postgresql.org 4299 [ # # ]:UBC 0 : if (tstatus == TSTATUS_CONN_ERROR)
4300 : 0 : pg_log_error("perhaps the backend died while processing");
4301 : :
4302 : 0 : pg_log_error("client %d aborted while receiving the transaction status", st->id);
4303 : 0 : st->state = CSTATE_ABORTED;
4304 : 0 : break;
4305 : : }
4306 : :
1364 ishii@postgresql.org 4307 [ + + ]:CBC 7705 : if (is_connect)
4308 : : {
4309 : 110 : pg_time_usec_t start = now;
4310 : :
4311 : 110 : pg_time_now_lazy(&start);
4312 : 110 : finishCon(st);
4313 : 110 : now = pg_time_now();
4314 : 110 : thread->conn_duration += now - start;
4315 : : }
4316 : :
4317 [ + + - + : 7705 : if ((st->cnt >= nxacts && duration <= 0) || timer_exceeded)
- + ]
4318 : : {
4319 : : /* script completed */
4320 : 73 : st->state = CSTATE_FINISHED;
4321 : 73 : break;
4322 : : }
4323 : :
4324 : : /* next transaction (script) */
4325 : 7632 : st->state = CSTATE_CHOOSE_SCRIPT;
4326 : :
4327 : : /*
4328 : : * Ensure that we always return on this point, so as to
4329 : : * avoid an infinite loop if the script only contains meta
4330 : : * commands.
4331 : : */
4332 : 7632 : return;
4333 : : }
4334 : :
4335 : : /*
4336 : : * Final states. Close the connection if it's still open.
4337 : : */
3368 heikki.linnakangas@i 4338 : 127 : case CSTATE_ABORTED:
4339 : : case CSTATE_FINISHED:
4340 : :
4341 : : /*
4342 : : * Don't measure the disconnection delays here even if in
4343 : : * CSTATE_FINISHED and -C/--connect option is specified.
4344 : : * Because in this case all the connections that this thread
4345 : : * established are closed at the end of transactions and the
4346 : : * disconnection delays should have already been measured at
4347 : : * that moment.
4348 : : *
4349 : : * In CSTATE_ABORTED state, the measurement is no longer
4350 : : * necessary because we cannot report complete results anyways
4351 : : * in this case.
4352 : : */
2847 andres@anarazel.de 4353 : 127 : finishCon(st);
3368 heikki.linnakangas@i 4354 : 127 : return;
4355 : : }
4356 : : }
4357 : : }
4358 : :
4359 : : /*
4360 : : * Subroutine for advanceConnectionState -- initiate or execute the current
4361 : : * meta command, and return the next state to set.
4362 : : *
4363 : : * *now is updated to the current time, unless the command is expected to
4364 : : * take no time to execute.
4365 : : */
4366 : : static ConnectionStateEnum
1742 tmunro@postgresql.or 4367 : 2424 : executeMetaCommand(CState *st, pg_time_usec_t *now)
4368 : : {
2582 alvherre@alvh.no-ip. 4369 : 2424 : Command *command = sql_script[st->use_file].commands[st->command];
4370 : : int argc;
4371 : : char **argv;
4372 : :
2456 4373 [ + - + - ]: 2424 : Assert(command != NULL && command->type == META_COMMAND);
4374 : :
4375 : 2424 : argc = command->argc;
4376 : 2424 : argv = command->argv;
4377 : :
2169 peter@eisentraut.org 4378 [ + + ]: 2424 : if (unlikely(__pg_log_level <= PG_LOG_DEBUG))
4379 : : {
4380 : : PQExpBufferData buf;
4381 : :
2167 michael@paquier.xyz 4382 : 703 : initPQExpBuffer(&buf);
4383 : :
4384 : 703 : printfPQExpBuffer(&buf, "client %d executing \\%s", st->id, argv[0]);
2456 alvherre@alvh.no-ip. 4385 [ + + ]: 1406 : for (int i = 1; i < argc; i++)
2167 michael@paquier.xyz 4386 : 703 : appendPQExpBuffer(&buf, " %s", argv[i]);
4387 : :
4388 [ + - ]: 703 : pg_log_debug("%s", buf.data);
4389 : :
4390 : 703 : termPQExpBuffer(&buf);
4391 : : }
4392 : :
2456 alvherre@alvh.no-ip. 4393 [ + + ]: 2424 : if (command->meta == META_SLEEP)
4394 : : {
4395 : : int usec;
4396 : :
4397 : : /*
4398 : : * A \sleep doesn't execute anything, we just get the delay from the
4399 : : * argument, and enter the CSTATE_SLEEP state. (The per-command
4400 : : * latency will be recorded in CSTATE_SLEEP state, not here, after the
4401 : : * delay has elapsed.)
4402 : : */
1364 ishii@postgresql.org 4403 [ + + ]: 6 : if (!evaluateSleep(&st->variables, argc, argv, &usec))
4404 : : {
2456 alvherre@alvh.no-ip. 4405 : 1 : commandFailed(st, "sleep", "execution of meta-command failed");
4406 : 1 : return CSTATE_ABORTED;
4407 : : }
4408 : :
1742 tmunro@postgresql.or 4409 : 5 : pg_time_now_lazy(now);
4410 : 5 : st->sleep_until = (*now) + usec;
2456 alvherre@alvh.no-ip. 4411 : 5 : return CSTATE_SLEEP;
4412 : : }
4413 [ + + ]: 2418 : else if (command->meta == META_SET)
4414 : : {
4415 : 1712 : PgBenchExpr *expr = command->expr;
4416 : : PgBenchValue result;
4417 : :
2449 tgl@sss.pgh.pa.us 4418 [ + + ]: 1712 : if (!evaluateExpr(st, expr, &result))
4419 : : {
2456 alvherre@alvh.no-ip. 4420 : 23 : commandFailed(st, argv[0], "evaluation of meta-command failed");
4421 : 24 : return CSTATE_ABORTED;
4422 : : }
4423 : :
1364 ishii@postgresql.org 4424 [ + + ]: 1689 : if (!putVariableValue(&st->variables, argv[0], argv[1], &result))
4425 : : {
2456 alvherre@alvh.no-ip. 4426 : 1 : commandFailed(st, "set", "assignment of meta-command failed");
4427 : 1 : return CSTATE_ABORTED;
4428 : : }
4429 : : }
4430 [ + + ]: 706 : else if (command->meta == META_IF)
4431 : : {
4432 : : /* backslash commands with an expression to evaluate */
4433 : 527 : PgBenchExpr *expr = command->expr;
4434 : : PgBenchValue result;
4435 : : bool cond;
4436 : :
2449 tgl@sss.pgh.pa.us 4437 [ - + ]: 527 : if (!evaluateExpr(st, expr, &result))
4438 : : {
2456 alvherre@alvh.no-ip. 4439 :UBC 0 : commandFailed(st, argv[0], "evaluation of meta-command failed");
4440 : 0 : return CSTATE_ABORTED;
4441 : : }
4442 : :
2456 alvherre@alvh.no-ip. 4443 :CBC 527 : cond = valueTruth(&result);
4444 [ + + ]: 527 : conditional_stack_push(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
4445 : : }
4446 [ + + ]: 179 : else if (command->meta == META_ELIF)
4447 : : {
4448 : : /* backslash commands with an expression to evaluate */
4449 : 7 : PgBenchExpr *expr = command->expr;
4450 : : PgBenchValue result;
4451 : : bool cond;
4452 : :
4453 [ + + ]: 7 : if (conditional_stack_peek(st->cstack) == IFSTATE_TRUE)
4454 : : {
4455 : : /* elif after executed block, skip eval and wait for endif. */
4456 : 2 : conditional_stack_poke(st->cstack, IFSTATE_IGNORED);
4457 : 2 : return CSTATE_END_COMMAND;
4458 : : }
4459 : :
2449 tgl@sss.pgh.pa.us 4460 [ - + ]: 5 : if (!evaluateExpr(st, expr, &result))
4461 : : {
2456 alvherre@alvh.no-ip. 4462 :UBC 0 : commandFailed(st, argv[0], "evaluation of meta-command failed");
4463 : 0 : return CSTATE_ABORTED;
4464 : : }
4465 : :
2456 alvherre@alvh.no-ip. 4466 :CBC 5 : cond = valueTruth(&result);
4467 [ - + ]: 5 : Assert(conditional_stack_peek(st->cstack) == IFSTATE_FALSE);
4468 [ + + ]: 5 : conditional_stack_poke(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
4469 : : }
4470 [ + + ]: 172 : else if (command->meta == META_ELSE)
4471 : : {
4472 [ + - ]: 2 : switch (conditional_stack_peek(st->cstack))
4473 : : {
4474 : 2 : case IFSTATE_TRUE:
4475 : 2 : conditional_stack_poke(st->cstack, IFSTATE_ELSE_FALSE);
4476 : 2 : break;
2456 alvherre@alvh.no-ip. 4477 :UBC 0 : case IFSTATE_FALSE: /* inconsistent if active */
4478 : : case IFSTATE_IGNORED: /* inconsistent if active */
4479 : : case IFSTATE_NONE: /* else without if */
4480 : : case IFSTATE_ELSE_TRUE: /* else after else */
4481 : : case IFSTATE_ELSE_FALSE: /* else after else */
4482 : : default:
4483 : : /* dead code if conditional check is ok */
4484 : 0 : Assert(false);
4485 : : }
4486 : : }
2456 alvherre@alvh.no-ip. 4487 [ + + ]:CBC 170 : else if (command->meta == META_ENDIF)
4488 : : {
4489 [ - + ]: 44 : Assert(!conditional_stack_empty(st->cstack));
4490 : 44 : conditional_stack_pop(st->cstack);
4491 : : }
4492 [ + + ]: 126 : else if (command->meta == META_SETSHELL)
4493 : : {
1364 ishii@postgresql.org 4494 [ + + ]: 3 : if (!runShellCommand(&st->variables, argv[1], argv + 2, argc - 2))
4495 : : {
2456 alvherre@alvh.no-ip. 4496 : 2 : commandFailed(st, "setshell", "execution of meta-command failed");
4497 : 2 : return CSTATE_ABORTED;
4498 : : }
4499 : : }
4500 [ + + ]: 123 : else if (command->meta == META_SHELL)
4501 : : {
1364 ishii@postgresql.org 4502 [ + + ]: 3 : if (!runShellCommand(&st->variables, NULL, argv + 1, argc - 1))
4503 : : {
2456 alvherre@alvh.no-ip. 4504 : 2 : commandFailed(st, "shell", "execution of meta-command failed");
4505 : 2 : return CSTATE_ABORTED;
4506 : : }
4507 : : }
1737 4508 [ + + ]: 120 : else if (command->meta == META_STARTPIPELINE)
4509 : : {
4510 : : /*
4511 : : * In pipeline mode, we use a workflow based on libpq pipeline
4512 : : * functions.
4513 : : */
4514 [ - + ]: 60 : if (querymode == QUERY_SIMPLE)
4515 : : {
1737 alvherre@alvh.no-ip. 4516 :UBC 0 : commandFailed(st, "startpipeline", "cannot use pipeline mode with the simple query protocol");
4517 : 0 : return CSTATE_ABORTED;
4518 : : }
4519 : :
4520 : : /*
4521 : : * If we're in prepared-query mode, we need to prepare all the
4522 : : * commands that are inside the pipeline before we actually start the
4523 : : * pipeline itself. This solves the problem that running BEGIN
4524 : : * ISOLATION LEVEL SERIALIZABLE in a pipeline would fail due to a
4525 : : * snapshot having been acquired by the prepare within the pipeline.
4526 : : */
1029 alvherre@alvh.no-ip. 4527 [ + + ]:CBC 60 : if (querymode == QUERY_PREPARED)
4528 : 42 : prepareCommandsInPipeline(st);
4529 : :
1737 4530 [ + + ]: 60 : if (PQpipelineStatus(st->con) != PQ_PIPELINE_OFF)
4531 : : {
4532 : 1 : commandFailed(st, "startpipeline", "already in pipeline mode");
4533 : 1 : return CSTATE_ABORTED;
4534 : : }
4535 [ - + ]: 59 : if (PQenterPipelineMode(st->con) == 0)
4536 : : {
1737 alvherre@alvh.no-ip. 4537 :UBC 0 : commandFailed(st, "startpipeline", "failed to enter pipeline mode");
4538 : 0 : return CSTATE_ABORTED;
4539 : : }
4540 : : }
692 michael@paquier.xyz 4541 [ + + ]:CBC 60 : else if (command->meta == META_SYNCPIPELINE)
4542 : : {
4543 [ - + ]: 5 : if (PQpipelineStatus(st->con) != PQ_PIPELINE_ON)
4544 : : {
692 michael@paquier.xyz 4545 :UBC 0 : commandFailed(st, "syncpipeline", "not in pipeline mode");
4546 : 0 : return CSTATE_ABORTED;
4547 : : }
692 michael@paquier.xyz 4548 [ - + ]:CBC 5 : if (PQsendPipelineSync(st->con) == 0)
4549 : : {
692 michael@paquier.xyz 4550 :UBC 0 : commandFailed(st, "syncpipeline", "failed to send a pipeline sync");
4551 : 0 : return CSTATE_ABORTED;
4552 : : }
692 michael@paquier.xyz 4553 :CBC 5 : st->num_syncs++;
4554 : : }
1737 alvherre@alvh.no-ip. 4555 [ + - ]: 55 : else if (command->meta == META_ENDPIPELINE)
4556 : : {
4557 [ + + ]: 55 : if (PQpipelineStatus(st->con) != PQ_PIPELINE_ON)
4558 : : {
4559 : 1 : commandFailed(st, "endpipeline", "not in pipeline mode");
4560 : 1 : return CSTATE_ABORTED;
4561 : : }
4562 [ - + ]: 54 : if (!PQpipelineSync(st->con))
4563 : : {
1737 alvherre@alvh.no-ip. 4564 :UBC 0 : commandFailed(st, "endpipeline", "failed to send a pipeline sync");
4565 : 0 : return CSTATE_ABORTED;
4566 : : }
692 michael@paquier.xyz 4567 :CBC 54 : st->num_syncs++;
4568 : : /* Now wait for the PGRES_PIPELINE_SYNC and exit pipeline mode there */
4569 : : /* collect pending results before getting out of pipeline mode */
1737 alvherre@alvh.no-ip. 4570 : 54 : return CSTATE_WAIT_RESULT;
4571 : : }
4572 : :
4573 : : /*
4574 : : * executing the expression or shell command might have taken a
4575 : : * non-negligible amount of time, so reset 'now'
4576 : : */
1742 tmunro@postgresql.or 4577 : 2332 : *now = 0;
4578 : :
2456 alvherre@alvh.no-ip. 4579 : 2332 : return CSTATE_END_COMMAND;
4580 : : }
4581 : :
4582 : : /*
4583 : : * Return the number of failed transactions.
4584 : : */
4585 : : static int64
1364 ishii@postgresql.org 4586 : 100 : getFailures(const StatsData *stats)
4587 : : {
4588 : 100 : return (stats->serialization_failures +
39 fujii@postgresql.org 4589 :GNC 200 : stats->deadlock_failures +
4590 : 100 : stats->other_sql_failures);
4591 : : }
4592 : :
4593 : : /*
4594 : : * Return a string constant representing the result of a transaction
4595 : : * that is not successfully processed.
4596 : : */
4597 : : static const char *
1364 ishii@postgresql.org 4598 :UBC 0 : getResultString(bool skipped, EStatus estatus)
4599 : : {
4600 [ # # ]: 0 : if (skipped)
4601 : 0 : return "skipped";
4602 [ # # ]: 0 : else if (failures_detailed)
4603 : : {
4604 [ # # # # ]: 0 : switch (estatus)
4605 : : {
4606 : 0 : case ESTATUS_SERIALIZATION_ERROR:
4607 : 0 : return "serialization";
4608 : 0 : case ESTATUS_DEADLOCK_ERROR:
4609 : 0 : return "deadlock";
39 fujii@postgresql.org 4610 :UNC 0 : case ESTATUS_OTHER_SQL_ERROR:
4611 : 0 : return "other";
1364 ishii@postgresql.org 4612 :UBC 0 : default:
4613 : : /* internal error which should never occur */
1348 tgl@sss.pgh.pa.us 4614 : 0 : pg_fatal("unexpected error status: %d", estatus);
4615 : : }
4616 : : }
4617 : : else
1364 ishii@postgresql.org 4618 : 0 : return "failed";
4619 : : }
4620 : :
4621 : : /*
4622 : : * Print log entry after completing one transaction.
4623 : : *
4624 : : * We print Unix-epoch timestamps in the log, so that entries can be
4625 : : * correlated against other logs.
4626 : : *
4627 : : * XXX We could obtain the time from the caller and just shift it here, to
4628 : : * avoid the cost of an extra call to pg_time_now().
4629 : : */
4630 : : static void
3270 tgl@sss.pgh.pa.us 4631 :CBC 110 : doLog(TState *thread, CState *st,
4632 : : StatsData *agg, bool skipped, double latency, double lag)
4633 : : {
3477 rhaas@postgresql.org 4634 : 110 : FILE *logfile = thread->logfile;
1619 tmunro@postgresql.or 4635 : 110 : pg_time_usec_t now = pg_time_now() + epoch_shift;
4636 : :
3595 alvherre@alvh.no-ip. 4637 [ - + ]: 110 : Assert(use_log);
4638 : :
4639 : : /*
4640 : : * Skip the log entry if sampling is enabled and this row doesn't belong
4641 : : * to the random sample.
4642 : : */
4093 heikki.linnakangas@i 4643 [ + + ]: 110 : if (sample_rate != 0.0 &&
1479 tgl@sss.pgh.pa.us 4644 [ + + ]: 100 : pg_prng_double(&thread->ts_sample_rs) > sample_rate)
4093 heikki.linnakangas@i 4645 : 47 : return;
4646 : :
4647 : : /* should we aggregate the results or not? */
4648 [ - + ]: 63 : if (agg_interval > 0)
4649 : : {
4650 : : pg_time_usec_t next;
4651 : :
4652 : : /*
4653 : : * Loop until we reach the interval of the current moment, and print
4654 : : * any empty intervals in between (this may happen with very low tps,
4655 : : * e.g. --rate=0.1).
4656 : : */
4657 : :
1619 tmunro@postgresql.or 4658 [ # # ]:UBC 0 : while ((next = agg->start_time + agg_interval * INT64CONST(1000000)) <= now)
4659 : : {
1350 ishii@postgresql.org 4660 : 0 : double lag_sum = 0.0;
4661 : 0 : double lag_sum2 = 0.0;
4662 : 0 : double lag_min = 0.0;
4663 : 0 : double lag_max = 0.0;
4664 : 0 : int64 skipped = 0;
4665 : 0 : int64 serialization_failures = 0;
4666 : 0 : int64 deadlock_failures = 0;
39 fujii@postgresql.org 4667 :UNC 0 : int64 other_sql_failures = 0;
1350 ishii@postgresql.org 4668 :UBC 0 : int64 retried = 0;
4669 : 0 : int64 retries = 0;
4670 : :
4671 : : /* print aggregated report to logfile */
1742 tmunro@postgresql.or 4672 : 0 : fprintf(logfile, INT64_FORMAT " " INT64_FORMAT " %.0f %.0f %.0f %.0f",
1619 4673 : 0 : agg->start_time / 1000000, /* seconds since Unix epoch */
4674 : : agg->cnt,
4675 : : agg->latency.sum,
4676 : : agg->latency.sum2,
4677 : : agg->latency.min,
4678 : : agg->latency.max);
4679 : :
3609 alvherre@alvh.no-ip. 4680 [ # # ]: 0 : if (throttle_delay)
4681 : : {
1350 ishii@postgresql.org 4682 : 0 : lag_sum = agg->lag.sum;
4683 : 0 : lag_sum2 = agg->lag.sum2;
4684 : 0 : lag_min = agg->lag.min;
4685 : 0 : lag_max = agg->lag.max;
4686 : : }
4687 : 0 : fprintf(logfile, " %.0f %.0f %.0f %.0f",
4688 : : lag_sum,
4689 : : lag_sum2,
4690 : : lag_min,
4691 : : lag_max);
4692 : :
4693 [ # # ]: 0 : if (latency_limit)
4694 : 0 : skipped = agg->skipped;
4695 : 0 : fprintf(logfile, " " INT64_FORMAT, skipped);
4696 : :
1364 4697 [ # # ]: 0 : if (max_tries != 1)
4698 : : {
1350 4699 : 0 : retried = agg->retried;
4700 : 0 : retries = agg->retries;
4701 : : }
4702 : 0 : fprintf(logfile, " " INT64_FORMAT " " INT64_FORMAT, retried, retries);
4703 : :
4704 [ # # ]: 0 : if (failures_detailed)
4705 : : {
4706 : 0 : serialization_failures = agg->serialization_failures;
4707 : 0 : deadlock_failures = agg->deadlock_failures;
39 fujii@postgresql.org 4708 :UNC 0 : other_sql_failures = agg->other_sql_failures;
4709 : : }
4710 : 0 : fprintf(logfile, " " INT64_FORMAT " " INT64_FORMAT " " INT64_FORMAT,
4711 : : serialization_failures,
4712 : : deadlock_failures,
4713 : : other_sql_failures);
4714 : :
3609 alvherre@alvh.no-ip. 4715 :UBC 0 : fputc('\n', logfile);
4716 : :
4717 : : /* reset data and move to next interval */
1619 tmunro@postgresql.or 4718 : 0 : initStats(agg, next);
4719 : : }
4720 : :
4721 : : /* accumulate the current transaction */
1364 ishii@postgresql.org 4722 : 0 : accumStats(agg, skipped, latency, lag, st->estatus, st->tries);
4723 : : }
4724 : : else
4725 : : {
4726 : : /* no, print raw transactions */
1364 ishii@postgresql.org 4727 [ + - + - ]:CBC 63 : if (!skipped && st->estatus == ESTATUS_NO_ERROR)
1742 tmunro@postgresql.or 4728 : 63 : fprintf(logfile, "%d " INT64_FORMAT " %.0f %d " INT64_FORMAT " "
4729 : : INT64_FORMAT,
4730 : : st->id, st->cnt, latency, st->use_file,
4731 : : now / 1000000, now % 1000000);
4732 : : else
1364 ishii@postgresql.org 4733 :UBC 0 : fprintf(logfile, "%d " INT64_FORMAT " %s %d " INT64_FORMAT " "
4734 : : INT64_FORMAT,
4735 : : st->id, st->cnt, getResultString(skipped, st->estatus),
4736 : : st->use_file, now / 1000000, now % 1000000);
4737 : :
4093 heikki.linnakangas@i 4738 [ - + ]:CBC 63 : if (throttle_delay)
4093 heikki.linnakangas@i 4739 :UBC 0 : fprintf(logfile, " %.0f", lag);
1364 ishii@postgresql.org 4740 [ - + ]:CBC 63 : if (max_tries != 1)
1336 peter@eisentraut.org 4741 :UBC 0 : fprintf(logfile, " %u", st->tries - 1);
4093 heikki.linnakangas@i 4742 :CBC 63 : fputc('\n', logfile);
4743 : : }
4744 : : }
4745 : :
4746 : : /*
4747 : : * Accumulate and report statistics at end of a transaction.
4748 : : *
4749 : : * (This is also called when a transaction is late and thus skipped.
4750 : : * Note that even skipped and failed transactions are counted in the CState
4751 : : * "cnt" field.)
4752 : : */
4753 : : static void
1742 tmunro@postgresql.or 4754 : 7715 : processXactStats(TState *thread, CState *st, pg_time_usec_t *now,
4755 : : bool skipped, StatsData *agg)
4756 : : {
3609 alvherre@alvh.no-ip. 4757 : 7715 : double latency = 0.0,
4758 : 7715 : lag = 0.0;
1364 ishii@postgresql.org 4759 [ + + + - : 7715 : bool detailed = progress || throttle_delay || latency_limit ||
+ + ]
942 tgl@sss.pgh.pa.us 4760 [ + - + + ]: 15430 : use_log || per_script_stats;
4761 : :
1364 ishii@postgresql.org 4762 [ + + + + : 7715 : if (detailed && !skipped && st->estatus == ESTATUS_NO_ERROR)
+ - ]
4763 : : {
1742 tmunro@postgresql.or 4764 : 1661 : pg_time_now_lazy(now);
4765 : :
4766 : : /* compute latency & lag */
4767 : 1661 : latency = (*now) - st->txn_scheduled;
4768 : 1661 : lag = st->txn_begin - st->txn_scheduled;
4769 : : }
4770 : :
4771 : : /* keep detailed thread stats */
1364 ishii@postgresql.org 4772 : 7715 : accumStats(&thread->stats, skipped, latency, lag, st->estatus, st->tries);
4773 : :
4774 : : /* count transactions over the latency limit, if needed */
4775 [ + + + + ]: 7715 : if (latency_limit && latency > latency_limit)
4776 : 1 : thread->latency_late++;
4777 : :
4778 : : /* client stat is just counting */
3025 tgl@sss.pgh.pa.us 4779 : 7715 : st->cnt++;
4780 : :
3609 alvherre@alvh.no-ip. 4781 [ + + ]: 7715 : if (use_log)
3270 tgl@sss.pgh.pa.us 4782 : 110 : doLog(thread, st, agg, skipped, latency, lag);
4783 : :
4784 : : /* XXX could use a mutex here, but we choose not to */
3606 alvherre@alvh.no-ip. 4785 [ + + ]: 7715 : if (per_script_stats)
1364 ishii@postgresql.org 4786 : 1350 : accumStats(&sql_script[st->use_file].stats, skipped, latency, lag,
4787 : 1350 : st->estatus, st->tries);
3609 alvherre@alvh.no-ip. 4788 : 7715 : }
4789 : :
4790 : :
4791 : : /* discard connections */
4792 : : static void
5979 ishii@postgresql.org 4793 : 174 : disconnect_all(CState *state, int length)
4794 : : {
4795 : : int i;
4796 : :
4797 [ + + ]: 426 : for (i = 0; i < length; i++)
2847 andres@anarazel.de 4798 : 252 : finishCon(&state[i]);
9467 ishii@postgresql.org 4799 : 174 : }
4800 : :
4801 : : /*
4802 : : * Remove old pgbench tables, if any exist
4803 : : */
4804 : : static void
2955 tgl@sss.pgh.pa.us 4805 : 3 : initDropTables(PGconn *con)
4806 : : {
4807 : 3 : fprintf(stderr, "dropping old tables...\n");
4808 : :
4809 : : /*
4810 : : * We drop all the tables in one command, so that whether there are
4811 : : * foreign key dependencies or not doesn't matter.
4812 : : */
4813 : 3 : executeStatement(con, "drop table if exists "
4814 : : "pgbench_accounts, "
4815 : : "pgbench_branches, "
4816 : : "pgbench_history, "
4817 : : "pgbench_tellers");
4818 : 3 : }
4819 : :
4820 : : /*
4821 : : * Create "pgbench_accounts" partitions if needed.
4822 : : *
4823 : : * This is the larger table of pgbench default tpc-b like schema
4824 : : * with a known size, so we choose to partition it.
4825 : : */
4826 : : static void
2268 akapila@postgresql.o 4827 : 2 : createPartitions(PGconn *con)
4828 : : {
4829 : : PQExpBufferData query;
4830 : :
4831 : : /* we must have to create some partitions */
4832 [ - + ]: 2 : Assert(partitions > 0);
4833 : :
4834 : 2 : fprintf(stderr, "creating %d partitions...\n", partitions);
4835 : :
1903 heikki.linnakangas@i 4836 : 2 : initPQExpBuffer(&query);
4837 : :
2268 akapila@postgresql.o 4838 [ + + ]: 7 : for (int p = 1; p <= partitions; p++)
4839 : : {
4840 [ + + ]: 5 : if (partition_method == PART_RANGE)
4841 : : {
4842 : 3 : int64 part_size = (naccounts * (int64) scale + partitions - 1) / partitions;
4843 : :
1903 heikki.linnakangas@i 4844 : 3 : printfPQExpBuffer(&query,
4845 : : "create%s table pgbench_accounts_%d\n"
4846 : : " partition of pgbench_accounts\n"
4847 : : " for values from (",
4848 [ + - ]: 3 : unlogged_tables ? " unlogged" : "", p);
4849 : :
4850 : : /*
4851 : : * For RANGE, we use open-ended partitions at the beginning and
4852 : : * end to allow any valid value for the primary key. Although the
4853 : : * actual minimum and maximum values can be derived from the
4854 : : * scale, it is more generic and the performance is better.
4855 : : */
2268 akapila@postgresql.o 4856 [ + + ]: 3 : if (p == 1)
1903 heikki.linnakangas@i 4857 : 1 : appendPQExpBufferStr(&query, "minvalue");
4858 : : else
4859 : 2 : appendPQExpBuffer(&query, INT64_FORMAT, (p - 1) * part_size + 1);
4860 : :
4861 : 3 : appendPQExpBufferStr(&query, ") to (");
4862 : :
2268 akapila@postgresql.o 4863 [ + + ]: 3 : if (p < partitions)
1903 heikki.linnakangas@i 4864 : 2 : appendPQExpBuffer(&query, INT64_FORMAT, p * part_size + 1);
4865 : : else
4866 : 1 : appendPQExpBufferStr(&query, "maxvalue");
4867 : :
4868 : 3 : appendPQExpBufferChar(&query, ')');
4869 : : }
2268 akapila@postgresql.o 4870 [ + - ]: 2 : else if (partition_method == PART_HASH)
1903 heikki.linnakangas@i 4871 : 2 : printfPQExpBuffer(&query,
4872 : : "create%s table pgbench_accounts_%d\n"
4873 : : " partition of pgbench_accounts\n"
4874 : : " for values with (modulus %d, remainder %d)",
4875 [ + - ]: 2 : unlogged_tables ? " unlogged" : "", p,
4876 : : partitions, p - 1);
4877 : : else /* cannot get there */
2268 akapila@postgresql.o 4878 :UBC 0 : Assert(0);
4879 : :
4880 : : /*
4881 : : * Per ddlinfo in initCreateTables, fillfactor is needed on table
4882 : : * pgbench_accounts.
4883 : : */
1903 heikki.linnakangas@i 4884 :CBC 5 : appendPQExpBuffer(&query, " with (fillfactor=%d)", fillfactor);
4885 : :
4886 : 5 : executeStatement(con, query.data);
4887 : : }
4888 : :
4889 : 2 : termPQExpBuffer(&query);
2268 akapila@postgresql.o 4890 : 2 : }
4891 : :
4892 : : /*
4893 : : * Create pgbench's standard tables
4894 : : */
4895 : : static void
2955 tgl@sss.pgh.pa.us 4896 : 3 : initCreateTables(PGconn *con)
4897 : : {
4898 : : /*
4899 : : * Note: TPC-B requires at least 100 bytes per row, and the "filler"
4900 : : * fields in these table declarations were intended to comply with that.
4901 : : * The pgbench_accounts table complies with that because the "filler"
4902 : : * column is set to blank-padded empty string. But for all other tables
4903 : : * the columns default to NULL and so don't actually take any space. We
4904 : : * could fix that by giving them non-null default values. However, that
4905 : : * would completely break comparability of pgbench results with prior
4906 : : * versions. Since pgbench has never pretended to be fully TPC-B compliant
4907 : : * anyway, we stick with the historical behavior.
4908 : : */
4909 : : struct ddlinfo
4910 : : {
4911 : : const char *table; /* table name */
4912 : : const char *smcols; /* column decls if accountIDs are 32 bits */
4913 : : const char *bigcols; /* column decls if accountIDs are 64 bits */
4914 : : int declare_fillfactor;
4915 : : };
4916 : : static const struct ddlinfo DDLs[] = {
4917 : : {
4918 : : "pgbench_history",
4919 : : "tid int,bid int,aid int,delta int,mtime timestamp,filler char(22)",
4920 : : "tid int,bid int,aid bigint,delta int,mtime timestamp,filler char(22)",
4921 : : 0
4922 : : },
4923 : : {
4924 : : "pgbench_tellers",
4925 : : "tid int not null,bid int,tbalance int,filler char(84)",
4926 : : "tid int not null,bid int,tbalance int,filler char(84)",
4927 : : 1
4928 : : },
4929 : : {
4930 : : "pgbench_accounts",
4931 : : "aid int not null,bid int,abalance int,filler char(84)",
4932 : : "aid bigint not null,bid int,abalance int,filler char(84)",
4933 : : 1
4934 : : },
4935 : : {
4936 : : "pgbench_branches",
4937 : : "bid int not null,bbalance int,filler char(88)",
4938 : : "bid int not null,bbalance int,filler char(88)",
4939 : : 1
4940 : : }
4941 : : };
4942 : : int i;
4943 : : PQExpBufferData query;
4944 : :
4945 : 3 : fprintf(stderr, "creating tables...\n");
4946 : :
1903 heikki.linnakangas@i 4947 : 3 : initPQExpBuffer(&query);
4948 : :
6829 ishii@postgresql.org 4949 [ + + ]: 15 : for (i = 0; i < lengthof(DDLs); i++)
4950 : : {
4229 tgl@sss.pgh.pa.us 4951 : 12 : const struct ddlinfo *ddl = &DDLs[i];
4952 : :
4953 : : /* Construct new create table statement. */
1903 heikki.linnakangas@i 4954 : 24 : printfPQExpBuffer(&query, "create%s table %s(%s)",
439 michael@paquier.xyz 4955 [ - + ]: 8 : (unlogged_tables && partition_method == PART_NONE) ? " unlogged" : "",
1903 heikki.linnakangas@i 4956 [ + + ]: 12 : ddl->table,
4957 [ - + ]: 12 : (scale >= SCALE_32BIT_THRESHOLD) ? ddl->bigcols : ddl->smcols);
4958 : :
4959 : : /* Partition pgbench_accounts table */
2268 akapila@postgresql.o 4960 [ + + + + ]: 12 : if (partition_method != PART_NONE && strcmp(ddl->table, "pgbench_accounts") == 0)
1903 heikki.linnakangas@i 4961 : 2 : appendPQExpBuffer(&query,
4962 : 2 : " partition by %s (aid)", PARTITION_METHOD[partition_method]);
2268 akapila@postgresql.o 4963 [ + + ]: 10 : else if (ddl->declare_fillfactor)
4964 : : {
4965 : : /* fillfactor is only expected on actual tables */
1903 heikki.linnakangas@i 4966 : 7 : appendPQExpBuffer(&query, " with (fillfactor=%d)", fillfactor);
4967 : : }
4968 : :
5258 rhaas@postgresql.org 4969 [ + + ]: 12 : if (tablespace != NULL)
4970 : : {
4971 : : char *escape_tablespace;
4972 : :
1903 heikki.linnakangas@i 4973 : 4 : escape_tablespace = PQescapeIdentifier(con, tablespace, strlen(tablespace));
4974 : 4 : appendPQExpBuffer(&query, " tablespace %s", escape_tablespace);
5258 rhaas@postgresql.org 4975 : 4 : PQfreemem(escape_tablespace);
4976 : : }
4977 : :
1903 heikki.linnakangas@i 4978 : 12 : executeStatement(con, query.data);
4979 : : }
4980 : :
4981 : 3 : termPQExpBuffer(&query);
4982 : :
2268 akapila@postgresql.o 4983 [ + + ]: 3 : if (partition_method != PART_NONE)
4984 : 2 : createPartitions(con);
4985 : 3 : }
4986 : :
4987 : : /*
4988 : : * Truncate away any old data, in one command in case there are foreign keys
4989 : : */
4990 : : static void
2232 fujii@postgresql.org 4991 : 3 : initTruncateTables(PGconn *con)
4992 : : {
4993 : 3 : executeStatement(con, "truncate table "
4994 : : "pgbench_accounts, "
4995 : : "pgbench_branches, "
4996 : : "pgbench_history, "
4997 : : "pgbench_tellers");
4998 : 3 : }
4999 : :
5000 : : static void
876 michael@paquier.xyz 5001 : 2 : initBranch(PQExpBufferData *sql, int64 curr)
5002 : : {
5003 : : /* "filler" column uses NULL */
5004 : 2 : printfPQExpBuffer(sql,
5005 : : INT64_FORMAT "\t0\t\\N\n",
5006 : : curr + 1);
5007 : 2 : }
5008 : :
5009 : : static void
5010 : 20 : initTeller(PQExpBufferData *sql, int64 curr)
5011 : : {
5012 : : /* "filler" column uses NULL */
5013 : 20 : printfPQExpBuffer(sql,
5014 : : INT64_FORMAT "\t" INT64_FORMAT "\t0\t\\N\n",
5015 : 20 : curr + 1, curr / ntellers + 1);
5016 : 20 : }
5017 : :
5018 : : static void
5019 : 200000 : initAccount(PQExpBufferData *sql, int64 curr)
5020 : : {
5021 : : /* "filler" column defaults to blank padded empty string */
5022 : 200000 : printfPQExpBuffer(sql,
5023 : : INT64_FORMAT "\t" INT64_FORMAT "\t0\t\n",
5024 : 200000 : curr + 1, curr / naccounts + 1);
5025 : 200000 : }
5026 : :
5027 : : static void
5028 : 6 : initPopulateTable(PGconn *con, const char *table, int64 base,
5029 : : initRowMethod init_row)
5030 : : {
5031 : : int n;
5032 : : int64 k;
5033 : 6 : int chars = 0;
384 fujii@postgresql.org 5034 : 6 : int prev_chars = 0;
5035 : : PGresult *res;
5036 : : PQExpBufferData sql;
5037 : : char copy_statement[256];
876 michael@paquier.xyz 5038 : 6 : const char *copy_statement_fmt = "copy %s from stdin";
5039 : 6 : int64 total = base * scale;
5040 : :
5041 : : /* used to track elapsed time and estimate of the remaining time */
5042 : : pg_time_usec_t start;
2955 tgl@sss.pgh.pa.us 5043 : 6 : int log_interval = 1;
5044 : :
5045 : : /* Stay on the same line if reporting to a terminal */
2204 michael@paquier.xyz 5046 [ - + ]: 6 : char eol = isatty(fileno(stderr)) ? '\r' : '\n';
5047 : :
1903 heikki.linnakangas@i 5048 : 6 : initPQExpBuffer(&sql);
5049 : :
5050 : : /* Use COPY with FREEZE on v14 and later for all ordinary tables */
308 melanieplageman@gmai 5051 [ + - + + ]: 12 : if ((PQserverVersion(con) >= 140000) &&
5052 : 6 : get_table_relkind(con, table) == RELKIND_RELATION)
5053 : 5 : copy_statement_fmt = "copy %s from stdin with (freeze on)";
5054 : :
5055 : :
876 michael@paquier.xyz 5056 : 6 : n = pg_snprintf(copy_statement, sizeof(copy_statement), copy_statement_fmt, table);
5057 [ - + ]: 6 : if (n >= sizeof(copy_statement))
876 michael@paquier.xyz 5058 :UBC 0 : pg_fatal("invalid buffer size: must be at least %d characters long", n);
876 michael@paquier.xyz 5059 [ - + ]:CBC 6 : else if (n == -1)
876 michael@paquier.xyz 5060 :UBC 0 : pg_fatal("invalid format string");
5061 : :
1566 ishii@postgresql.org 5062 :CBC 6 : res = PQexec(con, copy_statement);
5063 : :
6829 5064 [ - + ]: 6 : if (PQresultStatus(res) != PGRES_COPY_IN)
1348 tgl@sss.pgh.pa.us 5065 :UBC 0 : pg_fatal("unexpected copy in result: %s", PQerrorMessage(con));
7855 ishii@postgresql.org 5066 :CBC 6 : PQclear(res);
5067 : :
1742 tmunro@postgresql.or 5068 : 6 : start = pg_time_now();
5069 : :
876 michael@paquier.xyz 5070 [ + + ]: 200028 : for (k = 0; k < total; k++)
5071 : : {
4704 heikki.linnakangas@i 5072 : 200022 : int64 j = k + 1;
5073 : :
876 michael@paquier.xyz 5074 : 200022 : init_row(&sql, k);
1903 heikki.linnakangas@i 5075 [ - + ]: 200022 : if (PQputline(con, sql.data))
1348 tgl@sss.pgh.pa.us 5076 :UBC 0 : pg_fatal("PQputline failed");
5077 : :
2206 michael@paquier.xyz 5078 [ - + ]:CBC 200022 : if (CancelRequested)
2206 michael@paquier.xyz 5079 :UBC 0 : break;
5080 : :
5081 : : /*
5082 : : * If we want to stick with the original logging, print a message each
5083 : : * 100k inserted rows.
5084 : : */
4584 bruce@momjian.us 5085 [ + + + + ]:CBC 200022 : if ((!use_quiet) && (j % 100000 == 0))
4726 ishii@postgresql.org 5086 : 1 : {
1742 tmunro@postgresql.or 5087 : 1 : double elapsed_sec = PG_TIME_GET_DOUBLE(pg_time_now() - start);
876 michael@paquier.xyz 5088 : 1 : double remaining_sec = ((double) total - j) * elapsed_sec / j;
5089 : :
384 fujii@postgresql.org 5090 : 1 : chars = fprintf(stderr, INT64_FORMAT " of " INT64_FORMAT " tuples (%d%%) of %s done (elapsed %.2f s, remaining %.2f s)",
5091 : : j, total,
876 michael@paquier.xyz 5092 : 1 : (int) ((j * 100) / total),
5093 : : table, elapsed_sec, remaining_sec);
5094 : :
5095 : : /*
5096 : : * If the previous progress message is longer than the current
5097 : : * one, add spaces to the current line to fully overwrite any
5098 : : * remaining characters from the previous message.
5099 : : */
312 tgl@sss.pgh.pa.us 5100 [ - + ]: 1 : if (prev_chars > chars)
312 tgl@sss.pgh.pa.us 5101 :UBC 0 : fprintf(stderr, "%*c", prev_chars - chars, ' ');
312 tgl@sss.pgh.pa.us 5102 :CBC 1 : fputc(eol, stderr);
5103 : 1 : prev_chars = chars;
5104 : : }
5105 : : /* let's not call the timing for each row, but only each 100 rows */
4726 ishii@postgresql.org 5106 [ + + + + ]: 200021 : else if (use_quiet && (j % 100 == 0))
5107 : : {
1742 tmunro@postgresql.or 5108 : 1000 : double elapsed_sec = PG_TIME_GET_DOUBLE(pg_time_now() - start);
876 michael@paquier.xyz 5109 : 1000 : double remaining_sec = ((double) total - j) * elapsed_sec / j;
5110 : :
5111 : : /* have we reached the next interval (or end)? */
5112 [ + + - + ]: 1000 : if ((j == total) || (elapsed_sec >= log_interval * LOG_STEP_SECONDS))
5113 : : {
384 fujii@postgresql.org 5114 : 1 : chars = fprintf(stderr, INT64_FORMAT " of " INT64_FORMAT " tuples (%d%%) of %s done (elapsed %.2f s, remaining %.2f s)",
5115 : : j, total,
876 michael@paquier.xyz 5116 : 1 : (int) ((j * 100) / total),
5117 : : table, elapsed_sec, remaining_sec);
5118 : :
5119 : : /*
5120 : : * If the previous progress message is longer than the current
5121 : : * one, add spaces to the current line to fully overwrite any
5122 : : * remaining characters from the previous message.
5123 : : */
312 tgl@sss.pgh.pa.us 5124 [ - + ]: 1 : if (prev_chars > chars)
312 tgl@sss.pgh.pa.us 5125 :UBC 0 : fprintf(stderr, "%*c", prev_chars - chars, ' ');
312 tgl@sss.pgh.pa.us 5126 :CBC 1 : fputc(eol, stderr);
5127 : 1 : prev_chars = chars;
5128 : :
5129 : : /* skip to the next interval */
4584 bruce@momjian.us 5130 : 1 : log_interval = (int) ceil(elapsed_sec / LOG_STEP_SECONDS);
5131 : : }
5132 : : }
5133 : : }
5134 : :
876 michael@paquier.xyz 5135 [ + + - + ]: 6 : if (chars != 0 && eol != '\n')
384 fujii@postgresql.org 5136 :UBC 0 : fprintf(stderr, "%*c\r", chars, ' '); /* Clear the current line */
5137 : :
6829 ishii@postgresql.org 5138 [ - + ]:CBC 6 : if (PQputline(con, "\\.\n"))
1348 tgl@sss.pgh.pa.us 5139 :UBC 0 : pg_fatal("very last PQputline failed");
6829 ishii@postgresql.org 5140 [ - + ]:CBC 6 : if (PQendcopy(con))
1348 tgl@sss.pgh.pa.us 5141 :UBC 0 : pg_fatal("PQendcopy failed");
5142 : :
1903 heikki.linnakangas@i 5143 :CBC 6 : termPQExpBuffer(&sql);
876 michael@paquier.xyz 5144 : 6 : }
5145 : :
5146 : : /*
5147 : : * Fill the standard tables with some data generated and sent from the client.
5148 : : *
5149 : : * The filler column is NULL in pgbench_branches and pgbench_tellers, and is
5150 : : * a blank-padded string in pgbench_accounts.
5151 : : */
5152 : : static void
5153 : 2 : initGenerateDataClientSide(PGconn *con)
5154 : : {
5155 : 2 : fprintf(stderr, "generating data (client-side)...\n");
5156 : :
5157 : : /*
5158 : : * we do all of this in one transaction to enable the backend's
5159 : : * data-loading optimizations
5160 : : */
5161 : 2 : executeStatement(con, "begin");
5162 : :
5163 : : /* truncate away any old data */
5164 : 2 : initTruncateTables(con);
5165 : :
5166 : : /*
5167 : : * fill branches, tellers, accounts in that order in case foreign keys
5168 : : * already exist
5169 : : */
5170 : 2 : initPopulateTable(con, "pgbench_branches", nbranches, initBranch);
5171 : 2 : initPopulateTable(con, "pgbench_tellers", ntellers, initTeller);
5172 : 2 : initPopulateTable(con, "pgbench_accounts", naccounts, initAccount);
5173 : :
6829 ishii@postgresql.org 5174 : 2 : executeStatement(con, "commit");
2955 tgl@sss.pgh.pa.us 5175 : 2 : }
5176 : :
5177 : : /*
5178 : : * Fill the standard tables with some data generated on the server
5179 : : *
5180 : : * As already the case with the client-side data generation, the filler
5181 : : * column defaults to NULL in pgbench_branches and pgbench_tellers,
5182 : : * and is a blank-padded string in pgbench_accounts.
5183 : : */
5184 : : static void
2232 fujii@postgresql.org 5185 : 1 : initGenerateDataServerSide(PGconn *con)
5186 : : {
5187 : : PQExpBufferData sql;
5188 : :
5189 : 1 : fprintf(stderr, "generating data (server-side)...\n");
5190 : :
5191 : : /*
5192 : : * we do all of this in one transaction to enable the backend's
5193 : : * data-loading optimizations
5194 : : */
5195 : 1 : executeStatement(con, "begin");
5196 : :
5197 : : /* truncate away any old data */
5198 : 1 : initTruncateTables(con);
5199 : :
1903 heikki.linnakangas@i 5200 : 1 : initPQExpBuffer(&sql);
5201 : :
5202 : 1 : printfPQExpBuffer(&sql,
5203 : : "insert into pgbench_branches(bid,bbalance) "
5204 : : "select bid, 0 "
5205 : : "from generate_series(1, %d) as bid", nbranches * scale);
5206 : 1 : executeStatement(con, sql.data);
5207 : :
5208 : 1 : printfPQExpBuffer(&sql,
5209 : : "insert into pgbench_tellers(tid,bid,tbalance) "
5210 : : "select tid, (tid - 1) / %d + 1, 0 "
5211 : : "from generate_series(1, %d) as tid", ntellers, ntellers * scale);
5212 : 1 : executeStatement(con, sql.data);
5213 : :
5214 : 1 : printfPQExpBuffer(&sql,
5215 : : "insert into pgbench_accounts(aid,bid,abalance,filler) "
5216 : : "select aid, (aid - 1) / %d + 1, 0, '' "
5217 : : "from generate_series(1, " INT64_FORMAT ") as aid",
5218 : : naccounts, (int64) naccounts * scale);
5219 : 1 : executeStatement(con, sql.data);
5220 : :
5221 : 1 : termPQExpBuffer(&sql);
5222 : :
2232 fujii@postgresql.org 5223 : 1 : executeStatement(con, "commit");
5224 : 1 : }
5225 : :
5226 : : /*
5227 : : * Invoke vacuum on the standard tables
5228 : : */
5229 : : static void
2955 tgl@sss.pgh.pa.us 5230 : 2 : initVacuum(PGconn *con)
5231 : : {
5232 : 2 : fprintf(stderr, "vacuuming...\n");
5233 : 2 : executeStatement(con, "vacuum analyze pgbench_branches");
5234 : 2 : executeStatement(con, "vacuum analyze pgbench_tellers");
5235 : 2 : executeStatement(con, "vacuum analyze pgbench_accounts");
5236 : 2 : executeStatement(con, "vacuum analyze pgbench_history");
5237 : 2 : }
5238 : :
5239 : : /*
5240 : : * Create primary keys on the standard tables
5241 : : */
5242 : : static void
5243 : 3 : initCreatePKeys(PGconn *con)
5244 : : {
5245 : : static const char *const DDLINDEXes[] = {
5246 : : "alter table pgbench_branches add primary key (bid)",
5247 : : "alter table pgbench_tellers add primary key (tid)",
5248 : : "alter table pgbench_accounts add primary key (aid)"
5249 : : };
5250 : : int i;
5251 : : PQExpBufferData query;
5252 : :
5253 : 3 : fprintf(stderr, "creating primary keys...\n");
1903 heikki.linnakangas@i 5254 : 3 : initPQExpBuffer(&query);
5255 : :
4229 tgl@sss.pgh.pa.us 5256 [ + + ]: 12 : for (i = 0; i < lengthof(DDLINDEXes); i++)
5257 : : {
1903 heikki.linnakangas@i 5258 : 9 : resetPQExpBuffer(&query);
5259 : 9 : appendPQExpBufferStr(&query, DDLINDEXes[i]);
5260 : :
5258 rhaas@postgresql.org 5261 [ + + ]: 9 : if (index_tablespace != NULL)
5262 : : {
5263 : : char *escape_tablespace;
5264 : :
5265 : 3 : escape_tablespace = PQescapeIdentifier(con, index_tablespace,
5266 : : strlen(index_tablespace));
1903 heikki.linnakangas@i 5267 : 3 : appendPQExpBuffer(&query, " using index tablespace %s", escape_tablespace);
5258 rhaas@postgresql.org 5268 : 3 : PQfreemem(escape_tablespace);
5269 : : }
5270 : :
1903 heikki.linnakangas@i 5271 : 9 : executeStatement(con, query.data);
5272 : : }
5273 : :
5274 : 3 : termPQExpBuffer(&query);
2955 tgl@sss.pgh.pa.us 5275 : 3 : }
5276 : :
5277 : : /*
5278 : : * Create foreign key constraints between the standard tables
5279 : : */
5280 : : static void
5281 : 2 : initCreateFKeys(PGconn *con)
5282 : : {
5283 : : static const char *const DDLKEYs[] = {
5284 : : "alter table pgbench_tellers add constraint pgbench_tellers_bid_fkey foreign key (bid) references pgbench_branches",
5285 : : "alter table pgbench_accounts add constraint pgbench_accounts_bid_fkey foreign key (bid) references pgbench_branches",
5286 : : "alter table pgbench_history add constraint pgbench_history_bid_fkey foreign key (bid) references pgbench_branches",
5287 : : "alter table pgbench_history add constraint pgbench_history_tid_fkey foreign key (tid) references pgbench_tellers",
5288 : : "alter table pgbench_history add constraint pgbench_history_aid_fkey foreign key (aid) references pgbench_accounts"
5289 : : };
5290 : : int i;
5291 : :
5292 : 2 : fprintf(stderr, "creating foreign keys...\n");
5293 [ + + ]: 12 : for (i = 0; i < lengthof(DDLKEYs); i++)
5294 : : {
5295 : 10 : executeStatement(con, DDLKEYs[i]);
5296 : : }
5297 : 2 : }
5298 : :
5299 : : /*
5300 : : * Validate an initialization-steps string
5301 : : *
5302 : : * (We could just leave it to runInitSteps() to fail if there are wrong
5303 : : * characters, but since initialization can take awhile, it seems friendlier
5304 : : * to check during option parsing.)
5305 : : */
5306 : : static void
5307 : 4 : checkInitSteps(const char *initialize_steps)
5308 : : {
5309 [ - + ]: 4 : if (initialize_steps[0] == '\0')
1348 tgl@sss.pgh.pa.us 5310 :UBC 0 : pg_fatal("no initialization steps specified");
5311 : :
2232 fujii@postgresql.org 5312 [ + + ]:CBC 21 : for (const char *step = initialize_steps; *step != '\0'; step++)
5313 : : {
5314 [ + + ]: 18 : if (strchr(ALL_INIT_STEPS " ", *step) == NULL)
5315 : : {
1348 tgl@sss.pgh.pa.us 5316 : 1 : pg_log_error("unrecognized initialization step \"%c\"", *step);
5317 : 1 : pg_log_error_detail("Allowed step characters are: \"" ALL_INIT_STEPS "\".");
2955 5318 : 1 : exit(1);
5319 : : }
5320 : : }
5321 : 3 : }
5322 : :
5323 : : /*
5324 : : * Invoke each initialization step in the given string
5325 : : */
5326 : : static void
5327 : 3 : runInitSteps(const char *initialize_steps)
5328 : : {
5329 : : PQExpBufferData stats;
5330 : : PGconn *con;
5331 : : const char *step;
2345 tmunro@postgresql.or 5332 : 3 : double run_time = 0.0;
5333 : 3 : bool first = true;
5334 : :
5335 : 3 : initPQExpBuffer(&stats);
5336 : :
2955 tgl@sss.pgh.pa.us 5337 [ - + ]: 3 : if ((con = doConnect()) == NULL)
1348 tgl@sss.pgh.pa.us 5338 :UBC 0 : pg_fatal("could not create connection for initialization");
5339 : :
2206 michael@paquier.xyz 5340 :CBC 3 : setup_cancel_handler(NULL);
5341 : 3 : SetCancelConn(con);
5342 : :
2955 tgl@sss.pgh.pa.us 5343 [ + + ]: 22 : for (step = initialize_steps; *step != '\0'; step++)
5344 : : {
2345 tmunro@postgresql.or 5345 : 19 : char *op = NULL;
1742 5346 : 19 : pg_time_usec_t start = pg_time_now();
5347 : :
2955 tgl@sss.pgh.pa.us 5348 [ + + + + : 19 : switch (*step)
+ + + +
- ]
5349 : : {
5350 : 3 : case 'd':
2345 tmunro@postgresql.or 5351 : 3 : op = "drop tables";
2955 tgl@sss.pgh.pa.us 5352 : 3 : initDropTables(con);
5353 : 3 : break;
5354 : 3 : case 't':
2345 tmunro@postgresql.or 5355 : 3 : op = "create tables";
2955 tgl@sss.pgh.pa.us 5356 : 3 : initCreateTables(con);
5357 : 3 : break;
5358 : 2 : case 'g':
2232 fujii@postgresql.org 5359 : 2 : op = "client-side generate";
5360 : 2 : initGenerateDataClientSide(con);
5361 : 2 : break;
5362 : 1 : case 'G':
5363 : 1 : op = "server-side generate";
5364 : 1 : initGenerateDataServerSide(con);
2955 tgl@sss.pgh.pa.us 5365 : 1 : break;
5366 : 2 : case 'v':
2345 tmunro@postgresql.or 5367 : 2 : op = "vacuum";
2955 tgl@sss.pgh.pa.us 5368 : 2 : initVacuum(con);
5369 : 2 : break;
5370 : 3 : case 'p':
2345 tmunro@postgresql.or 5371 : 3 : op = "primary keys";
2955 tgl@sss.pgh.pa.us 5372 : 3 : initCreatePKeys(con);
5373 : 3 : break;
5374 : 2 : case 'f':
2345 tmunro@postgresql.or 5375 : 2 : op = "foreign keys";
2955 tgl@sss.pgh.pa.us 5376 : 2 : initCreateFKeys(con);
5377 : 2 : break;
5378 : 3 : case ' ':
5379 : 3 : break; /* ignore */
2955 tgl@sss.pgh.pa.us 5380 :UBC 0 : default:
1348 5381 : 0 : pg_log_error("unrecognized initialization step \"%c\"", *step);
2955 5382 : 0 : PQfinish(con);
5383 : 0 : exit(1);
5384 : : }
5385 : :
2345 tmunro@postgresql.or 5386 [ + + ]:CBC 19 : if (op != NULL)
5387 : : {
1742 5388 : 16 : double elapsed_sec = PG_TIME_GET_DOUBLE(pg_time_now() - start);
5389 : :
2345 5390 [ + + ]: 16 : if (!first)
5391 : 13 : appendPQExpBufferStr(&stats, ", ");
5392 : : else
5393 : 3 : first = false;
5394 : :
5395 : 16 : appendPQExpBuffer(&stats, "%s %.2f s", op, elapsed_sec);
5396 : :
5397 : 16 : run_time += elapsed_sec;
5398 : : }
5399 : : }
5400 : :
5401 : 3 : fprintf(stderr, "done in %.2f s (%s).\n", run_time, stats.data);
2206 michael@paquier.xyz 5402 : 3 : ResetCancelConn();
9379 bruce@momjian.us 5403 : 3 : PQfinish(con);
2345 tmunro@postgresql.or 5404 : 3 : termPQExpBuffer(&stats);
9379 bruce@momjian.us 5405 : 3 : }
5406 : :
5407 : : /*
5408 : : * Extract pgbench table information into global variables scale,
5409 : : * partition_method and partitions.
5410 : : */
5411 : : static void
2268 akapila@postgresql.o 5412 : 7 : GetTableInfo(PGconn *con, bool scale_given)
5413 : : {
5414 : : PGresult *res;
5415 : :
5416 : : /*
5417 : : * get the scaling factor that should be same as count(*) from
5418 : : * pgbench_branches if this is not a custom query
5419 : : */
5420 : 7 : res = PQexec(con, "select count(*) from pgbench_branches");
5421 [ + + ]: 7 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
5422 : : {
5423 : 1 : char *sqlState = PQresultErrorField(res, PG_DIAG_SQLSTATE);
5424 : :
1348 tgl@sss.pgh.pa.us 5425 : 1 : pg_log_error("could not count number of branches: %s", PQerrorMessage(con));
5426 : :
2268 akapila@postgresql.o 5427 [ + - + - ]: 1 : if (sqlState && strcmp(sqlState, ERRCODE_UNDEFINED_TABLE) == 0)
1348 tgl@sss.pgh.pa.us 5428 : 1 : pg_log_error_hint("Perhaps you need to do initialization (\"pgbench -i\") in database \"%s\".",
5429 : : PQdb(con));
5430 : :
2268 akapila@postgresql.o 5431 : 1 : exit(1);
5432 : : }
5433 : 6 : scale = atoi(PQgetvalue(res, 0, 0));
5434 [ - + ]: 6 : if (scale < 0)
1348 tgl@sss.pgh.pa.us 5435 :UBC 0 : pg_fatal("invalid count(*) from pgbench_branches: \"%s\"",
5436 : : PQgetvalue(res, 0, 0));
2268 akapila@postgresql.o 5437 :CBC 6 : PQclear(res);
5438 : :
5439 : : /* warn if we override user-given -s switch */
5440 [ + + ]: 6 : if (scale_given)
2169 peter@eisentraut.org 5441 : 1 : pg_log_warning("scale option ignored, using count from pgbench_branches table (%d)",
5442 : : scale);
5443 : :
5444 : : /*
5445 : : * Get the partition information for the first "pgbench_accounts" table
5446 : : * found in search_path.
5447 : : *
5448 : : * The result is empty if no "pgbench_accounts" is found.
5449 : : *
5450 : : * Otherwise, it always returns one row even if the table is not
5451 : : * partitioned (in which case the partition strategy is NULL).
5452 : : *
5453 : : * The number of partitions can be 0 even for partitioned tables, if no
5454 : : * partition is attached.
5455 : : *
5456 : : * We assume no partitioning on any failure, so as to avoid failing on an
5457 : : * old version without "pg_partitioned_table".
5458 : : */
2268 akapila@postgresql.o 5459 : 6 : res = PQexec(con,
5460 : : "select o.n, p.partstrat, pg_catalog.count(i.inhparent) "
5461 : : "from pg_catalog.pg_class as c "
5462 : : "join pg_catalog.pg_namespace as n on (n.oid = c.relnamespace) "
5463 : : "cross join lateral (select pg_catalog.array_position(pg_catalog.current_schemas(true), n.nspname)) as o(n) "
5464 : : "left join pg_catalog.pg_partitioned_table as p on (p.partrelid = c.oid) "
5465 : : "left join pg_catalog.pg_inherits as i on (c.oid = i.inhparent) "
5466 : : "where c.relname = 'pgbench_accounts' and o.n is not null "
5467 : : "group by 1, 2 "
5468 : : "order by 1 asc "
5469 : : "limit 1");
5470 : :
5471 [ - + ]: 6 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
5472 : : {
5473 : : /* probably an older version, coldly assume no partitioning */
2268 akapila@postgresql.o 5474 :UBC 0 : partition_method = PART_NONE;
5475 : 0 : partitions = 0;
5476 : : }
2268 akapila@postgresql.o 5477 [ - + ]:CBC 6 : else if (PQntuples(res) == 0)
5478 : : {
5479 : : /*
5480 : : * This case is unlikely as pgbench already found "pgbench_branches"
5481 : : * above to compute the scale.
5482 : : */
578 peter@eisentraut.org 5483 :UBC 0 : pg_log_error("no pgbench_accounts table found in \"search_path\"");
1348 tgl@sss.pgh.pa.us 5484 : 0 : pg_log_error_hint("Perhaps you need to do initialization (\"pgbench -i\") in database \"%s\".", PQdb(con));
2268 akapila@postgresql.o 5485 : 0 : exit(1);
5486 : : }
5487 : : else /* PQntuples(res) == 1 */
5488 : : {
5489 : : /* normal case, extract partition information */
2268 akapila@postgresql.o 5490 [ - + ]:CBC 6 : if (PQgetisnull(res, 0, 1))
2268 akapila@postgresql.o 5491 :UBC 0 : partition_method = PART_NONE;
5492 : : else
5493 : : {
2268 akapila@postgresql.o 5494 :CBC 6 : char *ps = PQgetvalue(res, 0, 1);
5495 : :
5496 : : /* column must be there */
5497 [ - + ]: 6 : Assert(ps != NULL);
5498 : :
5499 [ + - ]: 6 : if (strcmp(ps, "r") == 0)
5500 : 6 : partition_method = PART_RANGE;
2268 akapila@postgresql.o 5501 [ # # ]:UBC 0 : else if (strcmp(ps, "h") == 0)
5502 : 0 : partition_method = PART_HASH;
5503 : : else
5504 : : {
5505 : : /* possibly a newer version with new partition method */
1348 tgl@sss.pgh.pa.us 5506 : 0 : pg_fatal("unexpected partition method: \"%s\"", ps);
5507 : : }
5508 : : }
5509 : :
2268 akapila@postgresql.o 5510 :CBC 6 : partitions = atoi(PQgetvalue(res, 0, 2));
5511 : : }
5512 : :
5513 : 6 : PQclear(res);
5514 : 6 : }
5515 : :
5516 : : /*
5517 : : * Replace :param with $n throughout the command's SQL text, which
5518 : : * is a modifiable string in cmd->lines.
5519 : : */
5520 : : static bool
3049 tgl@sss.pgh.pa.us 5521 : 94 : parseQuery(Command *cmd)
5522 : : {
5523 : : char *sql,
5524 : : *p;
5525 : :
6481 ishii@postgresql.org 5526 : 94 : cmd->argc = 1;
5527 : :
2532 alvherre@alvh.no-ip. 5528 : 94 : p = sql = pg_strdup(cmd->lines.data);
6481 ishii@postgresql.org 5529 [ + + ]: 390 : while ((p = strchr(p, ':')) != NULL)
5530 : : {
5531 : : char var[13];
5532 : : char *name;
5533 : : int eaten;
5534 : :
5535 : 297 : name = parseVariable(p, &eaten);
5536 [ + + ]: 297 : if (name == NULL)
5537 : : {
6032 bruce@momjian.us 5538 [ + + ]: 48 : while (*p == ':')
5539 : : {
5540 : 32 : p++;
5541 : : }
6481 ishii@postgresql.org 5542 : 16 : continue;
5543 : : }
5544 : :
5545 : : /*
5546 : : * cmd->argv[0] is the SQL statement itself, so the max number of
5547 : : * arguments is one less than MAX_ARGS
5548 : : */
5549 [ + + ]: 281 : if (cmd->argc >= MAX_ARGS)
5550 : : {
2169 peter@eisentraut.org 5551 : 1 : pg_log_error("statement has too many arguments (maximum is %d): %s",
5552 : : MAX_ARGS - 1, cmd->lines.data);
4004 ishii@postgresql.org 5553 : 1 : pg_free(name);
6481 5554 : 1 : return false;
5555 : : }
5556 : :
5557 : 280 : sprintf(var, "$%d", cmd->argc);
5605 tgl@sss.pgh.pa.us 5558 : 280 : p = replaceVariable(&sql, p, eaten, var);
5559 : :
6481 ishii@postgresql.org 5560 : 280 : cmd->argv[cmd->argc] = name;
5561 : 280 : cmd->argc++;
5562 : : }
5563 : :
2532 alvherre@alvh.no-ip. 5564 [ - + ]: 93 : Assert(cmd->argv[0] == NULL);
6481 ishii@postgresql.org 5565 : 93 : cmd->argv[0] = sql;
5566 : 93 : return true;
5567 : : }
5568 : :
5569 : : /*
5570 : : * syntax error while parsing a script (in practice, while parsing a
5571 : : * backslash command, because we don't detect syntax errors in SQL)
5572 : : *
5573 : : * source: source of script (filename or builtin-script ID)
5574 : : * lineno: line number within script (count from 1)
5575 : : * line: whole line of backslash command, if available
5576 : : * command: backslash command name, if available
5577 : : * msg: the actual error message
5578 : : * more: optional extra message
5579 : : * column: zero-based column number, or -1 if unknown
5580 : : */
5581 : : void
3558 tgl@sss.pgh.pa.us 5582 : 33 : syntax_error(const char *source, int lineno,
5583 : : const char *line, const char *command,
5584 : : const char *msg, const char *more, int column)
5585 : : {
5586 : : PQExpBufferData buf;
5587 : :
2169 peter@eisentraut.org 5588 : 33 : initPQExpBuffer(&buf);
5589 : :
5590 : 33 : printfPQExpBuffer(&buf, "%s:%d: %s", source, lineno, msg);
3911 rhaas@postgresql.org 5591 [ + + ]: 33 : if (more != NULL)
2169 peter@eisentraut.org 5592 : 15 : appendPQExpBuffer(&buf, " (%s)", more);
3558 tgl@sss.pgh.pa.us 5593 [ + + - + ]: 33 : if (column >= 0 && line == NULL)
2169 peter@eisentraut.org 5594 :UBC 0 : appendPQExpBuffer(&buf, " at column %d", column + 1);
3558 tgl@sss.pgh.pa.us 5595 [ + + ]:CBC 33 : if (command != NULL)
2169 peter@eisentraut.org 5596 : 30 : appendPQExpBuffer(&buf, " in command \"%s\"", command);
5597 : :
1348 tgl@sss.pgh.pa.us 5598 : 33 : pg_log_error("%s", buf.data);
5599 : :
2169 peter@eisentraut.org 5600 : 33 : termPQExpBuffer(&buf);
5601 : :
3911 rhaas@postgresql.org 5602 [ + + ]: 33 : if (line != NULL)
5603 : : {
5604 : 28 : fprintf(stderr, "%s\n", line);
3558 tgl@sss.pgh.pa.us 5605 [ + + ]: 28 : if (column >= 0)
2042 5606 : 21 : fprintf(stderr, "%*c error found here\n", column + 1, '^');
5607 : : }
5608 : :
3911 rhaas@postgresql.org 5609 : 33 : exit(1);
5610 : : }
5611 : :
5612 : : /*
5613 : : * Return a pointer to the start of the SQL command, after skipping over
5614 : : * whitespace and "--" comments.
5615 : : * If the end of the string is reached, return NULL.
5616 : : */
5617 : : static char *
2532 alvherre@alvh.no-ip. 5618 : 1174 : skip_sql_comments(char *sql_command)
5619 : : {
5620 : 1174 : char *p = sql_command;
5621 : :
5622 : : /* Skip any leading whitespace, as well as "--" style comments */
5623 : : for (;;)
5624 : : {
3558 tgl@sss.pgh.pa.us 5625 [ - + ]: 1174 : if (isspace((unsigned char) *p))
3558 tgl@sss.pgh.pa.us 5626 :UBC 0 : p++;
3558 tgl@sss.pgh.pa.us 5627 [ - + ]:CBC 1174 : else if (strncmp(p, "--", 2) == 0)
5628 : : {
3558 tgl@sss.pgh.pa.us 5629 :UBC 0 : p = strchr(p, '\n');
5630 [ # # ]: 0 : if (p == NULL)
5631 : 0 : return NULL;
5632 : 0 : p++;
5633 : : }
5634 : : else
3558 tgl@sss.pgh.pa.us 5635 :CBC 1174 : break;
5636 : : }
5637 : :
5638 : : /* NULL if there's nothing but whitespace and comments */
5639 [ + + ]: 1174 : if (*p == '\0')
5640 : 749 : return NULL;
5641 : :
2532 alvherre@alvh.no-ip. 5642 : 425 : return p;
5643 : : }
5644 : :
5645 : : /*
5646 : : * Parse a SQL command; return a Command struct, or NULL if it's a comment
5647 : : *
5648 : : * On entry, psqlscan.l has collected the command into "buf", so we don't
5649 : : * really need to do much here except check for comments and set up a Command
5650 : : * struct.
5651 : : */
5652 : : static Command *
89 fujii@postgresql.org 5653 :GNC 1174 : create_sql_command(PQExpBuffer buf)
5654 : : {
5655 : : Command *my_command;
2532 alvherre@alvh.no-ip. 5656 :CBC 1174 : char *p = skip_sql_comments(buf->data);
5657 : :
5658 [ + + ]: 1174 : if (p == NULL)
5659 : 749 : return NULL;
5660 : :
5661 : : /* Allocate and initialize Command structure */
5662 : 425 : my_command = (Command *) pg_malloc(sizeof(Command));
5663 : 425 : initPQExpBuffer(&my_command->lines);
5664 : 425 : appendPQExpBufferStr(&my_command->lines, p);
5665 : 425 : my_command->first_line = NULL; /* this is set later */
3558 tgl@sss.pgh.pa.us 5666 : 425 : my_command->type = SQL_COMMAND;
2966 5667 : 425 : my_command->meta = META_NONE;
2532 alvherre@alvh.no-ip. 5668 : 425 : my_command->argc = 0;
1364 ishii@postgresql.org 5669 : 425 : my_command->retries = 0;
5670 : 425 : my_command->failures = 0;
2532 alvherre@alvh.no-ip. 5671 : 425 : memset(my_command->argv, 0, sizeof(my_command->argv));
5672 : 425 : my_command->varprefix = NULL; /* allocated later, if needed */
5673 : 425 : my_command->expr = NULL;
3558 tgl@sss.pgh.pa.us 5674 : 425 : initSimpleStats(&my_command->stats);
1029 alvherre@alvh.no-ip. 5675 : 425 : my_command->prepname = NULL; /* set later, if needed */
5676 : :
2532 5677 : 425 : return my_command;
5678 : : }
5679 : :
5680 : : /* Free a Command structure and associated data */
5681 : : static void
5682 : 31 : free_command(Command *command)
5683 : : {
5684 : 31 : termPQExpBuffer(&command->lines);
1278 peter@eisentraut.org 5685 : 31 : pg_free(command->first_line);
2532 alvherre@alvh.no-ip. 5686 [ + + ]: 64 : for (int i = 0; i < command->argc; i++)
5687 : 33 : pg_free(command->argv[i]);
1278 peter@eisentraut.org 5688 : 31 : pg_free(command->varprefix);
5689 : :
5690 : : /*
5691 : : * It should also free expr recursively, but this is currently not needed
5692 : : * as only gset commands (which do not have an expression) are freed.
5693 : : */
2532 alvherre@alvh.no-ip. 5694 : 31 : pg_free(command);
5695 : 31 : }
5696 : :
5697 : : /*
5698 : : * Once an SQL command is fully parsed, possibly by accumulating several
5699 : : * parts, complete other fields of the Command structure.
5700 : : */
5701 : : static void
5702 : 294 : postprocess_sql_command(Command *my_command)
5703 : : {
5704 : : char buffer[128];
5705 : : static int prepnum = 0;
5706 : :
5707 [ - + ]: 294 : Assert(my_command->type == SQL_COMMAND);
5708 : :
5709 : : /* Save the first line for error display. */
5710 : 294 : strlcpy(buffer, my_command->lines.data, sizeof(buffer));
5711 : 294 : buffer[strcspn(buffer, "\n\r")] = '\0';
5712 : 294 : my_command->first_line = pg_strdup(buffer);
5713 : :
5714 : : /* Parse query and generate prepared statement name, if necessary */
5715 [ + + + - ]: 294 : switch (querymode)
5716 : : {
5717 : 200 : case QUERY_SIMPLE:
5718 : 200 : my_command->argv[0] = my_command->lines.data;
5719 : 200 : my_command->argc++;
5720 : 200 : break;
5721 : 52 : case QUERY_PREPARED:
1029 5722 : 52 : my_command->prepname = psprintf("P_%d", prepnum++);
5723 : : /* fall through */
5724 : 94 : case QUERY_EXTENDED:
2532 5725 [ + + ]: 94 : if (!parseQuery(my_command))
5726 : 1 : exit(1);
5727 : 93 : break;
2532 alvherre@alvh.no-ip. 5728 :UBC 0 : default:
5729 : 0 : exit(1);
5730 : : }
2532 alvherre@alvh.no-ip. 5731 :CBC 293 : }
5732 : :
5733 : : /*
5734 : : * Parse a backslash command; return a Command struct, or NULL if comment
5735 : : *
5736 : : * At call, we have scanned only the initial backslash.
5737 : : */
5738 : : static Command *
292 tgl@sss.pgh.pa.us 5739 : 532 : process_backslash_command(PsqlScanState sstate, const char *source,
5740 : : int lineno, int start_offset)
5741 : : {
5742 : : Command *my_command;
5743 : : PQExpBufferData word_buf;
5744 : : int word_offset;
5745 : : int offsets[MAX_ARGS]; /* offsets of argument words */
5746 : : int j;
5747 : :
3558 5748 : 532 : initPQExpBuffer(&word_buf);
5749 : :
5750 : : /* Collect first word of command */
5751 [ - + ]: 532 : if (!expr_lex_one_word(sstate, &word_buf, &word_offset))
5752 : : {
3558 tgl@sss.pgh.pa.us 5753 :UBC 0 : termPQExpBuffer(&word_buf);
7378 ishii@postgresql.org 5754 : 0 : return NULL;
5755 : : }
5756 : :
5757 : : /* Allocate and initialize Command structure */
3558 tgl@sss.pgh.pa.us 5758 :CBC 532 : my_command = (Command *) pg_malloc0(sizeof(Command));
5759 : 532 : my_command->type = META_COMMAND;
5760 : 532 : my_command->argc = 0;
5761 : 532 : initSimpleStats(&my_command->stats);
5762 : :
5763 : : /* Save first word (command name) */
5764 : 532 : j = 0;
5765 : 532 : offsets[j] = word_offset;
5766 : 532 : my_command->argv[j++] = pg_strdup(word_buf.data);
5767 : 532 : my_command->argc++;
5768 : :
5769 : : /* ... and convert it to enum form */
2966 5770 : 532 : my_command->meta = getMetaCommand(my_command->argv[0]);
5771 : :
2826 teodor@sigaev.ru 5772 [ + + ]: 532 : if (my_command->meta == META_SET ||
5773 [ + + ]: 169 : my_command->meta == META_IF ||
5774 [ + + ]: 145 : my_command->meta == META_ELIF)
5775 : : {
5776 : : yyscan_t yyscanner;
5777 : :
5778 : : /* For \set, collect var name */
5779 [ + + ]: 400 : if (my_command->meta == META_SET)
5780 : : {
5781 [ + + ]: 363 : if (!expr_lex_one_word(sstate, &word_buf, &word_offset))
2532 alvherre@alvh.no-ip. 5782 : 1 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5783 : : "missing argument", NULL, -1);
5784 : :
2826 teodor@sigaev.ru 5785 : 362 : offsets[j] = word_offset;
5786 : 362 : my_command->argv[j++] = pg_strdup(word_buf.data);
5787 : 362 : my_command->argc++;
5788 : : }
5789 : :
5790 : : /* then for all parse the expression */
3558 tgl@sss.pgh.pa.us 5791 : 399 : yyscanner = expr_scanner_init(sstate, source, lineno, start_offset,
5792 : 399 : my_command->argv[0]);
5793 : :
326 peter@eisentraut.org 5794 [ - + ]: 399 : if (expr_yyparse(&my_command->expr, yyscanner) != 0)
5795 : : {
5796 : : /* dead code: exit done from syntax_error called by yyerror */
3558 tgl@sss.pgh.pa.us 5797 :UBC 0 : exit(1);
5798 : : }
5799 : :
5800 : : /* Save line, trimming any trailing newline */
2532 alvherre@alvh.no-ip. 5801 :CBC 380 : my_command->first_line =
5802 : 380 : expr_scanner_get_substring(sstate,
5803 : : start_offset,
5804 : : true);
5805 : :
3558 tgl@sss.pgh.pa.us 5806 : 380 : expr_scanner_finish(yyscanner);
5807 : :
5808 : 380 : termPQExpBuffer(&word_buf);
5809 : :
5810 : 380 : return my_command;
5811 : : }
5812 : :
5813 : : /* For all other commands, collect remaining words. */
5814 [ + + ]: 423 : while (expr_lex_one_word(sstate, &word_buf, &word_offset))
5815 : : {
5816 : : /*
5817 : : * my_command->argv[0] is the command itself, so the max number of
5818 : : * arguments is one less than MAX_ARGS
5819 : : */
5820 [ + + ]: 292 : if (j >= MAX_ARGS)
2532 alvherre@alvh.no-ip. 5821 : 1 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5822 : : "too many arguments", NULL, -1);
5823 : :
3558 tgl@sss.pgh.pa.us 5824 : 291 : offsets[j] = word_offset;
5825 : 291 : my_command->argv[j++] = pg_strdup(word_buf.data);
5826 : 291 : my_command->argc++;
5827 : : }
5828 : :
5829 : : /* Save line, trimming any trailing newline */
2532 alvherre@alvh.no-ip. 5830 : 131 : my_command->first_line =
5831 : 131 : expr_scanner_get_substring(sstate,
5832 : : start_offset,
5833 : : true);
5834 : :
2966 tgl@sss.pgh.pa.us 5835 [ + + ]: 131 : if (my_command->meta == META_SLEEP)
5836 : : {
3558 5837 [ + + ]: 9 : if (my_command->argc < 2)
2532 alvherre@alvh.no-ip. 5838 : 1 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5839 : : "missing argument", NULL, -1);
5840 : :
3558 tgl@sss.pgh.pa.us 5841 [ + + ]: 8 : if (my_command->argc > 3)
2532 alvherre@alvh.no-ip. 5842 : 1 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5843 : : "too many arguments", NULL,
3558 tgl@sss.pgh.pa.us 5844 : 1 : offsets[3] - start_offset);
5845 : :
5846 : : /*
5847 : : * Split argument into number and unit to allow "sleep 1ms" etc. We
5848 : : * don't have to terminate the number argument with null because it
5849 : : * will be parsed with atoi, which ignores trailing non-digit
5850 : : * characters.
5851 : : */
1730 fujii@postgresql.org 5852 [ + + ]: 7 : if (my_command->argv[1][0] != ':')
5853 : : {
3558 tgl@sss.pgh.pa.us 5854 : 4 : char *c = my_command->argv[1];
1730 fujii@postgresql.org 5855 : 4 : bool have_digit = false;
5856 : :
5857 : : /* Skip sign */
5858 [ + - - + ]: 4 : if (*c == '+' || *c == '-')
1730 fujii@postgresql.org 5859 :UBC 0 : c++;
5860 : :
5861 : : /* Require at least one digit */
1730 fujii@postgresql.org 5862 [ + - + - ]:CBC 4 : if (*c && isdigit((unsigned char) *c))
5863 : 4 : have_digit = true;
5864 : :
5865 : : /* Eat all digits */
5866 [ + + + + ]: 10 : while (*c && isdigit((unsigned char) *c))
3558 tgl@sss.pgh.pa.us 5867 : 6 : c++;
5868 : :
5869 [ + + ]: 4 : if (*c)
5870 : : {
1730 fujii@postgresql.org 5871 [ + - + - ]: 1 : if (my_command->argc == 2 && have_digit)
5872 : : {
5873 : 1 : my_command->argv[2] = c;
5874 : 1 : offsets[2] = offsets[1] + (c - my_command->argv[1]);
5875 : 1 : my_command->argc = 3;
5876 : : }
5877 : : else
5878 : : {
5879 : : /*
5880 : : * Raise an error if argument starts with non-digit
5881 : : * character (after sign).
5882 : : */
1730 fujii@postgresql.org 5883 :UBC 0 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5884 : : "invalid sleep time, must be an integer",
5885 : 0 : my_command->argv[1], offsets[1] - start_offset);
5886 : : }
5887 : : }
5888 : : }
5889 : :
3558 tgl@sss.pgh.pa.us 5890 [ + + ]:CBC 7 : if (my_command->argc == 3)
5891 : : {
5892 [ + + + + ]: 9 : if (pg_strcasecmp(my_command->argv[2], "us") != 0 &&
5893 [ + + ]: 6 : pg_strcasecmp(my_command->argv[2], "ms") != 0 &&
5894 : 2 : pg_strcasecmp(my_command->argv[2], "s") != 0)
2532 alvherre@alvh.no-ip. 5895 : 1 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5896 : : "unrecognized time unit, must be us, ms or s",
3558 tgl@sss.pgh.pa.us 5897 : 1 : my_command->argv[2], offsets[2] - start_offset);
5898 : : }
5899 : : }
2966 5900 [ + + ]: 122 : else if (my_command->meta == META_SETSHELL)
5901 : : {
3558 5902 [ + + ]: 4 : if (my_command->argc < 3)
2532 alvherre@alvh.no-ip. 5903 : 1 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5904 : : "missing argument", NULL, -1);
5905 : : }
2966 tgl@sss.pgh.pa.us 5906 [ + + ]: 118 : else if (my_command->meta == META_SHELL)
5907 : : {
3558 5908 [ + + ]: 4 : if (my_command->argc < 2)
2532 alvherre@alvh.no-ip. 5909 : 1 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5910 : : "missing command", NULL, -1);
5911 : : }
1737 5912 [ + + + + ]: 114 : else if (my_command->meta == META_ELSE || my_command->meta == META_ENDIF ||
5913 [ + + ]: 79 : my_command->meta == META_STARTPIPELINE ||
692 michael@paquier.xyz 5914 [ + + ]: 58 : my_command->meta == META_ENDPIPELINE ||
5915 [ + + ]: 41 : my_command->meta == META_SYNCPIPELINE)
5916 : : {
2826 teodor@sigaev.ru 5917 [ + + ]: 78 : if (my_command->argc != 1)
2532 alvherre@alvh.no-ip. 5918 : 2 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5919 : : "unexpected argument", NULL, -1);
5920 : : }
2083 michael@paquier.xyz 5921 [ + + + + ]: 36 : else if (my_command->meta == META_GSET || my_command->meta == META_ASET)
5922 : : {
2532 alvherre@alvh.no-ip. 5923 [ + + ]: 35 : if (my_command->argc > 2)
5924 : 1 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5925 : : "too many arguments", NULL, -1);
5926 : : }
5927 : : else
5928 : : {
5929 : : /* my_command->meta == META_NONE */
5930 : 1 : syntax_error(source, lineno, my_command->first_line, my_command->argv[0],
5931 : : "invalid command", NULL, -1);
5932 : : }
5933 : :
3558 tgl@sss.pgh.pa.us 5934 : 122 : termPQExpBuffer(&word_buf);
5935 : :
5936 : 122 : return my_command;
5937 : : }
5938 : :
5939 : : static void
2826 teodor@sigaev.ru 5940 : 6 : ConditionError(const char *desc, int cmdn, const char *msg)
5941 : : {
1348 tgl@sss.pgh.pa.us 5942 : 6 : pg_fatal("condition error in script \"%s\" command %d: %s",
5943 : : desc, cmdn, msg);
5944 : : }
5945 : :
5946 : : /*
5947 : : * Partial evaluation of conditionals before recording and running the script.
5948 : : */
5949 : : static void
1360 5950 : 250 : CheckConditional(const ParsedScript *ps)
5951 : : {
5952 : : /* statically check conditional structure */
2826 teodor@sigaev.ru 5953 : 250 : ConditionalStack cs = conditional_stack_create();
5954 : : int i;
5955 : :
1360 tgl@sss.pgh.pa.us 5956 [ + + ]: 1128 : for (i = 0; ps->commands[i] != NULL; i++)
5957 : : {
5958 : 883 : Command *cmd = ps->commands[i];
5959 : :
2826 teodor@sigaev.ru 5960 [ + + ]: 883 : if (cmd->type == META_COMMAND)
5961 : : {
5962 [ + + + + : 461 : switch (cmd->meta)
+ ]
5963 : : {
2791 tgl@sss.pgh.pa.us 5964 : 20 : case META_IF:
5965 : 20 : conditional_stack_push(cs, IFSTATE_FALSE);
5966 : 20 : break;
5967 : 12 : case META_ELIF:
5968 [ + + ]: 12 : if (conditional_stack_empty(cs))
1360 5969 : 1 : ConditionError(ps->desc, i + 1, "\\elif without matching \\if");
2791 5970 [ + + ]: 11 : if (conditional_stack_peek(cs) == IFSTATE_ELSE_FALSE)
1360 5971 : 1 : ConditionError(ps->desc, i + 1, "\\elif after \\else");
2791 5972 : 10 : break;
5973 : 13 : case META_ELSE:
5974 [ + + ]: 13 : if (conditional_stack_empty(cs))
1360 5975 : 1 : ConditionError(ps->desc, i + 1, "\\else without matching \\if");
2791 5976 [ + + ]: 12 : if (conditional_stack_peek(cs) == IFSTATE_ELSE_FALSE)
1360 5977 : 1 : ConditionError(ps->desc, i + 1, "\\else after \\else");
2791 5978 : 11 : conditional_stack_poke(cs, IFSTATE_ELSE_FALSE);
5979 : 11 : break;
5980 : 18 : case META_ENDIF:
5981 [ + + ]: 18 : if (!conditional_stack_pop(cs))
1360 5982 : 1 : ConditionError(ps->desc, i + 1, "\\endif without matching \\if");
2791 5983 : 17 : break;
5984 : 398 : default:
5985 : : /* ignore anything else... */
5986 : 398 : break;
5987 : : }
5988 : : }
5989 : : }
2826 teodor@sigaev.ru 5990 [ + + ]: 245 : if (!conditional_stack_empty(cs))
1360 tgl@sss.pgh.pa.us 5991 : 1 : ConditionError(ps->desc, i + 1, "\\if without matching \\endif");
2826 teodor@sigaev.ru 5992 : 244 : conditional_stack_destroy(cs);
5993 : 244 : }
5994 : :
5995 : : /*
5996 : : * Parse a script (either the contents of a file, or a built-in script)
5997 : : * and add it to the list of scripts.
5998 : : */
5999 : : static void
3558 tgl@sss.pgh.pa.us 6000 : 285 : ParseScript(const char *script, const char *desc, int weight)
6001 : : {
6002 : : ParsedScript ps;
6003 : : PsqlScanState sstate;
6004 : : PQExpBufferData line_buf;
6005 : : int alloc_num;
6006 : : int index;
6007 : :
6008 : : #define COMMANDS_ALLOC_NUM 128
6009 : 285 : alloc_num = COMMANDS_ALLOC_NUM;
6010 : :
6011 : : /* Initialize all fields of ps */
6012 : 285 : ps.desc = desc;
6013 : 285 : ps.weight = weight;
6014 : 285 : ps.commands = (Command **) pg_malloc(sizeof(Command *) * alloc_num);
3270 6015 : 285 : initStats(&ps.stats, 0);
6016 : :
6017 : : /* Prepare to parse script */
3558 6018 : 285 : sstate = psql_scan_create(&pgbench_callbacks);
6019 : :
6020 : : /*
6021 : : * Ideally, we'd scan scripts using the encoding and stdstrings settings
6022 : : * we get from a DB connection. However, without major rearrangement of
6023 : : * pgbench's argument parsing, we can't have a DB connection at the time
6024 : : * we parse scripts. Using SQL_ASCII (encoding 0) should work well enough
6025 : : * with any backend-safe encoding, though conceivably we could be fooled
6026 : : * if a script file uses a client-only encoding. We also assume that
6027 : : * stdstrings should be true, which is a bit riskier.
6028 : : */
6029 : 285 : psql_scan_setup(sstate, script, strlen(script), 0, true);
6030 : :
6031 : 285 : initPQExpBuffer(&line_buf);
6032 : :
6033 : 285 : index = 0;
6034 : :
6035 : : for (;;)
6036 : 889 : {
6037 : : PsqlScanResult sr;
6038 : : promptStatus_t prompt;
2532 alvherre@alvh.no-ip. 6039 : 1174 : Command *command = NULL;
6040 : :
3558 tgl@sss.pgh.pa.us 6041 : 1174 : resetPQExpBuffer(&line_buf);
6042 : :
6043 : 1174 : sr = psql_scan(sstate, &line_buf, &prompt);
6044 : :
6045 : : /* If we collected a new SQL command, process that */
89 fujii@postgresql.org 6046 :GNC 1174 : command = create_sql_command(&line_buf);
6047 : :
6048 : : /* store new command */
2458 alvherre@alvh.no-ip. 6049 [ + + ]:CBC 1174 : if (command)
6050 : 425 : ps.commands[index++] = command;
6051 : :
6052 : : /* If we reached a backslash, process that */
3558 tgl@sss.pgh.pa.us 6053 [ + + ]: 1174 : if (sr == PSCAN_BACKSLASH)
6054 : : {
6055 : : int lineno;
6056 : : int start_offset;
6057 : :
6058 : : /* Capture location of the backslash */
292 6059 : 532 : psql_scan_get_location(sstate, &lineno, &start_offset);
6060 : 532 : start_offset--;
6061 : :
6062 : 532 : command = process_backslash_command(sstate, desc,
6063 : : lineno, start_offset);
6064 : :
3558 6065 [ + - ]: 502 : if (command)
6066 : : {
6067 : : /*
6068 : : * If this is gset or aset, merge into the preceding command.
6069 : : * (We don't use a command slot in this case).
6070 : : */
2083 michael@paquier.xyz 6071 [ + + + + ]: 502 : if (command->meta == META_GSET || command->meta == META_ASET)
3558 tgl@sss.pgh.pa.us 6072 : 31 : {
6073 : : Command *cmd;
6074 : :
2532 alvherre@alvh.no-ip. 6075 [ + + ]: 34 : if (index == 0)
6076 : 1 : syntax_error(desc, lineno, NULL, NULL,
6077 : : "\\gset must follow an SQL command",
6078 : : NULL, -1);
6079 : :
6080 : 33 : cmd = ps.commands[index - 1];
6081 : :
2458 6082 [ + + ]: 33 : if (cmd->type != SQL_COMMAND ||
6083 [ + + ]: 32 : cmd->varprefix != NULL)
2532 6084 : 2 : syntax_error(desc, lineno, NULL, NULL,
6085 : : "\\gset must follow an SQL command",
6086 : 2 : cmd->first_line, -1);
6087 : :
6088 : : /* get variable prefix */
6089 [ + + - + ]: 31 : if (command->argc <= 1 || command->argv[1][0] == '\0')
2458 6090 : 29 : cmd->varprefix = pg_strdup("");
6091 : : else
6092 : 2 : cmd->varprefix = pg_strdup(command->argv[1]);
6093 : :
6094 : : /* update the sql command meta */
2083 michael@paquier.xyz 6095 : 31 : cmd->meta = command->meta;
6096 : :
6097 : : /* cleanup unused command */
2532 alvherre@alvh.no-ip. 6098 : 31 : free_command(command);
6099 : :
6100 : 31 : continue;
6101 : : }
6102 : :
6103 : : /* Attach any other backslash command as a new command */
6104 : 468 : ps.commands[index++] = command;
6105 : : }
6106 : : }
6107 : :
6108 : : /*
6109 : : * Since we used a command slot, allocate more if needed. Note we
6110 : : * always allocate one more in order to accommodate the NULL
6111 : : * terminator below.
6112 : : */
6113 [ - + ]: 1110 : if (index >= alloc_num)
6114 : : {
2532 alvherre@alvh.no-ip. 6115 :UBC 0 : alloc_num += COMMANDS_ALLOC_NUM;
6116 : 0 : ps.commands = (Command **)
6117 : 0 : pg_realloc(ps.commands, sizeof(Command *) * alloc_num);
6118 : : }
6119 : :
6120 : : /* Done if we reached EOF */
3558 tgl@sss.pgh.pa.us 6121 [ + + + + ]:CBC 1110 : if (sr == PSCAN_INCOMPLETE || sr == PSCAN_EOL)
6122 : : break;
6123 : : }
6124 : :
6125 : 252 : ps.commands[index] = NULL;
6126 : :
1360 6127 : 252 : addScript(&ps);
6128 : :
3558 6129 : 244 : termPQExpBuffer(&line_buf);
6130 : 244 : psql_scan_finish(sstate);
6131 : 244 : psql_scan_destroy(sstate);
7378 ishii@postgresql.org 6132 : 244 : }
6133 : :
6134 : : /*
6135 : : * Read the entire contents of file fd, and return it in a malloc'd buffer.
6136 : : *
6137 : : * The buffer will typically be larger than necessary, but we don't care
6138 : : * in this program, because we'll free it as soon as we've parsed the script.
6139 : : */
6140 : : static char *
3558 tgl@sss.pgh.pa.us 6141 : 132 : read_file_contents(FILE *fd)
6142 : : {
6143 : : char *buf;
4414 6144 : 132 : size_t buflen = BUFSIZ;
6145 : 132 : size_t used = 0;
6146 : :
3558 6147 : 132 : buf = (char *) pg_malloc(buflen);
6148 : :
6149 : : for (;;)
4414 tgl@sss.pgh.pa.us 6150 :UBC 0 : {
6151 : : size_t nread;
6152 : :
3558 tgl@sss.pgh.pa.us 6153 :CBC 132 : nread = fread(buf + used, 1, BUFSIZ, fd);
6154 : 132 : used += nread;
6155 : : /* If fread() read less than requested, must be EOF or error */
6156 [ + - ]: 132 : if (nread < BUFSIZ)
4414 6157 : 132 : break;
6158 : : /* Enlarge buf so we can read some more */
4414 tgl@sss.pgh.pa.us 6159 :UBC 0 : buflen += BUFSIZ;
6160 : 0 : buf = (char *) pg_realloc(buf, buflen);
6161 : : }
6162 : : /* There is surely room for a terminator */
3558 tgl@sss.pgh.pa.us 6163 :CBC 132 : buf[used] = '\0';
6164 : :
6165 : 132 : return buf;
6166 : : }
6167 : :
6168 : : /*
6169 : : * Given a file name, read it and add its script to the list.
6170 : : * "-" means to read stdin.
6171 : : * NB: filename must be storage that won't disappear.
6172 : : */
6173 : : static void
6174 : 133 : process_file(const char *filename, int weight)
6175 : : {
6176 : : FILE *fd;
6177 : : char *buf;
6178 : :
6179 : : /* Slurp the file contents into "buf" */
7378 ishii@postgresql.org 6180 [ - + ]: 133 : if (strcmp(filename, "-") == 0)
7378 ishii@postgresql.org 6181 :UBC 0 : fd = stdin;
7378 ishii@postgresql.org 6182 [ + + ]:CBC 133 : else if ((fd = fopen(filename, "r")) == NULL)
1348 tgl@sss.pgh.pa.us 6183 : 1 : pg_fatal("could not open file \"%s\": %m", filename);
6184 : :
3558 6185 : 132 : buf = read_file_contents(fd);
6186 : :
6187 [ - + ]: 132 : if (ferror(fd))
1348 tgl@sss.pgh.pa.us 6188 :UBC 0 : pg_fatal("could not read file \"%s\": %m", filename);
6189 : :
3558 tgl@sss.pgh.pa.us 6190 [ + - ]:CBC 132 : if (fd != stdin)
6191 : 132 : fclose(fd);
6192 : :
6193 : 132 : ParseScript(buf, filename, weight);
6194 : :
6195 : 92 : free(buf);
7378 ishii@postgresql.org 6196 : 92 : }
6197 : :
6198 : : /* Parse the given builtin script and add it to the list. */
6199 : : static void
3558 tgl@sss.pgh.pa.us 6200 : 153 : process_builtin(const BuiltinScript *bi, int weight)
6201 : : {
6202 : 153 : ParseScript(bi->script, bi->desc, weight);
7383 ishii@postgresql.org 6203 : 152 : }
6204 : :
6205 : : /* show available builtin scripts */
6206 : : static void
3611 alvherre@alvh.no-ip. 6207 : 3 : listAvailableScripts(void)
6208 : : {
6209 : : int i;
6210 : :
6211 : 3 : fprintf(stderr, "Available builtin scripts:\n");
3559 6212 [ + + ]: 12 : for (i = 0; i < lengthof(builtin_script); i++)
2345 tmunro@postgresql.or 6213 : 9 : fprintf(stderr, " %13s: %s\n", builtin_script[i].name, builtin_script[i].desc);
3611 alvherre@alvh.no-ip. 6214 : 3 : fprintf(stderr, "\n");
6215 : 3 : }
6216 : :
6217 : : /* return builtin script "name" if unambiguous, fails if not found */
6218 : : static const BuiltinScript *
3559 6219 : 156 : findBuiltin(const char *name)
6220 : : {
6221 : : int i,
3575 6222 : 156 : found = 0,
6223 : 156 : len = strlen(name);
3558 tgl@sss.pgh.pa.us 6224 : 156 : const BuiltinScript *result = NULL;
6225 : :
3559 alvherre@alvh.no-ip. 6226 [ + + ]: 624 : for (i = 0; i < lengthof(builtin_script); i++)
6227 : : {
3575 6228 [ + + ]: 468 : if (strncmp(builtin_script[i].name, name, len) == 0)
6229 : : {
3559 6230 : 156 : result = &builtin_script[i];
3575 6231 : 156 : found++;
6232 : : }
6233 : : }
6234 : :
6235 : : /* ok, unambiguous result */
6236 [ + + ]: 156 : if (found == 1)
3559 6237 : 154 : return result;
6238 : :
6239 : : /* error cases */
3575 6240 [ + + ]: 2 : if (found == 0)
1348 tgl@sss.pgh.pa.us 6241 : 1 : pg_log_error("no builtin script found for name \"%s\"", name);
6242 : : else /* found > 1 */
6243 : 1 : pg_log_error("ambiguous builtin name: %d builtin scripts found for prefix \"%s\"", found, name);
6244 : :
3611 alvherre@alvh.no-ip. 6245 : 2 : listAvailableScripts();
6246 : 2 : exit(1);
6247 : : }
6248 : :
6249 : : /*
6250 : : * Determine the weight specification from a script option (-b, -f), if any,
6251 : : * and return it as an integer (1 is returned if there's no weight). The
6252 : : * script name is returned in *script as a malloc'd string.
6253 : : */
6254 : : static int
3559 6255 : 144 : parseScriptWeight(const char *option, char **script)
6256 : : {
6257 : : const char *sep;
6258 : : int weight;
6259 : :
6260 [ + + ]: 144 : if ((sep = strrchr(option, WSEP)))
6261 : : {
6262 : 9 : int namelen = sep - option;
6263 : : long wtmp;
6264 : : char *badp;
6265 : :
6266 : : /* generate the script name */
6267 : 9 : *script = pg_malloc(namelen + 1);
6268 : 9 : strncpy(*script, option, namelen);
6269 : 9 : (*script)[namelen] = '\0';
6270 : :
6271 : : /* process digits of the weight spec */
6272 : 9 : errno = 0;
6273 : 9 : wtmp = strtol(sep + 1, &badp, 10);
6274 [ + - + + : 9 : if (errno != 0 || badp == sep + 1 || *badp != '\0')
- + ]
1348 tgl@sss.pgh.pa.us 6275 : 1 : pg_fatal("invalid weight specification: %s", sep);
3549 alvherre@alvh.no-ip. 6276 [ + - + + ]: 8 : if (wtmp > INT_MAX || wtmp < 0)
7 peter@eisentraut.org 6277 :GNC 1 : pg_fatal("weight specification out of range (0 .. %d): %ld",
6278 : : INT_MAX, wtmp);
3559 alvherre@alvh.no-ip. 6279 :CBC 7 : weight = wtmp;
6280 : : }
6281 : : else
6282 : : {
6283 : 135 : *script = pg_strdup(option);
6284 : 135 : weight = 1;
6285 : : }
6286 : :
6287 : 142 : return weight;
6288 : : }
6289 : :
6290 : : /* append a script to the list of scripts to process */
6291 : : static void
1360 tgl@sss.pgh.pa.us 6292 : 252 : addScript(const ParsedScript *script)
6293 : : {
6294 [ + - + + ]: 252 : if (script->commands == NULL || script->commands[0] == NULL)
1348 6295 : 1 : pg_fatal("empty command list for script \"%s\"", script->desc);
6296 : :
3611 alvherre@alvh.no-ip. 6297 [ + + ]: 251 : if (num_scripts >= MAX_SCRIPTS)
1348 tgl@sss.pgh.pa.us 6298 : 1 : pg_fatal("at most %d SQL scripts are allowed", MAX_SCRIPTS);
6299 : :
2826 teodor@sigaev.ru 6300 : 250 : CheckConditional(script);
6301 : :
1360 tgl@sss.pgh.pa.us 6302 : 244 : sql_script[num_scripts] = *script;
3611 alvherre@alvh.no-ip. 6303 : 244 : num_scripts++;
6304 : 244 : }
6305 : :
6306 : : /*
6307 : : * Print progress report.
6308 : : *
6309 : : * On entry, *last and *last_report contain the statistics and time of last
6310 : : * progress report. On exit, they are updated with the new stats.
6311 : : */
6312 : : static void
1742 tmunro@postgresql.or 6313 :UBC 0 : printProgressReport(TState *threads, int64 test_start, pg_time_usec_t now,
6314 : : StatsData *last, int64 *last_report)
6315 : : {
6316 : : /* generate and show report */
6317 : 0 : pg_time_usec_t run = now - *last_report;
6318 : : int64 cnt,
6319 : : failures,
6320 : : retried;
6321 : : double tps,
6322 : : total_run,
6323 : : latency,
6324 : : sqlat,
6325 : : lag,
6326 : : stdev;
6327 : : char tbuf[315];
6328 : : StatsData cur;
6329 : :
6330 : : /*
6331 : : * Add up the statistics of all threads.
6332 : : *
6333 : : * XXX: No locking. There is no guarantee that we get an atomic snapshot
6334 : : * of the transaction count and latencies, so these figures can well be
6335 : : * off by a small amount. The progress report's purpose is to give a
6336 : : * quick overview of how the test is going, so that shouldn't matter too
6337 : : * much. (If a read from a 64-bit integer is not atomic, you might get a
6338 : : * "torn" read and completely bogus latencies though!)
6339 : : */
2458 heikki.linnakangas@i 6340 : 0 : initStats(&cur, 0);
6341 [ # # ]: 0 : for (int i = 0; i < nthreads; i++)
6342 : : {
2449 tgl@sss.pgh.pa.us 6343 : 0 : mergeSimpleStats(&cur.latency, &threads[i].stats.latency);
6344 : 0 : mergeSimpleStats(&cur.lag, &threads[i].stats.lag);
6345 : 0 : cur.cnt += threads[i].stats.cnt;
6346 : 0 : cur.skipped += threads[i].stats.skipped;
1364 ishii@postgresql.org 6347 : 0 : cur.retries += threads[i].stats.retries;
6348 : 0 : cur.retried += threads[i].stats.retried;
6349 : 0 : cur.serialization_failures +=
6350 : 0 : threads[i].stats.serialization_failures;
6351 : 0 : cur.deadlock_failures += threads[i].stats.deadlock_failures;
39 fujii@postgresql.org 6352 :UNC 0 : cur.other_sql_failures += threads[i].stats.other_sql_failures;
6353 : : }
6354 : :
6355 : : /* we count only actually executed transactions */
1364 ishii@postgresql.org 6356 :UBC 0 : cnt = cur.cnt - last->cnt;
2458 heikki.linnakangas@i 6357 : 0 : total_run = (now - test_start) / 1000000.0;
1364 ishii@postgresql.org 6358 : 0 : tps = 1000000.0 * cnt / run;
6359 [ # # ]: 0 : if (cnt > 0)
6360 : : {
6361 : 0 : latency = 0.001 * (cur.latency.sum - last->latency.sum) / cnt;
6362 : 0 : sqlat = 1.0 * (cur.latency.sum2 - last->latency.sum2) / cnt;
2458 heikki.linnakangas@i 6363 : 0 : stdev = 0.001 * sqrt(sqlat - 1000000.0 * latency * latency);
1364 ishii@postgresql.org 6364 : 0 : lag = 0.001 * (cur.lag.sum - last->lag.sum) / cnt;
6365 : : }
6366 : : else
6367 : : {
2458 heikki.linnakangas@i 6368 : 0 : latency = sqlat = stdev = lag = 0;
6369 : : }
1364 ishii@postgresql.org 6370 : 0 : failures = getFailures(&cur) - getFailures(last);
6371 : 0 : retried = cur.retried - last->retried;
6372 : :
2458 heikki.linnakangas@i 6373 [ # # ]: 0 : if (progress_timestamp)
6374 : : {
1619 tmunro@postgresql.or 6375 : 0 : snprintf(tbuf, sizeof(tbuf), "%.3f s",
6376 : 0 : PG_TIME_GET_DOUBLE(now + epoch_shift));
6377 : : }
6378 : : else
6379 : : {
6380 : : /* round seconds are expected, but the thread may be late */
2458 heikki.linnakangas@i 6381 : 0 : snprintf(tbuf, sizeof(tbuf), "%.1f s", total_run);
6382 : : }
6383 : :
6384 : 0 : fprintf(stderr,
6385 : : "progress: %s, %.1f tps, lat %.3f ms stddev %.3f, " INT64_FORMAT " failed",
6386 : : tbuf, tps, latency, stdev, failures);
6387 : :
6388 [ # # ]: 0 : if (throttle_delay)
6389 : : {
6390 : 0 : fprintf(stderr, ", lag %.3f ms", lag);
6391 [ # # ]: 0 : if (latency_limit)
6392 : 0 : fprintf(stderr, ", " INT64_FORMAT " skipped",
6393 : 0 : cur.skipped - last->skipped);
6394 : : }
6395 : :
6396 : : /* it can be non-zero only if max_tries is not equal to one */
1364 ishii@postgresql.org 6397 [ # # ]: 0 : if (max_tries != 1)
6398 : 0 : fprintf(stderr,
6399 : : ", " INT64_FORMAT " retried, " INT64_FORMAT " retries",
6400 : 0 : retried, cur.retries - last->retries);
2458 heikki.linnakangas@i 6401 : 0 : fprintf(stderr, "\n");
6402 : :
6403 : 0 : *last = cur;
6404 : 0 : *last_report = now;
6405 : 0 : }
6406 : :
6407 : : static void
2968 peter_e@gmx.net 6408 :CBC 15 : printSimpleStats(const char *prefix, SimpleStats *ss)
6409 : : {
2947 tgl@sss.pgh.pa.us 6410 [ + - ]: 15 : if (ss->count > 0)
6411 : : {
6412 : 15 : double latency = ss->sum / ss->count;
6413 : 15 : double stddev = sqrt(ss->sum2 / ss->count - latency * latency);
6414 : :
6415 : 15 : printf("%s average = %.3f ms\n", prefix, 0.001 * latency);
6416 : 15 : printf("%s stddev = %.3f ms\n", prefix, 0.001 * stddev);
6417 : : }
3609 alvherre@alvh.no-ip. 6418 : 15 : }
6419 : :
6420 : : /* print version banner */
6421 : : static void
1642 tgl@sss.pgh.pa.us 6422 : 89 : printVersion(PGconn *con)
6423 : : {
6424 : 89 : int server_ver = PQserverVersion(con);
6425 : 89 : int client_ver = PG_VERSION_NUM;
6426 : :
6427 [ - + ]: 89 : if (server_ver != client_ver)
6428 : : {
6429 : : const char *server_version;
6430 : : char sverbuf[32];
6431 : :
6432 : : /* Try to get full text form, might include "devel" etc */
1642 tgl@sss.pgh.pa.us 6433 :UBC 0 : server_version = PQparameterStatus(con, "server_version");
6434 : : /* Otherwise fall back on server_ver */
6435 [ # # ]: 0 : if (!server_version)
6436 : : {
6437 : 0 : formatPGVersionNumber(server_ver, true,
6438 : : sverbuf, sizeof(sverbuf));
6439 : 0 : server_version = sverbuf;
6440 : : }
6441 : :
6442 : 0 : printf(_("%s (%s, server %s)\n"),
6443 : : "pgbench", PG_VERSION, server_version);
6444 : : }
6445 : : /* For version match, only print pgbench version */
6446 : : else
1642 tgl@sss.pgh.pa.us 6447 :CBC 89 : printf("%s (%s)\n", "pgbench", PG_VERSION);
6448 : 89 : fflush(stdout);
6449 : 89 : }
6450 : :
6451 : : /* print out results */
6452 : : static void
1742 tmunro@postgresql.or 6453 : 87 : printResults(StatsData *total,
6454 : : pg_time_usec_t total_duration, /* benchmarking time */
6455 : : pg_time_usec_t conn_total_duration, /* is_connect */
6456 : : pg_time_usec_t conn_elapsed_duration, /* !is_connect */
6457 : : int64 latency_late)
6458 : : {
6459 : : /* tps is about actually executed transactions during benchmarking */
1364 ishii@postgresql.org 6460 : 87 : int64 failures = getFailures(total);
6461 : 87 : int64 total_cnt = total->cnt + total->skipped + failures;
1742 tmunro@postgresql.or 6462 : 87 : double bench_duration = PG_TIME_GET_DOUBLE(total_duration);
1364 ishii@postgresql.org 6463 : 87 : double tps = total->cnt / bench_duration;
6464 : :
6465 : : /* Report test parameters. */
3611 alvherre@alvh.no-ip. 6466 [ + + ]: 87 : printf("transaction type: %s\n",
6467 : : num_scripts == 1 ? sql_script[0].desc : "multiple scripts");
7034 ishii@postgresql.org 6468 : 87 : printf("scaling factor: %d\n", scale);
6469 : : /* only print partitioning information if some partitioning was detected */
2268 akapila@postgresql.o 6470 [ + + ]: 87 : if (partition_method != PART_NONE)
6471 : 6 : printf("partition method: %s\npartitions: %d\n",
6472 : : PARTITION_METHOD[partition_method], partitions);
6481 ishii@postgresql.org 6473 : 87 : printf("query mode: %s\n", QUERYMODE[querymode]);
9379 bruce@momjian.us 6474 : 87 : printf("number of clients: %d\n", nclients);
5979 ishii@postgresql.org 6475 : 87 : printf("number of threads: %d\n", nthreads);
6476 : :
1364 6477 [ + - ]: 87 : if (max_tries)
1336 peter@eisentraut.org 6478 : 87 : printf("maximum number of tries: %u\n", max_tries);
6479 : :
6305 tgl@sss.pgh.pa.us 6480 [ + - ]: 87 : if (duration <= 0)
6481 : : {
6482 : 87 : printf("number of transactions per client: %d\n", nxacts);
3609 alvherre@alvh.no-ip. 6483 : 87 : printf("number of transactions actually processed: " INT64_FORMAT "/%d\n",
6484 : : total->cnt, nxacts * nclients);
6485 : : }
6486 : : else
6487 : : {
6305 tgl@sss.pgh.pa.us 6488 :UBC 0 : printf("duration: %d s\n", duration);
4223 6489 : 0 : printf("number of transactions actually processed: " INT64_FORMAT "\n",
6490 : : total->cnt);
6491 : : }
6492 : :
6493 : : /*
6494 : : * Remaining stats are nonsensical if we failed to execute any xacts due
6495 : : * to other than serialization or deadlock errors and --continue-on-error
6496 : : * is not set.
6497 : : */
431 ishii@postgresql.org 6498 [ + + ]:CBC 87 : if (total_cnt <= 0)
6499 : 51 : return;
6500 : :
1364 6501 : 36 : printf("number of failed transactions: " INT64_FORMAT " (%.3f%%)\n",
6502 : : failures, 100.0 * failures / total_cnt);
6503 : :
6504 [ + + ]: 36 : if (failures_detailed)
6505 : : {
1364 ishii@postgresql.org 6506 :GBC 1 : printf("number of serialization failures: " INT64_FORMAT " (%.3f%%)\n",
6507 : : total->serialization_failures,
6508 : : 100.0 * total->serialization_failures / total_cnt);
6509 : 1 : printf("number of deadlock failures: " INT64_FORMAT " (%.3f%%)\n",
6510 : : total->deadlock_failures,
6511 : : 100.0 * total->deadlock_failures / total_cnt);
39 fujii@postgresql.org 6512 :GNC 1 : printf("number of other failures: " INT64_FORMAT " (%.3f%%)\n",
6513 : : total->other_sql_failures,
6514 : : 100.0 * total->other_sql_failures / total_cnt);
6515 : : }
6516 : :
6517 : : /* it can be non-zero only if max_tries is not equal to one */
1364 ishii@postgresql.org 6518 [ + + ]:CBC 36 : if (max_tries != 1)
6519 : : {
6520 : 2 : printf("number of transactions retried: " INT64_FORMAT " (%.3f%%)\n",
6521 : : total->retried, 100.0 * total->retried / total_cnt);
6522 : 2 : printf("total number of retries: " INT64_FORMAT "\n", total->retries);
6523 : : }
6524 : :
4082 heikki.linnakangas@i 6525 [ + + + - ]: 36 : if (throttle_delay && latency_limit)
1385 ishii@postgresql.org 6526 : 2 : printf("number of transactions skipped: " INT64_FORMAT " (%.3f%%)\n",
6527 : : total->skipped, 100.0 * total->skipped / total_cnt);
6528 : :
4082 heikki.linnakangas@i 6529 [ + + ]: 36 : if (latency_limit)
1385 ishii@postgresql.org 6530 [ + - ]: 2 : printf("number of transactions above the %.1f ms latency limit: " INT64_FORMAT "/" INT64_FORMAT " (%.3f%%)\n",
6531 : : latency_limit / 1000.0, latency_late, total->cnt,
6532 : : (total->cnt > 0) ? 100.0 * latency_late / total->cnt : 0.0);
6533 : :
4082 heikki.linnakangas@i 6534 [ + + + - : 36 : if (throttle_delay || progress || latency_limit)
- + ]
3609 alvherre@alvh.no-ip. 6535 : 2 : printSimpleStats("latency", &total->latency);
6536 : : else
6537 : : {
6538 : : /* no measurement, show average latency computed from run time */
1364 ishii@postgresql.org 6539 [ + + ]: 34 : printf("latency average = %.3f ms%s\n",
6540 : : 0.001 * total_duration * nclients / total_cnt,
6541 : : failures > 0 ? " (including failures)" : "");
6542 : : }
6543 : :
4529 6544 [ + + ]: 36 : if (throttle_delay)
6545 : : {
6546 : : /*
6547 : : * Report average transaction lag under rate limit throttling. This
6548 : : * is the delay between scheduled and actual start times for the
6549 : : * transaction. The measured lag may be caused by thread/client load,
6550 : : * the database load, or the Poisson throttling process.
6551 : : */
4455 noah@leadboat.com 6552 : 2 : printf("rate limit schedule lag: avg %.3f (max %.3f) ms\n",
6553 : : 0.001 * total->lag.sum / total->cnt, 0.001 * total->lag.max);
6554 : : }
6555 : :
6556 : : /*
6557 : : * Under -C/--connect, each transaction incurs a significant connection
6558 : : * cost, it would not make much sense to ignore it in tps, and it would
6559 : : * not be tps anyway.
6560 : : *
6561 : : * Otherwise connections are made just once at the beginning of the run
6562 : : * and should not impact performance but for very short run, so they are
6563 : : * (right)fully ignored in tps.
6564 : : */
1742 tmunro@postgresql.or 6565 [ + + ]: 36 : if (is_connect)
6566 : : {
1364 ishii@postgresql.org 6567 : 2 : printf("average connection time = %.3f ms\n", 0.001 * conn_total_duration / (total->cnt + failures));
1742 tmunro@postgresql.or 6568 : 2 : printf("tps = %f (including reconnection times)\n", tps);
6569 : : }
6570 : : else
6571 : : {
6572 : 34 : printf("initial connection time = %.3f ms\n", 0.001 * conn_elapsed_duration);
6573 : 34 : printf("tps = %f (without initial connection time)\n", tps);
6574 : : }
6575 : :
6576 : : /* Report per-script/command statistics */
2582 alvherre@alvh.no-ip. 6577 [ + + + + ]: 36 : if (per_script_stats || report_per_command)
6578 : : {
6579 : : int i;
6580 : :
3611 6581 [ + + ]: 21 : for (i = 0; i < num_scripts; i++)
6582 : : {
2947 tgl@sss.pgh.pa.us 6583 [ + + ]: 15 : if (per_script_stats)
6584 : : {
6585 : 13 : StatsData *sstats = &sql_script[i].stats;
1364 ishii@postgresql.org 6586 : 13 : int64 script_failures = getFailures(sstats);
6587 : 13 : int64 script_total_cnt =
942 tgl@sss.pgh.pa.us 6588 : 13 : sstats->cnt + sstats->skipped + script_failures;
6589 : :
3559 alvherre@alvh.no-ip. 6590 : 13 : printf("SQL script %d: %s\n"
6591 : : " - weight: %d (targets %.1f%% of total)\n"
6592 : : " - " INT64_FORMAT " transactions (%.1f%% of total)\n",
6593 : : i + 1, sql_script[i].desc,
6594 : : sql_script[i].weight,
6595 : : 100.0 * sql_script[i].weight / total_weight,
6596 : : script_total_cnt,
6597 : : 100.0 * script_total_cnt / total_cnt);
6598 : :
431 ishii@postgresql.org 6599 [ + - ]: 13 : if (script_total_cnt > 0)
6600 : : {
417 6601 : 13 : printf(" - number of transactions actually processed: " INT64_FORMAT " (tps = %f)\n",
6602 : : sstats->cnt, sstats->cnt / bench_duration);
6603 : :
431 6604 : 13 : printf(" - number of failed transactions: " INT64_FORMAT " (%.3f%%)\n",
6605 : : script_failures,
6606 : : 100.0 * script_failures / script_total_cnt);
6607 : :
6608 [ - + ]: 13 : if (failures_detailed)
6609 : : {
431 ishii@postgresql.org 6610 :UBC 0 : printf(" - number of serialization failures: " INT64_FORMAT " (%.3f%%)\n",
6611 : : sstats->serialization_failures,
6612 : : (100.0 * sstats->serialization_failures /
6613 : : script_total_cnt));
6614 : 0 : printf(" - number of deadlock failures: " INT64_FORMAT " (%.3f%%)\n",
6615 : : sstats->deadlock_failures,
6616 : : (100.0 * sstats->deadlock_failures /
6617 : : script_total_cnt));
39 fujii@postgresql.org 6618 :UNC 0 : printf(" - number of other failures: " INT64_FORMAT " (%.3f%%)\n",
6619 : : sstats->other_sql_failures,
6620 : : (100.0 * sstats->other_sql_failures /
6621 : : script_total_cnt));
6622 : : }
6623 : :
6624 : : /*
6625 : : * it can be non-zero only if max_tries is not equal to
6626 : : * one
6627 : : */
431 ishii@postgresql.org 6628 [ - + ]:CBC 13 : if (max_tries != 1)
6629 : : {
431 ishii@postgresql.org 6630 :UBC 0 : printf(" - number of transactions retried: " INT64_FORMAT " (%.3f%%)\n",
6631 : : sstats->retried,
6632 : : 100.0 * sstats->retried / script_total_cnt);
6633 : 0 : printf(" - total number of retries: " INT64_FORMAT "\n",
6634 : : sstats->retries);
6635 : : }
6636 : :
431 ishii@postgresql.org 6637 [ - + - - ]:CBC 13 : if (throttle_delay && latency_limit)
431 ishii@postgresql.org 6638 :UBC 0 : printf(" - number of transactions skipped: " INT64_FORMAT " (%.3f%%)\n",
6639 : : sstats->skipped,
6640 : : 100.0 * sstats->skipped / script_total_cnt);
6641 : :
6642 : : }
2947 tgl@sss.pgh.pa.us 6643 :CBC 13 : printSimpleStats(" - latency", &sstats->latency);
6644 : : }
6645 : :
6646 : : /*
6647 : : * Report per-command statistics: latencies, retries after errors,
6648 : : * failures (errors without retrying).
6649 : : */
2582 alvherre@alvh.no-ip. 6650 [ + + ]: 15 : if (report_per_command)
6651 : : {
6652 : : Command **commands;
6653 : :
1364 ishii@postgresql.org 6654 [ + - - + ]: 2 : printf("%sstatement latencies in milliseconds%s:\n",
6655 : : per_script_stats ? " - " : "",
6656 : : (max_tries == 1 ?
6657 : : " and failures" :
6658 : : ", failures and retries"));
6659 : :
3606 alvherre@alvh.no-ip. 6660 : 2 : for (commands = sql_script[i].commands;
6661 [ + + ]: 5 : *commands != NULL;
6662 : 3 : commands++)
6663 : : {
2947 tgl@sss.pgh.pa.us 6664 : 3 : SimpleStats *cstats = &(*commands)->stats;
6665 : :
1364 ishii@postgresql.org 6666 [ + - ]: 3 : if (max_tries == 1)
377 tmunro@postgresql.or 6667 [ + - ]: 3 : printf(" %11.3f %10" PRId64 " %s\n",
6668 : : (cstats->count > 0) ?
6669 : : 1000.0 * cstats->sum / cstats->count : 0.0,
6670 : : (*commands)->failures,
6671 : : (*commands)->first_line);
6672 : : else
377 tmunro@postgresql.or 6673 [ # # ]:UBC 0 : printf(" %11.3f %10" PRId64 " %10" PRId64 " %s\n",
6674 : : (cstats->count > 0) ?
6675 : : 1000.0 * cstats->sum / cstats->count : 0.0,
6676 : : (*commands)->failures,
6677 : : (*commands)->retries,
6678 : : (*commands)->first_line);
6679 : : }
6680 : : }
6681 : : }
6682 : : }
6683 : : }
6684 : :
6685 : : /*
6686 : : * Set up a random seed according to seed parameter (NULL means default),
6687 : : * and initialize base_random_sequence for use in initializing other sequences.
6688 : : */
6689 : : static bool
2817 tgl@sss.pgh.pa.us 6690 :CBC 182 : set_random_seed(const char *seed)
6691 : : {
6692 : : uint64 iseed;
6693 : :
2822 teodor@sigaev.ru 6694 [ + + - + ]: 182 : if (seed == NULL || strcmp(seed, "time") == 0)
6695 : : {
6696 : : /* rely on current time */
1742 tmunro@postgresql.or 6697 : 178 : iseed = pg_time_now();
6698 : : }
2822 teodor@sigaev.ru 6699 [ - + ]: 4 : else if (strcmp(seed, "rand") == 0)
6700 : : {
6701 : : /* use some "strong" random source */
2822 teodor@sigaev.ru 6702 [ # # ]:UBC 0 : if (!pg_strong_random(&iseed, sizeof(iseed)))
6703 : : {
2169 peter@eisentraut.org 6704 : 0 : pg_log_error("could not generate random seed");
2817 tgl@sss.pgh.pa.us 6705 : 0 : return false;
6706 : : }
6707 : : }
6708 : : else
6709 : : {
6710 : : char garbage;
6711 : :
262 peter@eisentraut.org 6712 [ + + ]:CBC 4 : if (sscanf(seed, "%" SCNu64 "%c", &iseed, &garbage) != 1)
6713 : : {
2169 6714 : 1 : pg_log_error("unrecognized random seed option \"%s\"", seed);
1348 tgl@sss.pgh.pa.us 6715 : 1 : pg_log_error_detail("Expecting an unsigned integer, \"time\" or \"rand\".");
2817 6716 : 1 : return false;
6717 : : }
6718 : : }
6719 : :
2822 teodor@sigaev.ru 6720 [ + + ]: 181 : if (seed != NULL)
262 peter@eisentraut.org 6721 : 3 : pg_log_info("setting random seed to %" PRIu64, iseed);
6722 : :
2822 teodor@sigaev.ru 6723 : 181 : random_seed = iseed;
6724 : :
6725 : : /* Initialize base_random_sequence using seed */
262 peter@eisentraut.org 6726 : 181 : pg_prng_seed(&base_random_sequence, iseed);
6727 : :
2817 tgl@sss.pgh.pa.us 6728 : 181 : return true;
6729 : : }
6730 : :
6731 : : int
9379 bruce@momjian.us 6732 : 180 : main(int argc, char **argv)
6733 : : {
6734 : : static struct option long_options[] = {
6735 : : /* systematic long/short named options */
6736 : : {"builtin", required_argument, NULL, 'b'},
6737 : : {"client", required_argument, NULL, 'c'},
6738 : : {"connect", no_argument, NULL, 'C'},
6739 : : {"dbname", required_argument, NULL, 'd'},
6740 : : {"define", required_argument, NULL, 'D'},
6741 : : {"file", required_argument, NULL, 'f'},
6742 : : {"fillfactor", required_argument, NULL, 'F'},
6743 : : {"host", required_argument, NULL, 'h'},
6744 : : {"initialize", no_argument, NULL, 'i'},
6745 : : {"init-steps", required_argument, NULL, 'I'},
6746 : : {"jobs", required_argument, NULL, 'j'},
6747 : : {"log", no_argument, NULL, 'l'},
6748 : : {"latency-limit", required_argument, NULL, 'L'},
6749 : : {"no-vacuum", no_argument, NULL, 'n'},
6750 : : {"port", required_argument, NULL, 'p'},
6751 : : {"progress", required_argument, NULL, 'P'},
6752 : : {"protocol", required_argument, NULL, 'M'},
6753 : : {"quiet", no_argument, NULL, 'q'},
6754 : : {"report-per-command", no_argument, NULL, 'r'},
6755 : : {"rate", required_argument, NULL, 'R'},
6756 : : {"scale", required_argument, NULL, 's'},
6757 : : {"select-only", no_argument, NULL, 'S'},
6758 : : {"skip-some-updates", no_argument, NULL, 'N'},
6759 : : {"time", required_argument, NULL, 'T'},
6760 : : {"transactions", required_argument, NULL, 't'},
6761 : : {"username", required_argument, NULL, 'U'},
6762 : : {"vacuum-all", no_argument, NULL, 'v'},
6763 : : /* long-named only options */
6764 : : {"unlogged-tables", no_argument, NULL, 1},
6765 : : {"tablespace", required_argument, NULL, 2},
6766 : : {"index-tablespace", required_argument, NULL, 3},
6767 : : {"sampling-rate", required_argument, NULL, 4},
6768 : : {"aggregate-interval", required_argument, NULL, 5},
6769 : : {"progress-timestamp", no_argument, NULL, 6},
6770 : : {"log-prefix", required_argument, NULL, 7},
6771 : : {"foreign-keys", no_argument, NULL, 8},
6772 : : {"random-seed", required_argument, NULL, 9},
6773 : : {"show-script", required_argument, NULL, 10},
6774 : : {"partitions", required_argument, NULL, 11},
6775 : : {"partition-method", required_argument, NULL, 12},
6776 : : {"failures-detailed", no_argument, NULL, 13},
6777 : : {"max-tries", required_argument, NULL, 14},
6778 : : {"verbose-errors", no_argument, NULL, 15},
6779 : : {"exit-on-abort", no_argument, NULL, 16},
6780 : : {"debug", no_argument, NULL, 17},
6781 : : {"continue-on-error", no_argument, NULL, 18},
6782 : : {NULL, 0, NULL, 0}
6783 : : };
6784 : :
6785 : : int c;
2955 tgl@sss.pgh.pa.us 6786 : 180 : bool is_init_mode = false; /* initialize mode? */
6787 : 180 : char *initialize_steps = NULL;
6788 : 180 : bool foreign_keys = false;
6789 : 180 : bool is_no_vacuum = false;
6790 : 180 : bool do_vacuum_accounts = false; /* vacuum accounts table? */
6791 : : int optindex;
6430 6792 : 180 : bool scale_given = false;
6793 : :
4144 ishii@postgresql.org 6794 : 180 : bool benchmarking_option_set = false;
6795 : 180 : bool initialization_option_set = false;
3611 alvherre@alvh.no-ip. 6796 : 180 : bool internal_script_used = false;
6797 : :
6798 : : CState *state; /* status of clients */
6799 : : TState *threads; /* array of thread */
6800 : :
6801 : : pg_time_usec_t
6802 : : start_time, /* start up time */
1742 tmunro@postgresql.or 6803 : 180 : bench_start = 0, /* first recorded benchmarking time */
6804 : : conn_total_duration; /* cumulated connection time in
6805 : : * threads */
4082 heikki.linnakangas@i 6806 : 180 : int64 latency_late = 0;
6807 : : StatsData stats;
6808 : : int weight;
6809 : :
6810 : : int i;
6811 : : int nclients_dealt;
6812 : :
6813 : : #ifdef HAVE_GETRLIMIT
6814 : : struct rlimit rlim;
6815 : : #endif
6816 : :
6817 : : PGconn *con;
6818 : : char *env;
6819 : :
2625 peter_e@gmx.net 6820 : 180 : int exit_code = 0;
6821 : : struct timeval tv;
6822 : :
6823 : : /*
6824 : : * Record difference between Unix time and instr_time time. We'll use
6825 : : * this for logging and aggregation.
6826 : : */
1619 tmunro@postgresql.or 6827 : 180 : gettimeofday(&tv, NULL);
6828 : 180 : epoch_shift = tv.tv_sec * INT64CONST(1000000) + tv.tv_usec - pg_time_now();
6829 : :
2451 peter@eisentraut.org 6830 : 180 : pg_logging_init(argv[0]);
6136 peter_e@gmx.net 6831 : 180 : progname = get_progname(argv[0]);
6832 : :
6833 [ + - ]: 180 : if (argc > 1)
6834 : : {
6835 [ + + - + ]: 180 : if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
6836 : : {
4913 rhaas@postgresql.org 6837 : 1 : usage();
6136 peter_e@gmx.net 6838 : 1 : exit(0);
6839 : : }
6840 [ + + - + ]: 179 : if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
6841 : : {
6842 : 1 : puts("pgbench (PostgreSQL) " PG_VERSION);
6843 : 1 : exit(0);
6844 : : }
6845 : : }
6846 : :
2456 michael@paquier.xyz 6847 : 178 : state = (CState *) pg_malloc0(sizeof(CState));
6848 : :
6849 : : /* set random seed early, because it may be used while parsing scripts. */
2817 tgl@sss.pgh.pa.us 6850 [ - + ]: 178 : if (!set_random_seed(getenv("PGBENCH_RANDOM_SEED")))
1348 tgl@sss.pgh.pa.us 6851 :UBC 0 : pg_fatal("error while setting random seed from PGBENCH_RANDOM_SEED environment variable");
6852 : :
631 nathan@postgresql.or 6853 [ + + ]:CBC 1248 : while ((c = getopt_long(argc, argv, "b:c:Cd:D:f:F:h:iI:j:lL:M:nNp:P:qrR:s:St:T:U:v", long_options, &optindex)) != -1)
6854 : : {
6855 : : char *script;
6856 : :
9379 bruce@momjian.us 6857 [ + + + - : 1139 : switch (c)
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + ]
6858 : : {
1100 peter@eisentraut.org 6859 : 12 : case 'b':
6860 [ + + ]: 12 : if (strcmp(optarg, "list") == 0)
6861 : : {
6862 : 1 : listAvailableScripts();
6863 : 1 : exit(0);
6864 : : }
6865 : 11 : weight = parseScriptWeight(optarg, &script);
6866 : 9 : process_builtin(findBuiltin(script), weight);
2955 tgl@sss.pgh.pa.us 6867 : 7 : benchmarking_option_set = true;
1100 peter@eisentraut.org 6868 : 7 : internal_script_used = true;
9379 bruce@momjian.us 6869 : 7 : break;
6870 : 25 : case 'c':
4144 ishii@postgresql.org 6871 : 25 : benchmarking_option_set = true;
1606 michael@paquier.xyz 6872 [ + + ]: 25 : if (!option_parse_int(optarg, "-c/--clients", 1, INT_MAX,
6873 : : &nclients))
6874 : : {
9379 bruce@momjian.us 6875 : 1 : exit(1);
6876 : : }
6877 : : #ifdef HAVE_GETRLIMIT
6878 [ - + ]: 24 : if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
1348 tgl@sss.pgh.pa.us 6879 :UBC 0 : pg_fatal("getrlimit failed: %m");
6880 : :
300 andres@anarazel.de 6881 [ - + ]:CBC 24 : if (rlim.rlim_max < nclients + 3)
6882 : : {
1348 tgl@sss.pgh.pa.us 6883 :UBC 0 : pg_log_error("need at least %d open files, but system limit is %ld",
6884 : : nclients + 3, (long) rlim.rlim_max);
6885 : 0 : pg_log_error_hint("Reduce number of clients, or use limit/ulimit to increase the system limit.");
9379 bruce@momjian.us 6886 : 0 : exit(1);
6887 : : }
6888 : :
300 andres@anarazel.de 6889 [ - + ]:CBC 24 : if (rlim.rlim_cur < nclients + 3)
6890 : : {
300 andres@anarazel.de 6891 :UBC 0 : rlim.rlim_cur = nclients + 3;
6892 [ # # ]: 0 : if (setrlimit(RLIMIT_NOFILE, &rlim) == -1)
6893 : : {
6894 : 0 : pg_log_error("need at least %d open files, but couldn't raise the limit: %m",
6895 : : nclients + 3);
6896 : 0 : pg_log_error_hint("Reduce number of clients, or use limit/ulimit to increase the system limit.");
6897 : 0 : exit(1);
6898 : : }
6899 : : }
6900 : : #endif /* HAVE_GETRLIMIT */
9379 bruce@momjian.us 6901 :CBC 24 : break;
8818 6902 : 2 : case 'C':
4144 ishii@postgresql.org 6903 : 2 : benchmarking_option_set = true;
5747 itagaki.takahiro@gma 6904 : 2 : is_connect = true;
8818 bruce@momjian.us 6905 : 2 : break;
1100 peter@eisentraut.org 6906 :UBC 0 : case 'd':
631 nathan@postgresql.or 6907 : 0 : dbName = pg_strdup(optarg);
7383 ishii@postgresql.org 6908 : 0 : break;
7083 ishii@postgresql.org 6909 :CBC 433 : case 'D':
6910 : : {
6911 : : char *p;
6912 : :
4144 6913 : 433 : benchmarking_option_set = true;
6914 : :
7083 6915 [ + + + - : 433 : if ((p = strchr(optarg, '=')) == NULL || p == optarg || *(p + 1) == '\0')
- + ]
1348 tgl@sss.pgh.pa.us 6916 : 1 : pg_fatal("invalid variable definition: \"%s\"", optarg);
6917 : :
7083 ishii@postgresql.org 6918 : 432 : *p++ = '\0';
1364 6919 [ - + ]: 432 : if (!putVariable(&state[0].variables, "option", optarg, p))
7083 ishii@postgresql.org 6920 :UBC 0 : exit(1);
6921 : : }
7083 ishii@postgresql.org 6922 :CBC 432 : break;
1100 peter@eisentraut.org 6923 : 133 : case 'f':
6924 : 133 : weight = parseScriptWeight(optarg, &script);
6925 : 133 : process_file(script, weight);
6926 : 92 : benchmarking_option_set = true;
6927 : 92 : break;
6827 ishii@postgresql.org 6928 : 3 : case 'F':
4144 6929 : 3 : initialization_option_set = true;
1606 michael@paquier.xyz 6930 [ + + ]: 3 : if (!option_parse_int(optarg, "-F/--fillfactor", 10, 100,
6931 : : &fillfactor))
6827 ishii@postgresql.org 6932 : 1 : exit(1);
6933 : 2 : break;
1100 peter@eisentraut.org 6934 : 1 : case 'h':
6935 : 1 : pghost = pg_strdup(optarg);
6936 : 1 : break;
6937 : 9 : case 'i':
6938 : 9 : is_init_mode = true;
6939 : 9 : break;
6940 : 4 : case 'I':
6941 : 4 : pg_free(initialize_steps);
6942 : 4 : initialize_steps = pg_strdup(optarg);
6943 : 4 : checkInitSteps(initialize_steps);
6944 : 3 : initialization_option_set = true;
6945 : 3 : break;
6946 : 4 : case 'j': /* jobs */
6947 : 4 : benchmarking_option_set = true;
6948 [ + + ]: 4 : if (!option_parse_int(optarg, "-j/--jobs", 1, INT_MAX,
6949 : : &nthreads))
6950 : : {
6951 : 1 : exit(1);
6952 : : }
6953 : 3 : break;
6954 : 7 : case 'l':
6955 : 7 : benchmarking_option_set = true;
6956 : 7 : use_log = true;
6957 : 7 : break;
6958 : 3 : case 'L':
6959 : : {
6960 : 3 : double limit_ms = atof(optarg);
6961 : :
6962 [ + + ]: 3 : if (limit_ms <= 0.0)
6963 : 1 : pg_fatal("invalid latency limit: \"%s\"", optarg);
6964 : 2 : benchmarking_option_set = true;
6965 : 2 : latency_limit = (int64) (limit_ms * 1000);
6966 : : }
6967 : 2 : break;
6481 ishii@postgresql.org 6968 : 88 : case 'M':
4144 6969 : 88 : benchmarking_option_set = true;
6481 6970 [ + + ]: 241 : for (querymode = 0; querymode < NUM_QUERYMODE; querymode++)
6971 [ + + ]: 240 : if (strcmp(optarg, QUERYMODE[querymode]) == 0)
6972 : 87 : break;
6973 [ + + ]: 88 : if (querymode >= NUM_QUERYMODE)
1348 tgl@sss.pgh.pa.us 6974 : 1 : pg_fatal("invalid query mode (-M): \"%s\"", optarg);
6481 ishii@postgresql.org 6975 : 87 : break;
1100 peter@eisentraut.org 6976 : 100 : case 'n':
6977 : 100 : is_no_vacuum = true;
6978 : 100 : break;
6979 : 1 : case 'N':
6980 : 1 : process_builtin(findBuiltin("simple-update"), 1);
6981 : 1 : benchmarking_option_set = true;
6982 : 1 : internal_script_used = true;
6983 : 1 : break;
6984 : 1 : case 'p':
6985 : 1 : pgport = pg_strdup(optarg);
6986 : 1 : break;
4535 ishii@postgresql.org 6987 : 2 : case 'P':
4144 6988 : 2 : benchmarking_option_set = true;
1606 michael@paquier.xyz 6989 [ + + ]: 2 : if (!option_parse_int(optarg, "-P/--progress", 1, INT_MAX,
6990 : : &progress))
4535 ishii@postgresql.org 6991 : 1 : exit(1);
6992 : 1 : break;
1100 peter@eisentraut.org 6993 : 1 : case 'q':
6994 : 1 : initialization_option_set = true;
6995 : 1 : use_quiet = true;
6996 : 1 : break;
6997 : 2 : case 'r':
6998 : 2 : benchmarking_option_set = true;
6999 : 2 : report_per_command = true;
7000 : 2 : break;
4529 ishii@postgresql.org 7001 : 3 : case 'R':
7002 : : {
7003 : : /* get a double from the beginning of option value */
4242 bruce@momjian.us 7004 : 3 : double throttle_value = atof(optarg);
7005 : :
4144 ishii@postgresql.org 7006 : 3 : benchmarking_option_set = true;
7007 : :
4242 bruce@momjian.us 7008 [ + + ]: 3 : if (throttle_value <= 0.0)
1348 tgl@sss.pgh.pa.us 7009 : 1 : pg_fatal("invalid rate limit: \"%s\"", optarg);
7010 : : /* Invert rate limit into per-transaction delay in usec */
2639 7011 : 2 : throttle_delay = 1000000.0 / throttle_value;
7012 : : }
4529 ishii@postgresql.org 7013 : 2 : break;
1100 peter@eisentraut.org 7014 : 3 : case 's':
7015 : 3 : scale_given = true;
7016 [ + + ]: 3 : if (!option_parse_int(optarg, "-s/--scale", 1, INT_MAX,
7017 : : &scale))
7018 : 1 : exit(1);
7019 : 2 : break;
7020 : 134 : case 'S':
7021 : 134 : process_builtin(findBuiltin("select-only"), 1);
7022 : 133 : benchmarking_option_set = true;
7023 : 133 : internal_script_used = true;
7024 : 133 : break;
7025 : 112 : case 't':
7026 : 112 : benchmarking_option_set = true;
7027 [ + + ]: 112 : if (!option_parse_int(optarg, "-t/--transactions", 1, INT_MAX,
7028 : : &nxacts))
7029 : 1 : exit(1);
7030 : 111 : break;
7031 : 5 : case 'T':
7032 : 5 : benchmarking_option_set = true;
7033 [ + + ]: 5 : if (!option_parse_int(optarg, "-T/--time", 1, INT_MAX,
7034 : : &duration))
7035 : 1 : exit(1);
7036 : 4 : break;
7037 : 1 : case 'U':
7038 : 1 : username = pg_strdup(optarg);
7039 : 1 : break;
7040 : 1 : case 'v':
7041 : 1 : benchmarking_option_set = true;
7042 : 1 : do_vacuum_accounts = true;
4082 heikki.linnakangas@i 7043 : 1 : break;
2955 tgl@sss.pgh.pa.us 7044 : 2 : case 1: /* unlogged-tables */
7045 : 2 : initialization_option_set = true;
7046 : 2 : unlogged_tables = true;
5258 rhaas@postgresql.org 7047 : 2 : break;
4937 bruce@momjian.us 7048 : 1 : case 2: /* tablespace */
4144 ishii@postgresql.org 7049 : 1 : initialization_option_set = true;
4813 bruce@momjian.us 7050 : 1 : tablespace = pg_strdup(optarg);
5258 rhaas@postgresql.org 7051 : 1 : break;
4937 bruce@momjian.us 7052 : 1 : case 3: /* index-tablespace */
4144 ishii@postgresql.org 7053 : 1 : initialization_option_set = true;
4813 bruce@momjian.us 7054 : 1 : index_tablespace = pg_strdup(optarg);
5258 rhaas@postgresql.org 7055 : 1 : break;
2955 tgl@sss.pgh.pa.us 7056 : 5 : case 4: /* sampling-rate */
4144 ishii@postgresql.org 7057 : 5 : benchmarking_option_set = true;
4822 heikki.linnakangas@i 7058 : 5 : sample_rate = atof(optarg);
7059 [ + + - + ]: 5 : if (sample_rate <= 0.0 || sample_rate > 1.0)
1348 tgl@sss.pgh.pa.us 7060 : 1 : pg_fatal("invalid sampling rate: \"%s\"", optarg);
4822 heikki.linnakangas@i 7061 : 4 : break;
2955 tgl@sss.pgh.pa.us 7062 : 6 : case 5: /* aggregate-interval */
4144 ishii@postgresql.org 7063 : 6 : benchmarking_option_set = true;
1606 michael@paquier.xyz 7064 [ + + ]: 6 : if (!option_parse_int(optarg, "--aggregate-interval", 1, INT_MAX,
7065 : : &agg_interval))
4702 ishii@postgresql.org 7066 : 1 : exit(1);
7067 : 5 : break;
2955 tgl@sss.pgh.pa.us 7068 : 2 : case 6: /* progress-timestamp */
3744 teodor@sigaev.ru 7069 : 2 : progress_timestamp = true;
7070 : 2 : benchmarking_option_set = true;
7071 : 2 : break;
2955 tgl@sss.pgh.pa.us 7072 : 4 : case 7: /* log-prefix */
3324 rhaas@postgresql.org 7073 : 4 : benchmarking_option_set = true;
7074 : 4 : logfile_prefix = pg_strdup(optarg);
7075 : 4 : break;
2955 tgl@sss.pgh.pa.us 7076 : 2 : case 8: /* foreign-keys */
7077 : 2 : initialization_option_set = true;
7078 : 2 : foreign_keys = true;
7079 : 2 : break;
2822 teodor@sigaev.ru 7080 : 4 : case 9: /* random-seed */
7081 : 4 : benchmarking_option_set = true;
2817 tgl@sss.pgh.pa.us 7082 [ + + ]: 4 : if (!set_random_seed(optarg))
1348 7083 : 1 : pg_fatal("error while setting random seed from --random-seed option");
2822 teodor@sigaev.ru 7084 : 3 : break;
2345 tmunro@postgresql.or 7085 : 1 : case 10: /* list */
7086 : : {
7087 : 1 : const BuiltinScript *s = findBuiltin(optarg);
7088 : :
7089 : 1 : fprintf(stderr, "-- %s: %s\n%s\n", s->name, s->desc, s->script);
7090 : 1 : exit(0);
7091 : : }
7092 : : break;
2268 akapila@postgresql.o 7093 : 3 : case 11: /* partitions */
7094 : 3 : initialization_option_set = true;
1308 michael@paquier.xyz 7095 [ + + ]: 3 : if (!option_parse_int(optarg, "--partitions", 0, INT_MAX,
7096 : : &partitions))
2268 akapila@postgresql.o 7097 : 1 : exit(1);
7098 : 2 : break;
7099 : 3 : case 12: /* partition-method */
7100 : 3 : initialization_option_set = true;
7101 [ - + ]: 3 : if (pg_strcasecmp(optarg, "range") == 0)
2268 akapila@postgresql.o 7102 :UBC 0 : partition_method = PART_RANGE;
2268 akapila@postgresql.o 7103 [ + + ]:CBC 3 : else if (pg_strcasecmp(optarg, "hash") == 0)
7104 : 2 : partition_method = PART_HASH;
7105 : : else
1348 tgl@sss.pgh.pa.us 7106 : 1 : pg_fatal("invalid partition method, expecting \"range\" or \"hash\", got: \"%s\"",
7107 : : optarg);
2268 akapila@postgresql.o 7108 : 2 : break;
1364 ishii@postgresql.org 7109 :GBC 1 : case 13: /* failures-detailed */
7110 : 1 : benchmarking_option_set = true;
7111 : 1 : failures_detailed = true;
7112 : 1 : break;
1364 ishii@postgresql.org 7113 :CBC 4 : case 14: /* max-tries */
7114 : : {
7115 : 4 : int32 max_tries_arg = atoi(optarg);
7116 : :
7117 [ + + ]: 4 : if (max_tries_arg < 0)
1348 tgl@sss.pgh.pa.us 7118 : 1 : pg_fatal("invalid number of maximum tries: \"%s\"", optarg);
7119 : :
1364 ishii@postgresql.org 7120 : 3 : benchmarking_option_set = true;
7121 : 3 : max_tries = (uint32) max_tries_arg;
7122 : : }
7123 : 3 : break;
7124 : 2 : case 15: /* verbose-errors */
7125 : 2 : benchmarking_option_set = true;
7126 : 2 : verbose_errors = true;
7127 : 2 : break;
839 7128 : 2 : case 16: /* exit-on-abort */
7129 : 2 : benchmarking_option_set = true;
7130 : 2 : exit_on_abort = true;
7131 : 2 : break;
631 nathan@postgresql.or 7132 : 2 : case 17: /* debug */
7133 : 2 : pg_logging_increase_verbosity();
7134 : 2 : break;
39 fujii@postgresql.org 7135 :GNC 1 : case 18: /* continue-on-error */
7136 : 1 : benchmarking_option_set = true;
7137 : 1 : continue_on_error = true;
7138 : 1 : break;
9379 bruce@momjian.us 7139 :CBC 3 : default:
7140 : : /* getopt_long already emitted a complaint */
1348 tgl@sss.pgh.pa.us 7141 : 3 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
9379 bruce@momjian.us 7142 : 3 : exit(1);
7143 : : }
7144 : : }
7145 : :
7146 : : /* set default script if none */
3611 alvherre@alvh.no-ip. 7147 [ + + + + ]: 109 : if (num_scripts == 0 && !is_init_mode)
7148 : : {
3558 tgl@sss.pgh.pa.us 7149 : 11 : process_builtin(findBuiltin("tpcb-like"), 1);
3611 alvherre@alvh.no-ip. 7150 : 11 : benchmarking_option_set = true;
7151 : 11 : internal_script_used = true;
7152 : : }
7153 : :
7154 : : /* complete SQL command initialization and compute total weight */
2532 7155 [ + + ]: 224 : for (i = 0; i < num_scripts; i++)
7156 : : {
7157 : 116 : Command **commands = sql_script[i].commands;
7158 : :
7159 [ + + ]: 732 : for (int j = 0; commands[j] != NULL; j++)
7160 [ + + ]: 617 : if (commands[j]->type == SQL_COMMAND)
7161 : 294 : postprocess_sql_command(commands[j]);
7162 : :
7163 : : /* cannot overflow: weight is 32b, total_weight 64b */
3559 7164 : 115 : total_weight += sql_script[i].weight;
7165 : : }
7166 : :
3549 7167 [ + + + + ]: 108 : if (total_weight == 0 && !is_init_mode)
1348 tgl@sss.pgh.pa.us 7168 : 1 : pg_fatal("total script weight must not be zero");
7169 : :
7170 : : /* show per script stats if several scripts are used */
3606 alvherre@alvh.no-ip. 7171 [ + + ]: 107 : if (num_scripts > 1)
7172 : 4 : per_script_stats = true;
7173 : :
7174 : : /*
7175 : : * Don't need more threads than there are clients. (This is not merely an
7176 : : * optimization; throttle_delay is calculated incorrectly below if some
7177 : : * threads have no clients assigned to them.)
7178 : : */
3819 heikki.linnakangas@i 7179 [ + + ]: 107 : if (nthreads > nclients)
7180 : 1 : nthreads = nclients;
7181 : :
7182 : : /*
7183 : : * Convert throttle_delay to a per-thread delay time. Note that this
7184 : : * might be a fractional number of usec, but that's OK, since it's just
7185 : : * the center of a Poisson distribution of delays.
7186 : : */
4529 ishii@postgresql.org 7187 : 107 : throttle_delay *= nthreads;
7188 : :
631 nathan@postgresql.or 7189 [ + - ]: 107 : if (dbName == NULL)
7190 : : {
7191 [ + + ]: 107 : if (argc > optind)
7192 : 1 : dbName = argv[optind++];
7193 : : else
7194 : : {
7195 [ + + + - ]: 106 : if ((env = getenv("PGDATABASE")) != NULL && *env != '\0')
7196 : 92 : dbName = env;
7197 [ - + - - ]: 14 : else if ((env = getenv("PGUSER")) != NULL && *env != '\0')
631 nathan@postgresql.or 7198 :UBC 0 : dbName = env;
7199 : : else
631 nathan@postgresql.or 7200 :CBC 14 : dbName = get_user_name_or_exit(progname);
7201 : : }
7202 : : }
7203 : :
2301 peter@eisentraut.org 7204 [ - + ]: 107 : if (optind < argc)
7205 : : {
1348 tgl@sss.pgh.pa.us 7206 :UBC 0 : pg_log_error("too many command-line arguments (first is \"%s\")",
7207 : : argv[optind]);
7208 : 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
2301 peter@eisentraut.org 7209 : 0 : exit(1);
7210 : : }
7211 : :
9379 bruce@momjian.us 7212 [ + + ]:CBC 107 : if (is_init_mode)
7213 : : {
4144 ishii@postgresql.org 7214 [ + + ]: 5 : if (benchmarking_option_set)
1348 tgl@sss.pgh.pa.us 7215 : 1 : pg_fatal("some of the specified options cannot be used in initialization (-i) mode");
7216 : :
2268 akapila@postgresql.o 7217 [ + + + + ]: 4 : if (partitions == 0 && partition_method != PART_NONE)
1348 tgl@sss.pgh.pa.us 7218 : 1 : pg_fatal("--partition-method requires greater than zero --partitions");
7219 : :
7220 : : /* set default method */
2268 akapila@postgresql.o 7221 [ + + + + ]: 3 : if (partitions > 0 && partition_method == PART_NONE)
7222 : 1 : partition_method = PART_RANGE;
7223 : :
2955 tgl@sss.pgh.pa.us 7224 [ + + ]: 3 : if (initialize_steps == NULL)
7225 : 1 : initialize_steps = pg_strdup(DEFAULT_INIT_STEPS);
7226 : :
7227 [ + + ]: 3 : if (is_no_vacuum)
7228 : : {
7229 : : /* Remove any vacuum step in initialize_steps */
7230 : : char *p;
7231 : :
7232 [ + + ]: 4 : while ((p = strchr(initialize_steps, 'v')) != NULL)
7233 : 3 : *p = ' ';
7234 : : }
7235 : :
7236 [ + + ]: 3 : if (foreign_keys)
7237 : : {
7238 : : /* Add 'f' to end of initialize_steps, if not already there */
7239 [ + - ]: 2 : if (strchr(initialize_steps, 'f') == NULL)
7240 : : {
7241 : : initialize_steps = (char *)
7242 : 2 : pg_realloc(initialize_steps,
7243 : 2 : strlen(initialize_steps) + 2);
7244 : 2 : strcat(initialize_steps, "f");
7245 : : }
7246 : : }
7247 : :
7248 : 3 : runInitSteps(initialize_steps);
9379 bruce@momjian.us 7249 : 3 : exit(0);
7250 : : }
7251 : : else
7252 : : {
4144 ishii@postgresql.org 7253 [ + + ]: 102 : if (initialization_option_set)
1348 tgl@sss.pgh.pa.us 7254 : 2 : pg_fatal("some of the specified options cannot be used in benchmarking mode");
7255 : : }
7256 : :
2955 7257 [ + + + + ]: 100 : if (nxacts > 0 && duration > 0)
1348 7258 : 2 : pg_fatal("specify either a number of transactions (-t) or a duration (-T), not both");
7259 : :
7260 : : /* Use DEFAULT_NXACTS if neither nxacts nor duration is specified. */
6305 7261 [ + + + + ]: 98 : if (nxacts <= 0 && duration <= 0)
7262 : 8 : nxacts = DEFAULT_NXACTS;
7263 : :
7264 : : /* --sampling-rate may be used only with -l */
4822 heikki.linnakangas@i 7265 [ + + + + ]: 98 : if (sample_rate > 0.0 && !use_log)
1348 tgl@sss.pgh.pa.us 7266 : 1 : pg_fatal("log sampling (--sampling-rate) is allowed only when logging transactions (-l)");
7267 : :
7268 : : /* --sampling-rate may not be used with --aggregate-interval */
4702 ishii@postgresql.org 7269 [ + + + + ]: 97 : if (sample_rate > 0.0 && agg_interval > 0)
1348 tgl@sss.pgh.pa.us 7270 : 1 : pg_fatal("log sampling (--sampling-rate) and aggregation (--aggregate-interval) cannot be used at the same time");
7271 : :
3817 7272 [ + + + + ]: 96 : if (agg_interval > 0 && !use_log)
1348 7273 : 1 : pg_fatal("log aggregation is allowed only when actually logging transactions");
7274 : :
3324 rhaas@postgresql.org 7275 [ + + + + ]: 95 : if (!use_log && logfile_prefix)
1348 tgl@sss.pgh.pa.us 7276 : 1 : pg_fatal("log file prefix (--log-prefix) is allowed only when logging transactions (-l)");
7277 : :
3817 7278 [ + + + + ]: 94 : if (duration > 0 && agg_interval > duration)
1348 7279 : 1 : pg_fatal("number of seconds for aggregation (%d) must not be higher than test duration (%d)", agg_interval, duration);
7280 : :
3817 7281 [ + + + - : 93 : if (duration > 0 && agg_interval > 0 && duration % agg_interval != 0)
+ - ]
1348 7282 : 1 : pg_fatal("duration (%d) must be a multiple of aggregation interval (%d)", duration, agg_interval);
7283 : :
3025 7284 [ + + + - ]: 92 : if (progress_timestamp && progress == 0)
1348 7285 : 1 : pg_fatal("--progress-timestamp is allowed only under --progress");
7286 : :
1364 ishii@postgresql.org 7287 [ + + ]: 91 : if (!max_tries)
7288 : : {
7289 [ + - + - ]: 1 : if (!latency_limit && duration <= 0)
1348 tgl@sss.pgh.pa.us 7290 : 1 : pg_fatal("an unlimited number of transaction tries can only be used with --latency-limit or a duration (-T)");
7291 : : }
7292 : :
7293 : : /*
7294 : : * save main process id in the global variable because process id will be
7295 : : * changed after fork.
7296 : : */
5747 itagaki.takahiro@gma 7297 : 90 : main_pid = (int) getpid();
7298 : :
7083 ishii@postgresql.org 7299 [ + + ]: 90 : if (nclients > 1)
7300 : : {
4823 tgl@sss.pgh.pa.us 7301 : 15 : state = (CState *) pg_realloc(state, sizeof(CState) * nclients);
5605 7302 : 15 : memset(state + 1, 0, sizeof(CState) * (nclients - 1));
7303 : :
7304 : : /* copy any -D switch values to all clients */
7083 ishii@postgresql.org 7305 [ + + ]: 55 : for (i = 1; i < nclients; i++)
7306 : : {
7307 : : int j;
7308 : :
5979 7309 : 40 : state[i].id = i;
1364 7310 [ + + ]: 41 : for (j = 0; j < state[0].variables.nvars; j++)
7311 : : {
7312 : 1 : Variable *var = &state[0].variables.vars[j];
7313 : :
2898 teodor@sigaev.ru 7314 [ - + ]: 1 : if (var->value.type != PGBT_NO_VALUE)
7315 : : {
1364 ishii@postgresql.org 7316 [ # # ]:UBC 0 : if (!putVariableValue(&state[i].variables, "startup",
2791 tgl@sss.pgh.pa.us 7317 : 0 : var->name, &var->value))
3511 7318 : 0 : exit(1);
7319 : : }
7320 : : else
7321 : : {
1364 ishii@postgresql.org 7322 [ - + ]:CBC 1 : if (!putVariable(&state[i].variables, "startup",
2898 teodor@sigaev.ru 7323 : 1 : var->name, var->svalue))
3511 tgl@sss.pgh.pa.us 7324 :UBC 0 : exit(1);
7325 : : }
7326 : : }
7327 : : }
7328 : : }
7329 : :
7330 : : /* other CState initializations */
2826 teodor@sigaev.ru 7331 [ + + ]:CBC 220 : for (i = 0; i < nclients; i++)
7332 : : {
7333 : 130 : state[i].cstack = conditional_stack_create();
2587 alvherre@alvh.no-ip. 7334 : 130 : initRandomState(&state[i].cs_func_rs);
7335 : : }
7336 : :
7337 : : /* opening connection... */
8864 ishii@postgresql.org 7338 : 90 : con = doConnect();
7339 [ + + ]: 90 : if (con == NULL)
1348 tgl@sss.pgh.pa.us 7340 : 1 : pg_fatal("could not create connection for setup");
7341 : :
7342 : : /* report pgbench and server versions */
1642 7343 : 89 : printVersion(con);
7344 : :
1746 michael@paquier.xyz 7345 [ + + + - : 89 : pg_log_debug("pghost: %s pgport: %s nclients: %d %s: %d dbName: %s",
+ - ]
7346 : : PQhost(con), PQport(con), nclients,
7347 : : duration <= 0 ? "nxacts" : "duration",
7348 : : duration <= 0 ? nxacts : duration, PQdb(con));
7349 : :
3611 alvherre@alvh.no-ip. 7350 [ + + ]: 89 : if (internal_script_used)
2268 akapila@postgresql.o 7351 : 7 : GetTableInfo(con, scale_given);
7352 : :
7353 : : /*
7354 : : * :scale variables normally get -s or database scale, but don't override
7355 : : * an explicit -D switch
7356 : : */
1364 ishii@postgresql.org 7357 [ + - ]: 88 : if (lookupVariable(&state[0].variables, "scale") == NULL)
7358 : : {
6430 tgl@sss.pgh.pa.us 7359 [ + + ]: 216 : for (i = 0; i < nclients; i++)
7360 : : {
1364 ishii@postgresql.org 7361 [ - + ]: 128 : if (!putVariableInt(&state[i].variables, "startup", "scale", scale))
6430 tgl@sss.pgh.pa.us 7362 :UBC 0 : exit(1);
7363 : : }
7364 : : }
7365 : :
7366 : : /*
7367 : : * Define a :client_id variable that is unique per connection. But don't
7368 : : * override an explicit -D switch.
7369 : : */
1364 ishii@postgresql.org 7370 [ + - ]:CBC 88 : if (lookupVariable(&state[0].variables, "client_id") == NULL)
7371 : : {
4568 heikki.linnakangas@i 7372 [ + + ]: 216 : for (i = 0; i < nclients; i++)
1364 ishii@postgresql.org 7373 [ - + ]: 128 : if (!putVariableInt(&state[i].variables, "startup", "client_id", i))
4568 heikki.linnakangas@i 7374 :UBC 0 : exit(1);
7375 : : }
7376 : :
7377 : : /* set default seed for hash functions */
1364 ishii@postgresql.org 7378 [ + - ]:CBC 88 : if (lookupVariable(&state[0].variables, "default_seed") == NULL)
7379 : : {
1479 tgl@sss.pgh.pa.us 7380 : 88 : uint64 seed = pg_prng_uint64(&base_random_sequence);
7381 : :
2827 teodor@sigaev.ru 7382 [ + + ]: 216 : for (i = 0; i < nclients; i++)
1364 ishii@postgresql.org 7383 [ - + ]: 128 : if (!putVariableInt(&state[i].variables, "startup", "default_seed",
7384 : : (int64) seed))
2827 teodor@sigaev.ru 7385 :UBC 0 : exit(1);
7386 : : }
7387 : :
7388 : : /* set random seed unless overwritten */
1364 ishii@postgresql.org 7389 [ + - ]:CBC 88 : if (lookupVariable(&state[0].variables, "random_seed") == NULL)
7390 : : {
2822 teodor@sigaev.ru 7391 [ + + ]: 216 : for (i = 0; i < nclients; i++)
1364 ishii@postgresql.org 7392 [ - + ]: 128 : if (!putVariableInt(&state[i].variables, "startup", "random_seed",
7393 : : random_seed))
2822 teodor@sigaev.ru 7394 :UBC 0 : exit(1);
7395 : : }
7396 : :
7378 ishii@postgresql.org 7397 [ + + ]:CBC 88 : if (!is_no_vacuum)
7398 : : {
7399 : 11 : fprintf(stderr, "starting vacuum...");
3871 sfrost@snowman.net 7400 : 11 : tryExecuteStatement(con, "vacuum pgbench_branches");
7401 : 11 : tryExecuteStatement(con, "vacuum pgbench_tellers");
7402 : 11 : tryExecuteStatement(con, "truncate pgbench_history");
7378 ishii@postgresql.org 7403 : 11 : fprintf(stderr, "end.\n");
7404 : :
6829 7405 [ - + ]: 11 : if (do_vacuum_accounts)
7406 : : {
6067 tgl@sss.pgh.pa.us 7407 :UBC 0 : fprintf(stderr, "starting vacuum pgbench_accounts...");
3871 sfrost@snowman.net 7408 : 0 : tryExecuteStatement(con, "vacuum analyze pgbench_accounts");
9379 bruce@momjian.us 7409 : 0 : fprintf(stderr, "end.\n");
7410 : : }
7411 : : }
7378 ishii@postgresql.org 7412 :CBC 88 : PQfinish(con);
7413 : :
7414 : : /* set up thread data structures */
4823 tgl@sss.pgh.pa.us 7415 : 88 : threads = (TState *) pg_malloc(sizeof(TState) * nthreads);
3819 heikki.linnakangas@i 7416 : 88 : nclients_dealt = 0;
7417 : :
5605 tgl@sss.pgh.pa.us 7418 [ + + ]: 177 : for (i = 0; i < nthreads; i++)
7419 : : {
5364 bruce@momjian.us 7420 : 89 : TState *thread = &threads[i];
7421 : :
5605 tgl@sss.pgh.pa.us 7422 : 89 : thread->tid = i;
3819 heikki.linnakangas@i 7423 : 89 : thread->state = &state[nclients_dealt];
7424 : 89 : thread->nstate =
7425 : 89 : (nclients - nclients_dealt + nthreads - i - 1) / (nthreads - i);
2587 alvherre@alvh.no-ip. 7426 : 89 : initRandomState(&thread->ts_choose_rs);
7427 : 89 : initRandomState(&thread->ts_throttle_rs);
7428 : 89 : initRandomState(&thread->ts_sample_rs);
3477 rhaas@postgresql.org 7429 : 89 : thread->logfile = NULL; /* filled in later */
4082 heikki.linnakangas@i 7430 : 89 : thread->latency_late = 0;
3270 tgl@sss.pgh.pa.us 7431 : 89 : initStats(&thread->stats, 0);
7432 : :
3819 heikki.linnakangas@i 7433 : 89 : nclients_dealt += thread->nstate;
7434 : : }
7435 : :
7436 : : /* all clients must be assigned to a thread */
7437 [ - + ]: 88 : Assert(nclients_dealt == nclients);
7438 : :
7439 : : /* get start up time for the whole computation */
1742 tmunro@postgresql.or 7440 : 88 : start_time = pg_time_now();
7441 : :
7442 : : /* set alarm if duration is specified. */
5979 ishii@postgresql.org 7443 [ - + ]: 88 : if (duration > 0)
5979 ishii@postgresql.org 7444 :UBC 0 : setalarm(duration);
7445 : :
1742 tmunro@postgresql.or 7446 :CBC 88 : errno = THREAD_BARRIER_INIT(&barrier, nthreads);
7447 [ - + ]: 88 : if (errno != 0)
1348 tgl@sss.pgh.pa.us 7448 :UBC 0 : pg_fatal("could not initialize barrier: %m");
7449 : :
7450 : : /* start all threads but thread 0 which is executed directly later */
1742 tmunro@postgresql.or 7451 [ + + ]:CBC 89 : for (i = 1; i < nthreads; i++)
7452 : : {
5364 bruce@momjian.us 7453 : 1 : TState *thread = &threads[i];
7454 : :
1742 tmunro@postgresql.or 7455 : 1 : thread->create_time = pg_time_now();
7456 : 1 : errno = THREAD_CREATE(&thread->thread, threadRun, thread);
7457 : :
7458 [ - + ]: 1 : if (errno != 0)
1348 tgl@sss.pgh.pa.us 7459 :UBC 0 : pg_fatal("could not create thread: %m");
7460 : : }
7461 : :
7462 : : /* compute when to stop */
1742 tmunro@postgresql.or 7463 :CBC 88 : threads[0].create_time = pg_time_now();
3569 rhaas@postgresql.org 7464 [ - + ]: 88 : if (duration > 0)
1742 tmunro@postgresql.or 7465 :UBC 0 : end_time = threads[0].create_time + (int64) 1000000 * duration;
7466 : :
7467 : : /* run thread 0 directly */
1742 tmunro@postgresql.or 7468 :CBC 88 : (void) threadRun(&threads[0]);
7469 : :
7470 : : /* wait for other threads and accumulate results */
3270 tgl@sss.pgh.pa.us 7471 : 87 : initStats(&stats, 0);
1742 tmunro@postgresql.or 7472 : 87 : conn_total_duration = 0;
7473 : :
5979 ishii@postgresql.org 7474 [ + + ]: 174 : for (i = 0; i < nthreads; i++)
7475 : : {
3819 heikki.linnakangas@i 7476 : 87 : TState *thread = &threads[i];
7477 : :
1742 tmunro@postgresql.or 7478 [ - + ]: 87 : if (i > 0)
1742 tmunro@postgresql.or 7479 :UBC 0 : THREAD_JOIN(thread->thread);
7480 : :
2625 peter_e@gmx.net 7481 [ + + ]:CBC 213 : for (int j = 0; j < thread->nstate; j++)
1539 fujii@postgresql.org 7482 [ + + ]: 126 : if (thread->state[j].state != CSTATE_FINISHED)
2625 peter_e@gmx.net 7483 : 52 : exit_code = 2;
7484 : :
7485 : : /* aggregate thread level stats */
3609 alvherre@alvh.no-ip. 7486 : 87 : mergeSimpleStats(&stats.latency, &thread->stats.latency);
7487 : 87 : mergeSimpleStats(&stats.lag, &thread->stats.lag);
7488 : 87 : stats.cnt += thread->stats.cnt;
7489 : 87 : stats.skipped += thread->stats.skipped;
1364 ishii@postgresql.org 7490 : 87 : stats.retries += thread->stats.retries;
7491 : 87 : stats.retried += thread->stats.retried;
7492 : 87 : stats.serialization_failures += thread->stats.serialization_failures;
7493 : 87 : stats.deadlock_failures += thread->stats.deadlock_failures;
39 fujii@postgresql.org 7494 :GNC 87 : stats.other_sql_failures += thread->stats.other_sql_failures;
3774 heikki.linnakangas@i 7495 :CBC 87 : latency_late += thread->latency_late;
1742 tmunro@postgresql.or 7496 : 87 : conn_total_duration += thread->conn_duration;
7497 : :
7498 : : /* first recorded benchmarking start time */
7499 [ - + - - ]: 87 : if (bench_start == 0 || thread->bench_start < bench_start)
7500 : 87 : bench_start = thread->bench_start;
7501 : : }
7502 : :
7503 : : /*
7504 : : * All connections should be already closed in threadRun(), so this
7505 : : * disconnect_all() will be a no-op, but clean up the connections just to
7506 : : * be sure. We don't need to measure the disconnection delays here.
7507 : : */
5979 ishii@postgresql.org 7508 : 87 : disconnect_all(state, nclients);
7509 : :
7510 : : /*
7511 : : * Beware that performance of short benchmarks with many threads and
7512 : : * possibly long transactions can be deceptive because threads do not
7513 : : * start and finish at the exact same time. The total duration computed
7514 : : * here encompasses all transactions so that tps shown is somehow slightly
7515 : : * underestimated.
7516 : : */
1742 tmunro@postgresql.or 7517 : 87 : printResults(&stats, pg_time_now() - bench_start, conn_total_duration,
7518 : : bench_start - start_time, latency_late);
7519 : :
7520 : 87 : THREAD_BARRIER_DESTROY(&barrier);
7521 : :
2625 peter_e@gmx.net 7522 [ + + ]: 87 : if (exit_code != 0)
1348 tgl@sss.pgh.pa.us 7523 : 52 : pg_log_error("Run was aborted; the above results are incomplete.");
7524 : :
2625 peter_e@gmx.net 7525 : 87 : return exit_code;
7526 : : }
7527 : :
7528 : : static THREAD_FUNC_RETURN_TYPE THREAD_FUNC_CC
5979 ishii@postgresql.org 7529 : 89 : threadRun(void *arg)
7530 : : {
7531 : 89 : TState *thread = (TState *) arg;
7532 : 89 : CState *state = thread->state;
7533 : : pg_time_usec_t start;
7534 : 89 : int nstate = thread->nstate;
3100 tgl@sss.pgh.pa.us 7535 : 89 : int remains = nstate; /* number of remaining clients */
2640 7536 : 89 : socket_set *sockets = alloc_socket_set(nstate);
7537 : : int64 thread_start,
7538 : : last_report,
7539 : : next_report;
7540 : : StatsData last,
7541 : : aggs;
7542 : :
7543 : : /* open log file if requested */
5747 itagaki.takahiro@gma 7544 [ + + ]: 89 : if (use_log)
7545 : : {
7546 : : char logpath[MAXPGPATH];
3271 tgl@sss.pgh.pa.us 7547 [ + - ]: 2 : char *prefix = logfile_prefix ? logfile_prefix : "pgbench_log";
7548 : :
5747 itagaki.takahiro@gma 7549 [ + - ]: 2 : if (thread->tid == 0)
3324 rhaas@postgresql.org 7550 : 2 : snprintf(logpath, sizeof(logpath), "%s.%d", prefix, main_pid);
7551 : : else
3324 rhaas@postgresql.org 7552 :UBC 0 : snprintf(logpath, sizeof(logpath), "%s.%d.%d", prefix, main_pid, thread->tid);
7553 : :
3595 alvherre@alvh.no-ip. 7554 :CBC 2 : thread->logfile = fopen(logpath, "w");
7555 : :
7556 [ - + ]: 2 : if (thread->logfile == NULL)
1348 tgl@sss.pgh.pa.us 7557 :UBC 0 : pg_fatal("could not open logfile \"%s\": %m", logpath);
7558 : : }
7559 : :
7560 : : /* explicitly initialize the state machines */
1742 tmunro@postgresql.or 7561 [ + + ]:CBC 217 : for (int i = 0; i < nstate; i++)
7562 : 128 : state[i].state = CSTATE_CHOOSE_SCRIPT;
7563 : :
7564 : : /* READY */
7565 : 89 : THREAD_BARRIER_WAIT(&barrier);
7566 : :
7567 : 89 : thread_start = pg_time_now();
7568 : 89 : thread->started_time = thread_start;
1569 fujii@postgresql.org 7569 : 89 : thread->conn_duration = 0;
1742 tmunro@postgresql.or 7570 : 89 : last_report = thread_start;
7571 : 89 : next_report = last_report + (int64) 1000000 * progress;
7572 : :
7573 : : /* STEADY */
5747 itagaki.takahiro@gma 7574 [ + + ]: 89 : if (!is_connect)
7575 : : {
7576 : : /* make connections to the database before starting */
1742 tmunro@postgresql.or 7577 [ + + ]: 208 : for (int i = 0; i < nstate; i++)
7578 : : {
5979 ishii@postgresql.org 7579 [ - + ]: 121 : if ((state[i].con = doConnect()) == NULL)
7580 : : {
7581 : : /* coldly abort on initial connection failure */
1348 tgl@sss.pgh.pa.us 7582 :UBC 0 : pg_fatal("could not create connection for client %d",
7583 : : state[i].id);
7584 : : }
7585 : : }
7586 : : }
7587 : :
7588 : : /* GO */
1742 tmunro@postgresql.or 7589 :CBC 89 : THREAD_BARRIER_WAIT(&barrier);
7590 : :
7591 : 89 : start = pg_time_now();
7592 : 89 : thread->bench_start = start;
7593 : 89 : thread->throttle_trigger = start;
7594 : :
7595 : : /*
7596 : : * The log format currently has Unix epoch timestamps with whole numbers
7597 : : * of seconds. Round the first aggregate's start time down to the nearest
7598 : : * Unix epoch second (the very first aggregate might really have started a
7599 : : * fraction of a second later, but later aggregates are measured from the
7600 : : * whole number time that is actually logged).
7601 : : */
1619 7602 : 89 : initStats(&aggs, (start + epoch_shift) / 1000000 * 1000000);
1742 7603 : 89 : last = aggs;
7604 : :
7605 : : /* loop till all clients have terminated */
5979 ishii@postgresql.org 7606 [ + + ]: 13096 : while (remains > 0)
7607 : : {
7608 : : int nsocks; /* number of sockets to be waited for */
7609 : : pg_time_usec_t min_usec;
1742 tmunro@postgresql.or 7610 : 13009 : pg_time_usec_t now = 0; /* set this only if needed */
7611 : :
7612 : : /*
7613 : : * identify which client sockets should be checked for input, and
7614 : : * compute the nearest time (if any) at which we need to wake up.
7615 : : */
2640 tgl@sss.pgh.pa.us 7616 : 13009 : clear_socket_set(sockets);
7617 : 13009 : nsocks = 0;
3911 andres@anarazel.de 7618 : 13009 : min_usec = PG_INT64_MAX;
1742 tmunro@postgresql.or 7619 [ + + ]: 56682 : for (int i = 0; i < nstate; i++)
7620 : : {
5979 ishii@postgresql.org 7621 : 49340 : CState *st = &state[i];
7622 : :
2582 alvherre@alvh.no-ip. 7623 [ + + - + ]: 49340 : if (st->state == CSTATE_SLEEP || st->state == CSTATE_THROTTLE)
4529 ishii@postgresql.org 7624 : 3 : {
7625 : : /* a nap from the script, or under throttling */
7626 : : pg_time_usec_t this_usec;
7627 : :
7628 : : /* get current time if needed */
1742 tmunro@postgresql.or 7629 : 3 : pg_time_now_lazy(&now);
7630 : :
7631 : : /* min_usec should be the minimum delay across all clients */
3368 heikki.linnakangas@i 7632 : 6 : this_usec = (st->state == CSTATE_SLEEP ?
1742 tmunro@postgresql.or 7633 [ + - ]: 3 : st->sleep_until : st->txn_scheduled) - now;
3368 heikki.linnakangas@i 7634 [ + - ]: 3 : if (min_usec > this_usec)
7635 : 3 : min_usec = this_usec;
7636 : : }
1364 ishii@postgresql.org 7637 [ + + ]: 49337 : else if (st->state == CSTATE_WAIT_RESULT ||
7638 [ + + ]: 8726 : st->state == CSTATE_WAIT_ROLLBACK_RESULT)
5979 7639 : 40612 : {
7640 : : /*
7641 : : * waiting for result from server - nothing to do unless the
7642 : : * socket is readable
7643 : : */
3368 tgl@sss.pgh.pa.us 7644 : 40612 : int sock = PQsocket(st->con);
7645 : :
heikki.linnakangas@i 7646 [ - + ]: 40612 : if (sock < 0)
7647 : : {
2169 peter@eisentraut.org 7648 :UBC 0 : pg_log_error("invalid socket: %s", PQerrorMessage(st->con));
3368 heikki.linnakangas@i 7649 :CBC 1 : goto done;
7650 : : }
7651 : :
2640 tgl@sss.pgh.pa.us 7652 : 40612 : add_socket_to_set(sockets, sock, nsocks++);
7653 : : }
3368 7654 [ + - ]: 8725 : else if (st->state != CSTATE_ABORTED &&
7655 [ + + ]: 8725 : st->state != CSTATE_FINISHED)
7656 : : {
7657 : : /*
7658 : : * This client thread is ready to do something, so we don't
7659 : : * want to wait. No need to examine additional clients.
7660 : : */
heikki.linnakangas@i 7661 : 5667 : min_usec = 0;
7662 : 5667 : break;
7663 : : }
7664 : : }
7665 : :
7666 : : /* also wake up to print the next progress report on time */
3783 andres@anarazel.de 7667 [ - + - - : 13009 : if (progress && min_usec > 0 && thread->tid == 0)
- - ]
7668 : : {
1742 tmunro@postgresql.or 7669 :UBC 0 : pg_time_now_lazy(&now);
7670 : :
7671 [ # # ]: 0 : if (now >= next_report)
3819 heikki.linnakangas@i 7672 : 0 : min_usec = 0;
1742 tmunro@postgresql.or 7673 [ # # ]: 0 : else if ((next_report - now) < min_usec)
7674 : 0 : min_usec = next_report - now;
7675 : : }
7676 : :
7677 : : /*
7678 : : * If no clients are ready to execute actions, sleep until we receive
7679 : : * data on some client socket or the timeout (if any) elapses.
7680 : : */
2998 heikki.linnakangas@i 7681 [ + + ]:CBC 13009 : if (min_usec > 0)
7682 : : {
2640 tgl@sss.pgh.pa.us 7683 : 7342 : int rc = 0;
7684 : :
3911 andres@anarazel.de 7685 [ + + ]: 7342 : if (min_usec != PG_INT64_MAX)
7686 : : {
2640 tgl@sss.pgh.pa.us 7687 [ - + ]: 3 : if (nsocks > 0)
7688 : : {
2640 tgl@sss.pgh.pa.us 7689 :UBC 0 : rc = wait_on_socket_set(sockets, min_usec);
7690 : : }
7691 : : else /* nothing active, simple sleep */
7692 : : {
2998 heikki.linnakangas@i 7693 :CBC 3 : pg_usleep(min_usec);
7694 : : }
7695 : : }
7696 : : else /* no explicit delay, wait without timeout */
7697 : : {
2640 tgl@sss.pgh.pa.us 7698 : 7339 : rc = wait_on_socket_set(sockets, 0);
7699 : : }
7700 : :
7701 [ - + ]: 7341 : if (rc < 0)
7702 : : {
7383 ishii@postgresql.org 7703 [ # # ]:UBC 0 : if (errno == EINTR)
7704 : : {
7705 : : /* On EINTR, go back to top of loop */
7706 : 0 : continue;
7707 : : }
7708 : : /* must be something wrong */
1539 fujii@postgresql.org 7709 : 0 : pg_log_error("%s() failed: %m", SOCKET_WAIT_METHOD);
5979 ishii@postgresql.org 7710 : 0 : goto done;
7711 : : }
7712 : : }
7713 : : else
7714 : : {
7715 : : /* min_usec <= 0, i.e. something needs to be executed now */
7716 : :
7717 : : /* If we didn't wait, don't try to read any data */
2640 tgl@sss.pgh.pa.us 7718 :CBC 5667 : clear_socket_set(sockets);
7719 : : }
7720 : :
7721 : : /* ok, advance the state machine of each connection */
7722 : 13008 : nsocks = 0;
1742 tmunro@postgresql.or 7723 [ + + ]: 74889 : for (int i = 0; i < nstate; i++)
7724 : : {
5979 ishii@postgresql.org 7725 : 61882 : CState *st = &state[i];
7726 : :
1364 7727 [ + + ]: 61882 : if (st->state == CSTATE_WAIT_RESULT ||
7728 [ + + ]: 11279 : st->state == CSTATE_WAIT_ROLLBACK_RESULT)
9379 bruce@momjian.us 7729 : 10012 : {
7730 : : /* don't call advanceConnectionState unless data is available */
3592 alvherre@alvh.no-ip. 7731 : 50604 : int sock = PQsocket(st->con);
7732 : :
7733 [ - + ]: 50604 : if (sock < 0)
7734 : : {
2169 peter@eisentraut.org 7735 :UBC 0 : pg_log_error("invalid socket: %s", PQerrorMessage(st->con));
3592 alvherre@alvh.no-ip. 7736 : 0 : goto done;
7737 : : }
7738 : :
2640 tgl@sss.pgh.pa.us 7739 [ + + ]:CBC 50604 : if (!socket_has_input(sockets, sock, nsocks++))
3368 7740 : 40592 : continue;
7741 : : }
7742 [ + + ]: 11278 : else if (st->state == CSTATE_FINISHED ||
7743 [ - + ]: 7763 : st->state == CSTATE_ABORTED)
7744 : : {
7745 : : /* this client is done, no need to consider it anymore */
7746 : 3515 : continue;
7747 : : }
7748 : :
2582 alvherre@alvh.no-ip. 7749 : 17775 : advanceConnectionState(thread, st, &aggs);
7750 : :
7751 : : /*
7752 : : * If --exit-on-abort is used, the program is going to exit when
7753 : : * any client is aborted.
7754 : : */
839 ishii@postgresql.org 7755 [ + + + + ]: 17775 : if (exit_on_abort && st->state == CSTATE_ABORTED)
7756 : 1 : goto done;
7757 : :
7758 : : /*
7759 : : * If advanceConnectionState changed client to finished state,
7760 : : * that's one fewer client that remains.
7761 : : */
7762 [ + + ]: 17774 : else if (st->state == CSTATE_FINISHED ||
7763 [ + + ]: 17700 : st->state == CSTATE_ABORTED)
3368 tgl@sss.pgh.pa.us 7764 : 126 : remains--;
7765 : : }
7766 : :
7767 : : /* progress report is made by thread 0 for all threads */
4535 ishii@postgresql.org 7768 [ - + - - ]: 13007 : if (progress && thread->tid == 0)
7769 : : {
1167 drowley@postgresql.o 7770 :UBC 0 : pg_time_usec_t now2 = pg_time_now();
7771 : :
7772 [ # # ]: 0 : if (now2 >= next_report)
7773 : : {
7774 : : /*
7775 : : * Horrible hack: this relies on the thread pointer we are
7776 : : * passed to be equivalent to threads[0], that is the first
7777 : : * entry of the threads array. That is why this MUST be done
7778 : : * by thread 0 and not any other.
7779 : : */
7780 : 0 : printProgressReport(thread, thread_start, now2,
7781 : : &last, &last_report);
7782 : :
7783 : : /*
7784 : : * Ensure that the next report is in the future, in case
7785 : : * pgbench/postgres got stuck somewhere.
7786 : : */
7787 : : do
7788 : : {
1742 tmunro@postgresql.or 7789 : 0 : next_report += (int64) 1000000 * progress;
1167 drowley@postgresql.o 7790 [ # # ]: 0 : } while (now2 >= next_report);
7791 : : }
7792 : : }
7793 : : }
7794 : :
5979 ishii@postgresql.org 7795 :CBC 87 : done:
839 7796 [ + + ]: 88 : if (exit_on_abort)
7797 : : {
7798 : : /*
7799 : : * Abort if any client is not finished, meaning some error occurred.
7800 : : */
7801 [ + + ]: 3 : for (int i = 0; i < nstate; i++)
7802 : : {
7803 [ + + ]: 2 : if (state[i].state != CSTATE_FINISHED)
7804 : : {
7805 : 1 : pg_log_error("Run was aborted due to an error in thread %d",
7806 : : thread->tid);
7807 : 1 : exit(2);
7808 : : }
7809 : : }
7810 : : }
7811 : :
5979 7812 : 87 : disconnect_all(state, nstate);
7813 : :
3595 alvherre@alvh.no-ip. 7814 [ + + ]: 87 : if (thread->logfile)
7815 : : {
3270 tgl@sss.pgh.pa.us 7816 [ - + ]: 2 : if (agg_interval > 0)
7817 : : {
7818 : : /* log aggregated but not yet reported transactions */
3270 tgl@sss.pgh.pa.us 7819 :UBC 0 : doLog(thread, state, &aggs, false, 0, 0);
7820 : : }
3595 alvherre@alvh.no-ip. 7821 :CBC 2 : fclose(thread->logfile);
3270 tgl@sss.pgh.pa.us 7822 : 2 : thread->logfile = NULL;
7823 : : }
2640 7824 : 87 : free_socket_set(sockets);
1742 tmunro@postgresql.or 7825 : 87 : THREAD_FUNC_RETURN;
7826 : : }
7827 : :
7828 : : static void
2847 andres@anarazel.de 7829 : 489 : finishCon(CState *st)
7830 : : {
7831 [ + + ]: 489 : if (st->con != NULL)
7832 : : {
7833 : 230 : PQfinish(st->con);
7834 : 230 : st->con = NULL;
7835 : : }
7836 : 489 : }
7837 : :
7838 : : /*
7839 : : * Support for duration option: set timer_exceeded after so many seconds.
7840 : : */
7841 : :
7842 : : #ifndef WIN32
7843 : :
7844 : : static void
6305 tgl@sss.pgh.pa.us 7845 :UBC 0 : handle_sig_alarm(SIGNAL_ARGS)
7846 : : {
7847 : 0 : timer_exceeded = true;
7848 : 0 : }
7849 : :
7850 : : static void
7851 : 0 : setalarm(int seconds)
7852 : : {
7853 : 0 : pqsignal(SIGALRM, handle_sig_alarm);
7854 : 0 : alarm(seconds);
7855 : 0 : }
7856 : :
7857 : : #else /* WIN32 */
7858 : :
7859 : : static VOID CALLBACK
7860 : : win32_timer_callback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
7861 : : {
7862 : : timer_exceeded = true;
7863 : : }
7864 : :
7865 : : static void
7866 : : setalarm(int seconds)
7867 : : {
7868 : : HANDLE queue;
7869 : : HANDLE timer;
7870 : :
7871 : : /* This function will be called at most once, so we can cheat a bit. */
7872 : : queue = CreateTimerQueue();
7873 : : if (seconds > ((DWORD) -1) / 1000 ||
7874 : : !CreateTimerQueueTimer(&timer, queue,
7875 : : win32_timer_callback, NULL, seconds * 1000, 0,
7876 : : WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE))
7877 : : pg_fatal("failed to set timer");
7878 : : }
7879 : :
7880 : : #endif /* WIN32 */
7881 : :
7882 : :
7883 : : /*
7884 : : * These functions provide an abstraction layer that hides the syscall
7885 : : * we use to wait for input on a set of sockets.
7886 : : *
7887 : : * Currently there are two implementations, based on ppoll(2) and select(2).
7888 : : * ppoll() is preferred where available due to its typically higher ceiling
7889 : : * on the number of usable sockets. We do not use the more-widely-available
7890 : : * poll(2) because it only offers millisecond timeout resolution, which could
7891 : : * be problematic with high --rate settings.
7892 : : *
7893 : : * Function APIs:
7894 : : *
7895 : : * alloc_socket_set: allocate an empty socket set with room for up to
7896 : : * "count" sockets.
7897 : : *
7898 : : * free_socket_set: deallocate a socket set.
7899 : : *
7900 : : * clear_socket_set: reset a socket set to empty.
7901 : : *
7902 : : * add_socket_to_set: add socket with indicated FD to slot "idx" in the
7903 : : * socket set. Slots must be filled in order, starting with 0.
7904 : : *
7905 : : * wait_on_socket_set: wait for input on any socket in set, or for timeout
7906 : : * to expire. timeout is measured in microseconds; 0 means wait forever.
7907 : : * Returns result code of underlying syscall (>=0 if OK, else see errno).
7908 : : *
7909 : : * socket_has_input: after waiting, call this to see if given socket has
7910 : : * input. fd and idx parameters should match some previous call to
7911 : : * add_socket_to_set.
7912 : : *
7913 : : * Note that wait_on_socket_set destructively modifies the state of the
7914 : : * socket set. After checking for input, caller must apply clear_socket_set
7915 : : * and add_socket_to_set again before waiting again.
7916 : : */
7917 : :
7918 : : #ifdef POLL_USING_PPOLL
7919 : :
7920 : : static socket_set *
2640 tgl@sss.pgh.pa.us 7921 :CBC 89 : alloc_socket_set(int count)
7922 : : {
7923 : : socket_set *sa;
7924 : :
7925 : 89 : sa = (socket_set *) pg_malloc0(offsetof(socket_set, pollfds) +
7926 : : sizeof(struct pollfd) * count);
7927 : 89 : sa->maxfds = count;
7928 : 89 : sa->curfds = 0;
7929 : 89 : return sa;
7930 : : }
7931 : :
7932 : : static void
7933 : 87 : free_socket_set(socket_set *sa)
7934 : : {
7935 : 87 : pg_free(sa);
7936 : 87 : }
7937 : :
7938 : : static void
7939 : 18676 : clear_socket_set(socket_set *sa)
7940 : : {
7941 : 18676 : sa->curfds = 0;
7942 : 18676 : }
7943 : :
7944 : : static void
7945 : 40612 : add_socket_to_set(socket_set *sa, int fd, int idx)
7946 : : {
7947 [ + - + - ]: 40612 : Assert(idx < sa->maxfds && idx == sa->curfds);
7948 : 40612 : sa->pollfds[idx].fd = fd;
7949 : 40612 : sa->pollfds[idx].events = POLLIN;
7950 : 40612 : sa->pollfds[idx].revents = 0;
7951 : 40612 : sa->curfds++;
7952 : 40612 : }
7953 : :
7954 : : static int
7955 : 7339 : wait_on_socket_set(socket_set *sa, int64 usecs)
7956 : : {
7957 [ - + ]: 7339 : if (usecs > 0)
7958 : : {
7959 : : struct timespec timeout;
7960 : :
2640 tgl@sss.pgh.pa.us 7961 :UBC 0 : timeout.tv_sec = usecs / 1000000;
7962 : 0 : timeout.tv_nsec = (usecs % 1000000) * 1000;
7963 : 0 : return ppoll(sa->pollfds, sa->curfds, &timeout, NULL);
7964 : : }
7965 : : else
7966 : : {
2640 tgl@sss.pgh.pa.us 7967 :CBC 7339 : return ppoll(sa->pollfds, sa->curfds, NULL, NULL);
7968 : : }
7969 : : }
7970 : :
7971 : : static bool
7972 : 50604 : socket_has_input(socket_set *sa, int fd, int idx)
7973 : : {
7974 : : /*
7975 : : * In some cases, threadRun will apply clear_socket_set and then try to
7976 : : * apply socket_has_input anyway with arguments that it used before that,
7977 : : * or might've used before that except that it exited its setup loop
7978 : : * early. Hence, if the socket set is empty, silently return false
7979 : : * regardless of the parameters. If it's not empty, we can Assert that
7980 : : * the parameters match a previous call.
7981 : : */
7982 [ + + ]: 50604 : if (sa->curfds == 0)
7983 : 18267 : return false;
7984 : :
7985 [ + - + - ]: 32337 : Assert(idx < sa->curfds && sa->pollfds[idx].fd == fd);
7986 : 32337 : return (sa->pollfds[idx].revents & POLLIN) != 0;
7987 : : }
7988 : :
7989 : : #endif /* POLL_USING_PPOLL */
7990 : :
7991 : : #ifdef POLL_USING_SELECT
7992 : :
7993 : : static socket_set *
7994 : : alloc_socket_set(int count)
7995 : : {
7996 : : return (socket_set *) pg_malloc0(sizeof(socket_set));
7997 : : }
7998 : :
7999 : : static void
8000 : : free_socket_set(socket_set *sa)
8001 : : {
8002 : : pg_free(sa);
8003 : : }
8004 : :
8005 : : static void
8006 : : clear_socket_set(socket_set *sa)
8007 : : {
8008 : : FD_ZERO(&sa->fds);
8009 : : sa->maxfd = -1;
8010 : : }
8011 : :
8012 : : static void
8013 : : add_socket_to_set(socket_set *sa, int fd, int idx)
8014 : : {
8015 : : /* See connect_slot() for background on this code. */
8016 : : #ifdef WIN32
8017 : : if (sa->fds.fd_count + 1 >= FD_SETSIZE)
8018 : : {
8019 : : pg_log_error("too many concurrent database clients for this platform: %d",
8020 : : sa->fds.fd_count + 1);
8021 : : exit(1);
8022 : : }
8023 : : #else
8024 : : if (fd < 0 || fd >= FD_SETSIZE)
8025 : : {
8026 : : pg_log_error("socket file descriptor out of range for select(): %d",
8027 : : fd);
8028 : : pg_log_error_hint("Try fewer concurrent database clients.");
8029 : : exit(1);
8030 : : }
8031 : : #endif
8032 : : FD_SET(fd, &sa->fds);
8033 : : if (fd > sa->maxfd)
8034 : : sa->maxfd = fd;
8035 : : }
8036 : :
8037 : : static int
8038 : : wait_on_socket_set(socket_set *sa, int64 usecs)
8039 : : {
8040 : : if (usecs > 0)
8041 : : {
8042 : : struct timeval timeout;
8043 : :
8044 : : timeout.tv_sec = usecs / 1000000;
8045 : : timeout.tv_usec = usecs % 1000000;
8046 : : return select(sa->maxfd + 1, &sa->fds, NULL, NULL, &timeout);
8047 : : }
8048 : : else
8049 : : {
8050 : : return select(sa->maxfd + 1, &sa->fds, NULL, NULL, NULL);
8051 : : }
8052 : : }
8053 : :
8054 : : static bool
8055 : : socket_has_input(socket_set *sa, int fd, int idx)
8056 : : {
8057 : : return (FD_ISSET(fd, &sa->fds) != 0);
8058 : : }
8059 : :
8060 : : #endif /* POLL_USING_SELECT */
|