You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

6786 lines
169 KiB

/*
* pgbench.c
*
* A simple benchmark program for PostgreSQL
* Originally written by Tatsuo Ishii and enhanced by many contributors.
*
* src/bin/pgbench/pgbench.c
* Copyright (c) 2000-2020, PostgreSQL Global Development Group
* ALL RIGHTS RESERVED;
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose, without fee, and without a written agreement
* is hereby granted, provided that the above copyright notice and this
* paragraph and the following two paragraphs appear in all copies.
*
* IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
* LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
* DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
*/
#ifdef WIN32
#define FD_SETSIZE 1024 /* must set before winsock2.h is included */
#endif
#include "postgres_fe.h"
#include <ctype.h>
#include <float.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h> /* for getrlimit */
#endif
/* For testing, PGBENCH_USE_SELECT can be defined to force use of that code */
#if defined(HAVE_PPOLL) && !defined(PGBENCH_USE_SELECT)
#define POLL_USING_PPOLL
#ifdef HAVE_POLL_H
#include <poll.h>
#endif
#else /* no ppoll(), so use select() */
#define POLL_USING_SELECT
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#endif
#include "common/int.h"
#include "common/logging.h"
#include "fe_utils/cancel.h"
#include "fe_utils/conditional.h"
#include "getopt_long.h"
#include "libpq-fe.h"
#include "pgbench.h"
#include "portability/instr_time.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#define ERRCODE_UNDEFINED_TABLE "42P01"
/*
* Hashing constants
*/
#define FNV_PRIME UINT64CONST(0x100000001b3)
#define FNV_OFFSET_BASIS UINT64CONST(0xcbf29ce484222325)
#define MM2_MUL UINT64CONST(0xc6a4a7935bd1e995)
#define MM2_MUL_TIMES_8 UINT64CONST(0x35253c9ade8f4ca8)
#define MM2_ROT 47
/*
* Multi-platform socket set implementations
*/
#ifdef POLL_USING_PPOLL
#define SOCKET_WAIT_METHOD "ppoll"
typedef struct socket_set
{
int maxfds; /* allocated length of pollfds[] array */
int curfds; /* number currently in use */
struct pollfd pollfds[FLEXIBLE_ARRAY_MEMBER];
} socket_set;
#endif /* POLL_USING_PPOLL */
#ifdef POLL_USING_SELECT
#define SOCKET_WAIT_METHOD "select"
typedef struct socket_set
{
int maxfd; /* largest FD currently set in fds */
fd_set fds;
} socket_set;
#endif /* POLL_USING_SELECT */
/*
* Multi-platform pthread implementations
*/
#ifdef WIN32
/* Use native win32 threads on Windows */
typedef struct win32_pthread *pthread_t;
typedef int pthread_attr_t;
static int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
static int pthread_join(pthread_t th, void **thread_return);
#elif defined(ENABLE_THREAD_SAFETY)
/* Use platform-dependent pthread capability */
#include <pthread.h>
#else
/* No threads implementation, use none (-j 1) */
#define pthread_t void *
#endif
/********************************************************************
* some configurable parameters */
#define DEFAULT_INIT_STEPS "dtgvp" /* default -I setting */
#define ALL_INIT_STEPS "dtgGvpf" /* all possible steps */
#define LOG_STEP_SECONDS 5 /* seconds between log messages */
#define DEFAULT_NXACTS 10 /* default nxacts */
#define MIN_GAUSSIAN_PARAM 2.0 /* minimum parameter for gauss */
#define MIN_ZIPFIAN_PARAM 1.001 /* minimum parameter for zipfian */
#define MAX_ZIPFIAN_PARAM 1000.0 /* maximum parameter for zipfian */
int nxacts = 0; /* number of transactions per client */
int duration = 0; /* duration in seconds */
int64 end_time = 0; /* when to stop in micro seconds, under -T */
/*
* scaling factor. for example, scale = 10 will make 1000000 tuples in
* pgbench_accounts table.
*/
int scale = 1;
/*
* fillfactor. for example, fillfactor = 90 will use only 90 percent
* space during inserts and leave 10 percent free.
*/
int fillfactor = 100;
/*
* use unlogged tables?
*/
bool unlogged_tables = false;
/*
* log sampling rate (1.0 = log everything, 0.0 = option not given)
*/
double sample_rate = 0.0;
/*
* When threads are throttled to a given rate limit, this is the target delay
* to reach that rate in usec. 0 is the default and means no throttling.
*/
double throttle_delay = 0;
/*
* Transactions which take longer than this limit (in usec) are counted as
* late, and reported as such, although they are completed anyway. When
* throttling is enabled, execution time slots that are more than this late
* are skipped altogether, and counted separately.
*/
int64 latency_limit = 0;
/*
* tablespace selection
*/
char *tablespace = NULL;
char *index_tablespace = NULL;
/*
* Number of "pgbench_accounts" partitions. 0 is the default and means no
* partitioning.
*/
static int partitions = 0;
/* partitioning strategy for "pgbench_accounts" */
typedef enum
{
PART_NONE, /* no partitioning */
PART_RANGE, /* range partitioning */
PART_HASH /* hash partitioning */
} partition_method_t;
static partition_method_t partition_method = PART_NONE;
static const char *PARTITION_METHOD[] = {"none", "range", "hash"};
/* random seed used to initialize base_random_sequence */
int64 random_seed = -1;
/*
* end of configurable parameters
*********************************************************************/
#define nbranches 1 /* Makes little sense to change this. Change
* -s instead */
#define ntellers 10
#define naccounts 100000
/*
* The scale factor at/beyond which 32bit integers are incapable of storing
* 64bit values.
*
* Although the actual threshold is 21474, we use 20000 because it is easier to
* document and remember, and isn't that far away from the real threshold.
*/
#define SCALE_32BIT_THRESHOLD 20000
bool use_log; /* log transaction latencies to a file */
bool use_quiet; /* quiet logging onto stderr */
int agg_interval; /* log aggregates instead of individual
* transactions */
bool per_script_stats = false; /* whether to collect stats per script */
int progress = 0; /* thread progress report every this seconds */
bool progress_timestamp = false; /* progress report with Unix time */
int nclients = 1; /* number of clients */
int nthreads = 1; /* number of threads */
bool is_connect; /* establish connection for each transaction */
bool report_per_command; /* report per-command latencies */
int main_pid; /* main process id used in log filename */
char *pghost = "";
char *pgport = "";
char *login = NULL;
char *dbName;
char *logfile_prefix = NULL;
const char *progname;
#define WSEP '@' /* weight separator */
volatile bool timer_exceeded = false; /* flag from signal handler */
/*
* Variable definitions.
*
* If a variable only has a string value, "svalue" is that value, and value is
* "not set". If the value is known, "value" contains the value (in any
* variant).
*
* In this case "svalue" contains the string equivalent of the value, if we've
* had occasion to compute that, or NULL if we haven't.
*/
typedef struct
{
char *name; /* variable's name */
char *svalue; /* its value in string form, if known */
PgBenchValue value; /* actual variable's value */
} Variable;
#define MAX_SCRIPTS 128 /* max number of SQL scripts allowed */
#define SHELL_COMMAND_SIZE 256 /* maximum size allowed for shell command */
/*
* Simple data structure to keep stats about something.
*
* XXX probably the first value should be kept and used as an offset for
* better numerical stability...
*/
typedef struct SimpleStats
{
int64 count; /* how many values were encountered */
double min; /* the minimum seen */
double max; /* the maximum seen */
double sum; /* sum of values */
double sum2; /* sum of squared values */
} SimpleStats;
/*
* Data structure to hold various statistics: per-thread and per-script stats
* are maintained and merged together.
*/
typedef struct StatsData
{
time_t start_time; /* interval start time, for aggregates */
int64 cnt; /* number of transactions, including skipped */
int64 skipped; /* number of transactions skipped under --rate
* and --latency-limit */
SimpleStats latency;
SimpleStats lag;
} StatsData;
/*
* Struct to keep random state.
*/
typedef struct RandomState
{
unsigned short xseed[3];
} RandomState;
/* Various random sequences are initialized from this one. */
static RandomState base_random_sequence;
/*
* Connection state machine states.
*/
typedef enum
{
/*
* The client must first choose a script to execute. Once chosen, it can
* either be throttled (state CSTATE_PREPARE_THROTTLE under --rate), start
* right away (state CSTATE_START_TX) or not start at all if the timer was
* exceeded (state CSTATE_FINISHED).
*/
CSTATE_CHOOSE_SCRIPT,
/*
* CSTATE_START_TX performs start-of-transaction processing. Establishes
* a new connection for the transaction in --connect mode, records the
* transaction start time, and proceed to the first command.
*
* Note: once a script is started, it will either error or run till its
* end, where it may be interrupted. It is not interrupted while running,
* so pgbench --time is to be understood as tx are allowed to start in
* that time, and will finish when their work is completed.
*/
CSTATE_START_TX,
/*
* In CSTATE_PREPARE_THROTTLE state, we calculate when to begin the next
* transaction, and advance to CSTATE_THROTTLE. CSTATE_THROTTLE state
* sleeps until that moment, then advances to CSTATE_START_TX, or
* CSTATE_FINISHED if the next transaction would start beyond the end of
* the run.
*/
CSTATE_PREPARE_THROTTLE,
CSTATE_THROTTLE,
/*
* We loop through these states, to process each command in the script:
*
* CSTATE_START_COMMAND starts the execution of a command. On a SQL
* command, the command is sent to the server, and we move to
* CSTATE_WAIT_RESULT state. On a \sleep meta-command, the timer is set,
* and we enter the CSTATE_SLEEP state to wait for it to expire. Other
* meta-commands are executed immediately. If the command about to start
* is actually beyond the end of the script, advance to CSTATE_END_TX.
*
* CSTATE_WAIT_RESULT waits until we get a result set back from the server
* for the current command.
*
* CSTATE_SLEEP waits until the end of \sleep.
*
* CSTATE_END_COMMAND records the end-of-command timestamp, increments the
* command counter, and loops back to CSTATE_START_COMMAND state.
*
* CSTATE_SKIP_COMMAND is used by conditional branches which are not
* executed. It quickly skip commands that do not need any evaluation.
* This state can move forward several commands, till there is something
* to do or the end of the script.
*/
CSTATE_START_COMMAND,
CSTATE_WAIT_RESULT,
CSTATE_SLEEP,
CSTATE_END_COMMAND,
CSTATE_SKIP_COMMAND,
/*
* CSTATE_END_TX performs end-of-transaction processing. It calculates
* latency, and logs the transaction. In --connect mode, it closes the
* current connection.
*
* Then either starts over in CSTATE_CHOOSE_SCRIPT, or enters
* CSTATE_FINISHED if we have no more work to do.
*/
CSTATE_END_TX,
/*
* Final states. CSTATE_ABORTED means that the script execution was
* aborted because a command failed, CSTATE_FINISHED means success.
*/
CSTATE_ABORTED,
CSTATE_FINISHED
} ConnectionStateEnum;
/*
* Connection state.
*/
typedef struct
{
PGconn *con; /* connection handle to DB */
int id; /* client No. */
ConnectionStateEnum state; /* state machine's current state. */
ConditionalStack cstack; /* enclosing conditionals state */
/*
* Separate randomness for each client. This is used for random functions
* PGBENCH_RANDOM_* during the execution of the script.
*/
RandomState cs_func_rs;
int use_file; /* index in sql_script for this client */
int command; /* command number in script */
/* client variables */
Variable *variables; /* array of variable definitions */
int nvariables; /* number of variables */
bool vars_sorted; /* are variables sorted by name? */
/* various times about current transaction */
int64 txn_scheduled; /* scheduled start time of transaction (usec) */
int64 sleep_until; /* scheduled start time of next cmd (usec) */
instr_time txn_begin; /* used for measuring schedule lag times */
instr_time stmt_begin; /* used for measuring statement latencies */
bool prepared[MAX_SCRIPTS]; /* whether client prepared the script */
/* per client collected stats */
int64 cnt; /* client transaction count, for -t */
int ecnt; /* error count */
} CState;
/*
* Thread state
*/
typedef struct
{
int tid; /* thread id */
pthread_t thread; /* thread handle */
CState *state; /* array of CState */
int nstate; /* length of state[] */
/*
* Separate randomness for each thread. Each thread option uses its own
* random state to make all of them independent of each other and
* therefore deterministic at the thread level.
*/
RandomState ts_choose_rs; /* random state for selecting a script */
RandomState ts_throttle_rs; /* random state for transaction throttling */
RandomState ts_sample_rs; /* random state for log sampling */
int64 throttle_trigger; /* previous/next throttling (us) */
FILE *logfile; /* where to log, or NULL */
/* per thread collected stats */
instr_time start_time; /* thread start time */
instr_time conn_time;
StatsData stats;
int64 latency_late; /* executed but late transactions */
} TState;
#define INVALID_THREAD ((pthread_t) 0)
/*
* queries read from files
*/
#define SQL_COMMAND 1
#define META_COMMAND 2
/*
* max number of backslash command arguments or SQL variables,
* including the command or SQL statement itself
*/
#define MAX_ARGS 256
typedef enum MetaCommand
{
META_NONE, /* not a known meta-command */
META_SET, /* \set */
META_SETSHELL, /* \setshell */
META_SHELL, /* \shell */
META_SLEEP, /* \sleep */
META_GSET, /* \gset */
META_IF, /* \if */
META_ELIF, /* \elif */
META_ELSE, /* \else */
META_ENDIF /* \endif */
} MetaCommand;
typedef enum QueryMode
{
QUERY_SIMPLE, /* simple query */
QUERY_EXTENDED, /* extended query */
QUERY_PREPARED, /* extended query with prepared statements */
NUM_QUERYMODE
} QueryMode;
static QueryMode querymode = QUERY_SIMPLE;
static const char *QUERYMODE[] = {"simple", "extended", "prepared"};
/*
* struct Command represents one command in a script.
*
* lines The raw, possibly multi-line command text. Variable substitution
* not applied.
* first_line A short, single-line extract of 'lines', for error reporting.
* type SQL_COMMAND or META_COMMAND
* meta The type of meta-command, or META_NONE if command is SQL
* argc Number of arguments of the command, 0 if not yet processed.
* argv Command arguments, the first of which is the command or SQL
* string itself. For SQL commands, after post-processing
* argv[0] is the same as 'lines' with variables substituted.
* varprefix SQL commands terminated with \gset have this set
* to a non NULL value. If nonempty, it's used to prefix the
* variable name that receives the value.
* expr Parsed expression, if needed.
* stats Time spent in this command.
*/
typedef struct Command
{
PQExpBufferData lines;
char *first_line;
int type;
MetaCommand meta;
int argc;
char *argv[MAX_ARGS];
char *varprefix;
PgBenchExpr *expr;
SimpleStats stats;
} Command;
typedef struct ParsedScript
{
const char *desc; /* script descriptor (eg, file name) */
int weight; /* selection weight */
Command **commands; /* NULL-terminated array of Commands */
StatsData stats; /* total time spent in script */
} ParsedScript;
static ParsedScript sql_script[MAX_SCRIPTS]; /* SQL script files */
static int num_scripts; /* number of scripts in sql_script[] */
static int64 total_weight = 0;
/* Builtin test scripts */
typedef struct BuiltinScript
{
const char *name; /* very short name for -b ... */
const char *desc; /* short description */
const char *script; /* actual pgbench script */
} BuiltinScript;
static const BuiltinScript builtin_script[] =
{
{
"tpcb-like",
"<builtin: TPC-B (sort of)>",
"\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
"\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n"
"\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n"
"\\set delta random(-5000, 5000)\n"
"BEGIN;\n"
"UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n"
"SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
"UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;\n"
"UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;\n"
"INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);\n"
"END;\n"
},
{
"simple-update",
"<builtin: simple update>",
"\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
"\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n"
"\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n"
"\\set delta random(-5000, 5000)\n"
"BEGIN;\n"
"UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n"
"SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
"INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);\n"
"END;\n"
},
{
"select-only",
"<builtin: select only>",
"\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
"SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
}
};
/* Function prototypes */
static void setNullValue(PgBenchValue *pv);
static void setBoolValue(PgBenchValue *pv, bool bval);
static void setIntValue(PgBenchValue *pv, int64 ival);
static void setDoubleValue(PgBenchValue *pv, double dval);
static bool evaluateExpr(CState *st, PgBenchExpr *expr,
PgBenchValue *retval);
static ConnectionStateEnum executeMetaCommand(CState *st, instr_time *now);
static void doLog(TState *thread, CState *st,
StatsData *agg, bool skipped, double latency, double lag);
static void processXactStats(TState *thread, CState *st, instr_time *now,
bool skipped, StatsData *agg);
static void append_fillfactor(char *opts, int len);
static void addScript(ParsedScript script);
static void *threadRun(void *arg);
static void finishCon(CState *st);
static void setalarm(int seconds);
static socket_set *alloc_socket_set(int count);
static void free_socket_set(socket_set *sa);
static void clear_socket_set(socket_set *sa);
static void add_socket_to_set(socket_set *sa, int fd, int idx);
static int wait_on_socket_set(socket_set *sa, int64 usecs);
static bool socket_has_input(socket_set *sa, int fd, int idx);
/* callback functions for our flex lexer */
static const PsqlScanCallbacks pgbench_callbacks = {
NULL, /* don't need get_variable functionality */
};
static void
usage(void)
{
printf("%s is a benchmarking tool for PostgreSQL.\n\n"
"Usage:\n"
" %s [OPTION]... [DBNAME]\n"
"\nInitialization options:\n"
" -i, --initialize invokes initialization mode\n"
" -I, --init-steps=[" ALL_INIT_STEPS "]+ (default \"" DEFAULT_INIT_STEPS "\")\n"
" run selected initialization steps\n"
" -F, --fillfactor=NUM set fill factor\n"
" -n, --no-vacuum do not run VACUUM during initialization\n"
" -q, --quiet quiet logging (one message each 5 seconds)\n"
" -s, --scale=NUM scaling factor\n"
" --foreign-keys create foreign key constraints between tables\n"
" --index-tablespace=TABLESPACE\n"
" create indexes in the specified tablespace\n"
" --partitions=NUM partition pgbench_accounts in NUM parts (default: 0)\n"
" --partition-method=(range|hash)\n"
" partition pgbench_accounts with this method (default: range)\n"
" --tablespace=TABLESPACE create tables in the specified tablespace\n"
" --unlogged-tables create tables as unlogged tables\n"
"\nOptions to select what to run:\n"
" -b, --builtin=NAME[@W] add builtin script NAME weighted at W (default: 1)\n"
" (use \"-b list\" to list available scripts)\n"
" -f, --file=FILENAME[@W] add script FILENAME weighted at W (default: 1)\n"
" -N, --skip-some-updates skip updates of pgbench_tellers and pgbench_branches\n"
" (same as \"-b simple-update\")\n"
" -S, --select-only perform SELECT-only transactions\n"
" (same as \"-b select-only\")\n"
"\nBenchmarking options:\n"
" -c, --client=NUM number of concurrent database clients (default: 1)\n"
" -C, --connect establish new connection for each transaction\n"
" -D, --define=VARNAME=VALUE\n"
" define variable for use by custom script\n"
" -j, --jobs=NUM number of threads (default: 1)\n"
" -l, --log write transaction times to log file\n"
" -L, --latency-limit=NUM count transactions lasting more than NUM ms as late\n"
" -M, --protocol=simple|extended|prepared\n"
" protocol for submitting queries (default: simple)\n"
" -n, --no-vacuum do not run VACUUM before tests\n"
" -P, --progress=NUM show thread progress report every NUM seconds\n"
" -r, --report-latencies report average latency per command\n"
" -R, --rate=NUM target rate in transactions per second\n"
" -s, --scale=NUM report this scale factor in output\n"
" -t, --transactions=NUM number of transactions each client runs (default: 10)\n"
" -T, --time=NUM duration of benchmark test in seconds\n"
" -v, --vacuum-all vacuum all four standard tables before tests\n"
" --aggregate-interval=NUM aggregate data over NUM seconds\n"
" --log-prefix=PREFIX prefix for transaction time log file\n"
" (default: \"pgbench_log\")\n"
" --progress-timestamp use Unix epoch timestamps for progress\n"
" --random-seed=SEED set random seed (\"time\", \"rand\", integer)\n"
" --sampling-rate=NUM fraction of transactions to log (e.g., 0.01 for 1%%)\n"
" --show-script=NAME show builtin script code, then exit\n"
"\nCommon options:\n"
" -d, --debug print debugging output\n"
" -h, --host=HOSTNAME database server host or socket directory\n"
" -p, --port=PORT database server port number\n"
" -U, --username=USERNAME connect as specified database user\n"
" -V, --version output version information, then exit\n"
" -?, --help show this help, then exit\n"
"\n"
"Report bugs to <%s>.\n"
"%s home page: <%s>\n",
progname, progname, PACKAGE_BUGREPORT, PACKAGE_NAME, PACKAGE_URL);
}
/* return whether str matches "^\s*[-+]?[0-9]+$" */
static bool
is_an_int(const char *str)
{
const char *ptr = str;
/* skip leading spaces; cast is consistent with strtoint64 */
while (*ptr && isspace((unsigned char) *ptr))
ptr++;
/* skip sign */
if (*ptr == '+' || *ptr == '-')
ptr++;
/* at least one digit */
if (*ptr && !isdigit((unsigned char) *ptr))
return false;
/* eat all digits */
while (*ptr && isdigit((unsigned char) *ptr))
ptr++;
/* must have reached end of string */
return *ptr == '\0';
}
/*
* strtoint64 -- convert a string to 64-bit integer
*
* This function is a slightly modified version of scanint8() from
* src/backend/utils/adt/int8.c.
*
* The function returns whether the conversion worked, and if so
* "*result" is set to the result.
*
* If not errorOK, an error message is also printed out on errors.
*/
bool
strtoint64(const char *str, bool errorOK, int64 *result)
{
const char *ptr = str;
int64 tmp = 0;
bool neg = false;
/*
* Do our own scan, rather than relying on sscanf which might be broken
* for long long.
*
* As INT64_MIN can't be stored as a positive 64 bit integer, accumulate
* value as a negative number.
*/
/* skip leading spaces */
while (*ptr && isspace((unsigned char) *ptr))
ptr++;
/* handle sign */
if (*ptr == '-')
{
ptr++;
neg = true;
}
else if (*ptr == '+')
ptr++;
/* require at least one digit */
if (unlikely(!isdigit((unsigned char) *ptr)))
goto invalid_syntax;
/* process digits */
while (*ptr && isdigit((unsigned char) *ptr))
{
int8 digit = (*ptr++ - '0');
if (unlikely(pg_mul_s64_overflow(tmp, 10, &tmp)) ||
unlikely(pg_sub_s64_overflow(tmp, digit, &tmp)))
goto out_of_range;
}
/* allow trailing whitespace, but not other trailing chars */
while (*ptr != '\0' && isspace((unsigned char) *ptr))
ptr++;
if (unlikely(*ptr != '\0'))
goto invalid_syntax;
if (!neg)
{
if (unlikely(tmp == PG_INT64_MIN))
goto out_of_range;
tmp = -tmp;
}
*result = tmp;
return true;
out_of_range:
if (!errorOK)
pg_log_error("value \"%s\" is out of range for type bigint", str);
return false;
invalid_syntax:
if (!errorOK)
pg_log_error("invalid input syntax for type bigint: \"%s\"", str);
return false;
}
/* convert string to double, detecting overflows/underflows */
bool
strtodouble(const char *str, bool errorOK, double *dv)
{
char *end;
errno = 0;
*dv = strtod(str, &end);
if (unlikely(errno != 0))
{
if (!errorOK)
pg_log_error("value \"%s\" is out of range for type double", str);
return false;
}
if (unlikely(end == str || *end != '\0'))
{
if (!errorOK)
pg_log_error("invalid input syntax for type double: \"%s\"", str);
return false;
}
return true;
}
/*
* Initialize a random state struct.
*
* We derive the seed from base_random_sequence, which must be set up already.
*/
static void
initRandomState(RandomState *random_state)
{
random_state->xseed[0] = (unsigned short)
(pg_jrand48(base_random_sequence.xseed) & 0xFFFF);
random_state->xseed[1] = (unsigned short)
(pg_jrand48(base_random_sequence.xseed) & 0xFFFF);
random_state->xseed[2] = (unsigned short)
(pg_jrand48(base_random_sequence.xseed) & 0xFFFF);
}
/*
* Random number generator: uniform distribution from min to max inclusive.
*
* Although the limits are expressed as int64, you can't generate the full
* int64 range in one call, because the difference of the limits mustn't
* overflow int64. In practice it's unwise to ask for more than an int32
* range, because of the limited precision of pg_erand48().
*/
static int64
getrand(RandomState *random_state, int64 min, int64 max)
{
/*
* Odd coding is so that min and max have approximately the same chance of
* being selected as do numbers between them.
*
* pg_erand48() is thread-safe and concurrent, which is why we use it
* rather than random(), which in glibc is non-reentrant, and therefore
* protected by a mutex, and therefore a bottleneck on machines with many
* CPUs.
*/
return min + (int64) ((max - min + 1) * pg_erand48(random_state->xseed));
}
/*
* random number generator: exponential distribution from min to max inclusive.
* the parameter is so that the density of probability for the last cut-off max
* value is exp(-parameter).
*/
static int64
getExponentialRand(RandomState *random_state, int64 min, int64 max,
double parameter)
{
double cut,
uniform,
rand;
/* abort if wrong parameter, but must really be checked beforehand */
Assert(parameter > 0.0);
cut = exp(-parameter);
/* erand in [0, 1), uniform in (0, 1] */
uniform = 1.0 - pg_erand48(random_state->xseed);
/*
* inner expression in (cut, 1] (if parameter > 0), rand in [0, 1)
*/
Assert((1.0 - cut) != 0.0);
rand = -log(cut + (1.0 - cut) * uniform) / parameter;
/* return int64 random number within between min and max */
return min + (int64) ((max - min + 1) * rand);
}
/* random number generator: gaussian distribution from min to max inclusive */
static int64
getGaussianRand(RandomState *random_state, int64 min, int64 max,
double parameter)
{
double stdev;
double rand;
/* abort if parameter is too low, but must really be checked beforehand */
Assert(parameter >= MIN_GAUSSIAN_PARAM);
/*
* Get user specified random number from this loop, with -parameter <
* stdev <= parameter
*
* This loop is executed until the number is in the expected range.
*
* As the minimum parameter is 2.0, the probability of looping is low:
* sqrt(-2 ln(r)) <= 2 => r >= e^{-2} ~ 0.135, then when taking the
* average sinus multiplier as 2/pi, we have a 8.6% looping probability in
* the worst case. For a parameter value of 5.0, the looping probability
* is about e^{-5} * 2 / pi ~ 0.43%.
*/
do
{
/*
* pg_erand48 generates [0,1), but for the basic version of the
* Box-Muller transform the two uniformly distributed random numbers
* are expected in (0, 1] (see
* https://en.wikipedia.org/wiki/Box-Muller_transform)
*/
double rand1 = 1.0 - pg_erand48(random_state->xseed);
double rand2 = 1.0 - pg_erand48(random_state->xseed);
/* Box-Muller basic form transform */
double var_sqrt = sqrt(-2.0 * log(rand1));
stdev = var_sqrt * sin(2.0 * M_PI * rand2);
/*
* we may try with cos, but there may be a bias induced if the
* previous value fails the test. To be on the safe side, let us try
* over.
*/
}
while (stdev < -parameter || stdev >= parameter);
/* stdev is in [-parameter, parameter), normalization to [0,1) */
rand = (stdev + parameter) / (parameter * 2.0);
/* return int64 random number within between min and max */
return min + (int64) ((max - min + 1) * rand);
}
/*
* random number generator: generate a value, such that the series of values
* will approximate a Poisson distribution centered on the given value.
*
* Individual results are rounded to integers, though the center value need
* not be one.
*/
static int64
getPoissonRand(RandomState *random_state, double center)
{
/*
* Use inverse transform sampling to generate a value > 0, such that the
* expected (i.e. average) value is the given argument.
*/
double uniform;
/* erand in [0, 1), uniform in (0, 1] */
uniform = 1.0 - pg_erand48(random_state->xseed);
return (int64) (-log(uniform) * center + 0.5);
}
/*
* Computing zipfian using rejection method, based on
* "Non-Uniform Random Variate Generation",
* Luc Devroye, p. 550-551, Springer 1986.
*
* This works for s > 1.0, but may perform badly for s very close to 1.0.
*/
static int64
computeIterativeZipfian(RandomState *random_state, int64 n, double s)
{
double b = pow(2.0, s - 1.0);
double x,
t,
u,
v;
/* Ensure n is sane */
if (n <= 1)
return 1;
while (true)
{
/* random variates */
u = pg_erand48(random_state->xseed);
v = pg_erand48(random_state->xseed);
x = floor(pow(u, -1.0 / (s - 1.0)));
t = pow(1.0 + 1.0 / x, s - 1.0);
/* reject if too large or out of bound */
if (v * x * (t - 1.0) / (b - 1.0) <= t / b && x <= n)
break;
}
return (int64) x;
}
/* random number generator: zipfian distribution from min to max inclusive */
static int64
getZipfianRand(RandomState *random_state, int64 min, int64 max, double s)
{
int64 n = max - min + 1;
/* abort if parameter is invalid */
Assert(MIN_ZIPFIAN_PARAM <= s && s <= MAX_ZIPFIAN_PARAM);
return min - 1 + computeIterativeZipfian(random_state, n, s);
}
/*
* FNV-1a hash function
*/
static int64
getHashFnv1a(int64 val, uint64 seed)
{
int64 result;
int i;
result = FNV_OFFSET_BASIS ^ seed;
for (i = 0; i < 8; ++i)
{
int32 octet = val & 0xff;
val = val >> 8;
result = result ^ octet;
result = result * FNV_PRIME;
}
return result;
}
/*
* Murmur2 hash function
*
* Based on original work of Austin Appleby
* https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp
*/
static int64
getHashMurmur2(int64 val, uint64 seed)
{
uint64 result = seed ^ MM2_MUL_TIMES_8; /* sizeof(int64) */
uint64 k = (uint64) val;
k *= MM2_MUL;
k ^= k >> MM2_ROT;
k *= MM2_MUL;
result ^= k;
result *= MM2_MUL;
result ^= result >> MM2_ROT;
result *= MM2_MUL;
result ^= result >> MM2_ROT;
return (int64) result;
}
/*
* Initialize the given SimpleStats struct to all zeroes
*/
static void
initSimpleStats(SimpleStats *ss)
{
memset(ss, 0, sizeof(SimpleStats));
}
/*
* Accumulate one value into a SimpleStats struct.
*/
static void
addToSimpleStats(SimpleStats *ss, double val)
{
if (ss->count == 0 || val < ss->min)
ss->min = val;
if (ss->count == 0 || val > ss->max)
ss->max = val;
ss->count++;
ss->sum += val;
ss->sum2 += val * val;
}
/*
* Merge two SimpleStats objects
*/
static void
mergeSimpleStats(SimpleStats *acc, SimpleStats *ss)
{
if (acc->count == 0 || ss->min < acc->min)
acc->min = ss->min;
if (acc->count == 0 || ss->max > acc->max)
acc->max = ss->max;
acc->count += ss->count;
acc->sum += ss->sum;
acc->sum2 += ss->sum2;
}
/*
* Initialize a StatsData struct to mostly zeroes, with its start time set to
* the given value.
*/
static void
initStats(StatsData *sd, time_t start_time)
{
sd->start_time = start_time;
sd->cnt = 0;
sd->skipped = 0;
initSimpleStats(&sd->latency);
initSimpleStats(&sd->lag);
}
/*
* Accumulate one additional item into the given stats object.
*/
static void
accumStats(StatsData *stats, bool skipped, double lat, double lag)
{
stats->cnt++;
if (skipped)
{
/* no latency to record on skipped transactions */
stats->skipped++;
}
else
{
addToSimpleStats(&stats->latency, lat);
/* and possibly the same for schedule lag */
if (throttle_delay)
addToSimpleStats(&stats->lag, lag);
}
}
/* call PQexec() and exit() on failure */
static void
executeStatement(PGconn *con, const char *sql)
{
PGresult *res;
res = PQexec(con, sql);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
pg_log_fatal("query failed: %s", PQerrorMessage(con));
pg_log_info("query was: %s", sql);
exit(1);
}
PQclear(res);
}
/* call PQexec() and complain, but without exiting, on failure */
static void
tryExecuteStatement(PGconn *con, const char *sql)
{
PGresult *res;
res = PQexec(con, sql);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
pg_log_error("%s", PQerrorMessage(con));
pg_log_info("(ignoring this error and continuing anyway)");
}
PQclear(res);
}
/* set up a connection to the backend */
static PGconn *
doConnect(void)
{
PGconn *conn;
bool new_pass;
static bool have_password = false;
static char password[100];
/*
* Start the connection. Loop until we have a password if requested by
* backend.
*/
do
{
#define PARAMS_ARRAY_SIZE 7
const char *keywords[PARAMS_ARRAY_SIZE];
const char *values[PARAMS_ARRAY_SIZE];
keywords[0] = "host";
values[0] = pghost;
keywords[1] = "port";
values[1] = pgport;
keywords[2] = "user";
values[2] = login;
keywords[3] = "password";
values[3] = have_password ? password : NULL;
keywords[4] = "dbname";
values[4] = dbName;
keywords[5] = "fallback_application_name";
values[5] = progname;
keywords[6] = NULL;
values[6] = NULL;
new_pass = false;
conn = PQconnectdbParams(keywords, values, true);
if (!conn)
{
pg_log_error("connection to database \"%s\" failed", dbName);
return NULL;
}
if (PQstatus(conn) == CONNECTION_BAD &&
PQconnectionNeedsPassword(conn) &&
!have_password)
{
PQfinish(conn);
simple_prompt("Password: ", password, sizeof(password), false);
have_password = true;
new_pass = true;
}
} while (new_pass);
/* check to see that the backend connection was successfully made */
if (PQstatus(conn) == CONNECTION_BAD)
{
pg_log_error("connection to database \"%s\" failed: %s",
dbName, PQerrorMessage(conn));
PQfinish(conn);
return NULL;
}
return conn;
}
/* qsort comparator for Variable array */
static int
compareVariableNames(const void *v1, const void *v2)
{
return strcmp(((const Variable *) v1)->name,
((const Variable *) v2)->name);
}
/* Locate a variable by name; returns NULL if unknown */
static Variable *
lookupVariable(CState *st, char *name)
{
Variable key;
/* On some versions of Solaris, bsearch of zero items dumps core */
if (st->nvariables <= 0)
return NULL;
/* Sort if we have to */
if (!st->vars_sorted)
{
qsort((void *) st->variables, st->nvariables, sizeof(Variable),
compareVariableNames);
st->vars_sorted = true;
}
/* Now we can search */
key.name = name;
return (Variable *) bsearch((void *) &key,
(void *) st->variables,
st->nvariables,
sizeof(Variable),
compareVariableNames);
}
/* Get the value of a variable, in string form; returns NULL if unknown */
static char *
getVariable(CState *st, char *name)
{
Variable *var;
char stringform[64];
var = lookupVariable(st, name);
if (var == NULL)
return NULL; /* not found */
if (var->svalue)
return var->svalue; /* we have it in string form */
/* We need to produce a string equivalent of the value */
Assert(var->value.type != PGBT_NO_VALUE);
if (var->value.type == PGBT_NULL)
snprintf(stringform, sizeof(stringform), "NULL");
else if (var->value.type == PGBT_BOOLEAN)
snprintf(stringform, sizeof(stringform),
"%s", var->value.u.bval ? "true" : "false");
else if (var->value.type == PGBT_INT)
snprintf(stringform, sizeof(stringform),
INT64_FORMAT, var->value.u.ival);
else if (var->value.type == PGBT_DOUBLE)
snprintf(stringform, sizeof(stringform),
"%.*g", DBL_DIG, var->value.u.dval);
else /* internal error, unexpected type */
Assert(0);
var->svalue = pg_strdup(stringform);
return var->svalue;
}
/* Try to convert variable to a value; return false on failure */
static bool
makeVariableValue(Variable *var)
{
size_t slen;
if (var->value.type != PGBT_NO_VALUE)
return true; /* no work */
slen = strlen(var->svalue);
if (slen == 0)
/* what should it do on ""? */
return false;
if (pg_strcasecmp(var->svalue, "null") == 0)
{
setNullValue(&var->value);
}
/*
* accept prefixes such as y, ye, n, no... but not for "o". 0/1 are
* recognized later as an int, which is converted to bool if needed.
*/
else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
pg_strcasecmp(var->svalue, "on") == 0)
{
setBoolValue(&var->value, true);
}
else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
pg_strncasecmp(var->svalue, "no", slen) == 0 ||
pg_strcasecmp(var->svalue, "off") == 0 ||
pg_strcasecmp(var->svalue, "of") == 0)
{
setBoolValue(&var->value, false);
}
else if (is_an_int(var->svalue))
{
/* if it looks like an int, it must be an int without overflow */
int64 iv;
if (!strtoint64(var->svalue, false, &iv))
return false;
setIntValue(&var->value, iv);
}
else /* type should be double */
{
double dv;
if (!strtodouble(var->svalue, true, &dv))
{
pg_log_error("malformed variable \"%s\" value: \"%s\"",
var->name, var->svalue);
return false;
}
setDoubleValue(&var->value, dv);
}
return true;
}
/*
* Check whether a variable's name is allowed.
*
* We allow any non-ASCII character, as well as ASCII letters, digits, and
* underscore.
*
* Keep this in sync with the definitions of variable name characters in
* "src/fe_utils/psqlscan.l", "src/bin/psql/psqlscanslash.l" and
* "src/bin/pgbench/exprscan.l". Also see parseVariable(), below.
*
* Note: this static function is copied from "src/bin/psql/variables.c"
*/
static bool
valid_variable_name(const char *name)
{
const unsigned char *ptr = (const unsigned char *) name;
/* Mustn't be zero-length */
if (*ptr == '\0')
return false;
while (*ptr)
{
if (IS_HIGHBIT_SET(*ptr) ||
strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
"_0123456789", *ptr) != NULL)
ptr++;
else
return false;
}
return true;
}
/*
* Lookup a variable by name, creating it if need be.
* Caller is expected to assign a value to the variable.
* Returns NULL on failure (bad name).
*/
static Variable *
lookupCreateVariable(CState *st, const char *context, char *name)
{
Variable *var;
var = lookupVariable(st, name);
if (var == NULL)
{
Variable *newvars;
/*
* Check for the name only when declaring a new variable to avoid
* overhead.
*/
if (!valid_variable_name(name))
{
pg_log_error("%s: invalid variable name: \"%s\"", context, name);
return NULL;
}
/* Create variable at the end of the array */
if (st->variables)
newvars = (Variable *) pg_realloc(st->variables,
(st->nvariables + 1) * sizeof(Variable));
else
newvars = (Variable *) pg_malloc(sizeof(Variable));
st->variables = newvars;
var = &newvars[st->nvariables];
var->name = pg_strdup(name);
var->svalue = NULL;
/* caller is expected to initialize remaining fields */
st->nvariables++;
/* we don't re-sort the array till we have to */
st->vars_sorted = false;
}
return var;
}
/* Assign a string value to a variable, creating it if need be */
/* Returns false on failure (bad name) */
static bool
putVariable(CState *st, const char *context, char *name, const char *value)
{
Variable *var;
char *val;
var = lookupCreateVariable(st, context, name);
if (!var)
return false;
/* dup then free, in case value is pointing at this variable */
val = pg_strdup(value);
if (var->svalue)
free(var->svalue);
var->svalue = val;
var->value.type = PGBT_NO_VALUE;
return true;
}
/* Assign a value to a variable, creating it if need be */
/* Returns false on failure (bad name) */
static bool
putVariableValue(CState *st, const char *context, char *name,
const PgBenchValue *value)
{
Variable *var;
var = lookupCreateVariable(st, context, name);
if (!var)
return false;
if (var->svalue)
free(var->svalue);
var->svalue = NULL;
var->value = *value;
return true;
}
/* Assign an integer value to a variable, creating it if need be */
/* Returns false on failure (bad name) */
static bool
putVariableInt(CState *st, const char *context, char *name, int64 value)
{
PgBenchValue val;
setIntValue(&val, value);
return putVariableValue(st, context, name, &val);
}
/*
* Parse a possible variable reference (:varname).
*
* "sql" points at a colon. If what follows it looks like a valid
* variable name, return a malloc'd string containing the variable name,
* and set *eaten to the number of characters consumed.
* Otherwise, return NULL.
*/
static char *
parseVariable(const char *sql, int *eaten)
{
int i = 0;
char *name;
do
{
i++;
} while (IS_HIGHBIT_SET(sql[i]) ||
strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
"_0123456789", sql[i]) != NULL);
if (i == 1)
return NULL; /* no valid variable name chars */
name = pg_malloc(i);
memcpy(name, &sql[1], i - 1);
name[i - 1] = '\0';
*eaten = i;
return name;
}
static char *
replaceVariable(char **sql, char *param, int len, char *value)
{
int valueln = strlen(value);
if (valueln > len)
{
size_t offset = param - *sql;
*sql = pg_realloc(*sql, strlen(*sql) - len + valueln + 1);
param = *sql + offset;
}
if (valueln != len)
memmove(param + valueln, param + len, strlen(param + len) + 1);
memcpy(param, value, valueln);
return param + valueln;
}
static char *
assignVariables(CState *st, char *sql)
{
char *p,
*name,
*val;
p = sql;
while ((p = strchr(p, ':')) != NULL)
{
int eaten;
name = parseVariable(p, &eaten);
if (name == NULL)
{
while (*p == ':')
{
p++;
}
continue;
}
val = getVariable(st, name);
free(name);
if (val == NULL)
{
p++;
continue;
}
p = replaceVariable(&sql, p, eaten, val);
}
return sql;
}
static void
getQueryParams(CState *st, const Command *command, const char **params)
{
int i;
for (i = 0; i < command->argc - 1; i++)
params[i] = getVariable(st, command->argv[i + 1]);
}
static char *
valueTypeName(PgBenchValue *pval)
{
if (pval->type == PGBT_NO_VALUE)
return "none";
else if (pval->type == PGBT_NULL)
return "null";
else if (pval->type == PGBT_INT)
return "int";
else if (pval->type == PGBT_DOUBLE)
return "double";
else if (pval->type == PGBT_BOOLEAN)
return "boolean";
else
{
/* internal error, should never get there */
Assert(false);
return NULL;
}
}
/* get a value as a boolean, or tell if there is a problem */
static bool
coerceToBool(PgBenchValue *pval, bool *bval)
{
if (pval->type == PGBT_BOOLEAN)
{
*bval = pval->u.bval;
return true;
}
else /* NULL, INT or DOUBLE */
{
pg_log_error("cannot coerce %s to boolean", valueTypeName(pval));
*bval = false; /* suppress uninitialized-variable warnings */
return false;
}
}
/*
* Return true or false from an expression for conditional purposes.
* Non zero numerical values are true, zero and NULL are false.
*/
static bool
valueTruth(PgBenchValue *pval)
{
switch (pval->type)
{
case PGBT_NULL:
return false;
case PGBT_BOOLEAN:
return pval->u.bval;
case PGBT_INT:
return pval->u.ival != 0;
case PGBT_DOUBLE:
return pval->u.dval != 0.0;
default:
/* internal error, unexpected type */
Assert(0);
return false;
}
}
/* get a value as an int, tell if there is a problem */
static bool
coerceToInt(PgBenchValue *pval, int64 *ival)
{
if (pval->type == PGBT_INT)
{
*ival = pval->u.ival;
return true;
}
else if (pval->type == PGBT_DOUBLE)
{
double dval = rint(pval->u.dval);
if (isnan(dval) || !FLOAT8_FITS_IN_INT64(dval))
{
pg_log_error("double to int overflow for %f", dval);
return false;
}
*ival = (int64) dval;
return true;
}
else /* BOOLEAN or NULL */
{
pg_log_error("cannot coerce %s to int", valueTypeName(pval));
return false;
}
}
/* get a value as a double, or tell if there is a problem */
static bool
coerceToDouble(PgBenchValue *pval, double *dval)
{
if (pval->type == PGBT_DOUBLE)
{
*dval = pval->u.dval;
return true;
}
else if (pval->type == PGBT_INT)
{
*dval = (double) pval->u.ival;
return true;
}
else /* BOOLEAN or NULL */
{
pg_log_error("cannot coerce %s to double", valueTypeName(pval));
return false;
}
}
/* assign a null value */
static void
setNullValue(PgBenchValue *pv)
{
pv->type = PGBT_NULL;
pv->u.ival = 0;
}
/* assign a boolean value */
static void
setBoolValue(PgBenchValue *pv, bool bval)
{
pv->type = PGBT_BOOLEAN;
pv->u.bval = bval;
}
/* assign an integer value */
static void
setIntValue(PgBenchValue *pv, int64 ival)
{
pv->type = PGBT_INT;
pv->u.ival = ival;
}
/* assign a double value */
static void
setDoubleValue(PgBenchValue *pv, double dval)
{
pv->type = PGBT_DOUBLE;
pv->u.dval = dval;
}
static bool
isLazyFunc(PgBenchFunction func)
{
return func == PGBENCH_AND || func == PGBENCH_OR || func == PGBENCH_CASE;
}
/* lazy evaluation of some functions */
static bool
evalLazyFunc(CState *st,
PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
{
PgBenchValue a1,
a2;
bool ba1,
ba2;
Assert(isLazyFunc(func) && args != NULL && args->next != NULL);
/* args points to first condition */
if (!evaluateExpr(st, args->expr, &a1))
return false;
/* second condition for AND/OR and corresponding branch for CASE */
args = args->next;
switch (func)
{
case PGBENCH_AND:
if (a1.type == PGBT_NULL)
{
setNullValue(retval);
return true;
}
if (!coerceToBool(&a1, &ba1))
return false;
if (!ba1)
{
setBoolValue(retval, false);
return true;
}
if (!evaluateExpr(st, args->expr, &a2))
return false;
if (a2.type == PGBT_NULL)
{
setNullValue(retval);
return true;
}
else if (!coerceToBool(&a2, &ba2))
return false;
else
{
setBoolValue(retval, ba2);
return true;
}
return true;
case PGBENCH_OR:
if (a1.type == PGBT_NULL)
{
setNullValue(retval);
return true;
}
if (!coerceToBool(&a1, &ba1))
return false;
if (ba1)
{
setBoolValue(retval, true);
return true;
}
if (!evaluateExpr(st, args->expr, &a2))
return false;
if (a2.type == PGBT_NULL)
{
setNullValue(retval);
return true;
}
else if (!coerceToBool(&a2, &ba2))
return false;
else
{
setBoolValue(retval, ba2);
return true;
}
case PGBENCH_CASE:
/* when true, execute branch */
if (valueTruth(&a1))
return evaluateExpr(st, args->expr, retval);
/* now args contains next condition or final else expression */
args = args->next;
/* final else case? */
if (args->next == NULL)
return evaluateExpr(st, args->expr, retval);
/* no, another when, proceed */
return evalLazyFunc(st, PGBENCH_CASE, args, retval);
default:
/* internal error, cannot get here */
Assert(0);
break;
}
return false;
}
/* maximum number of function arguments */
#define MAX_FARGS 16
/*
* Recursive evaluation of standard functions,
* which do not require lazy evaluation.
*/
static bool
evalStandardFunc(CState *st,
PgBenchFunction func, PgBenchExprLink *args,
PgBenchValue *retval)
{
/* evaluate all function arguments */
int nargs = 0;
PgBenchValue vargs[MAX_FARGS];
PgBenchExprLink *l = args;
bool has_null = false;
for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
{
if (!evaluateExpr(st, l->expr, &vargs[nargs]))
return false;
has_null |= vargs[nargs].type == PGBT_NULL;
}
if (l != NULL)
{
pg_log_error("too many function arguments, maximum is %d", MAX_FARGS);
return false;
}
/* NULL arguments */
if (has_null && func != PGBENCH_IS && func != PGBENCH_DEBUG)
{
setNullValue(retval);
return true;
}
/* then evaluate function */
switch (func)
{
/* overloaded operators */
case PGBENCH_ADD:
case PGBENCH_SUB:
case PGBENCH_MUL:
case PGBENCH_DIV:
case PGBENCH_MOD:
case PGBENCH_EQ:
case PGBENCH_NE:
case PGBENCH_LE:
case PGBENCH_LT:
{
PgBenchValue *lval = &vargs[0],
*rval = &vargs[1];
Assert(nargs == 2);
/* overloaded type management, double if some double */
if ((lval->type == PGBT_DOUBLE ||
rval->type == PGBT_DOUBLE) && func != PGBENCH_MOD)
{
double ld,
rd;
if (!coerceToDouble(lval, &ld) ||
!coerceToDouble(rval, &rd))
return false;
switch (func)
{
case PGBENCH_ADD:
setDoubleValue(retval, ld + rd);
return true;
case PGBENCH_SUB:
setDoubleValue(retval, ld - rd);
return true;
case PGBENCH_MUL:
setDoubleValue(retval, ld * rd);
return true;
case PGBENCH_DIV:
setDoubleValue(retval, ld / rd);
return true;
case PGBENCH_EQ:
setBoolValue(retval, ld == rd);
return true;
case PGBENCH_NE:
setBoolValue(retval, ld != rd);
return true;
case PGBENCH_LE:
setBoolValue(retval, ld <= rd);
return true;
case PGBENCH_LT:
setBoolValue(retval, ld < rd);
return true;
default:
/* cannot get here */
Assert(0);
}
}
else /* we have integer operands, or % */
{
int64 li,
ri,
res;
if (!coerceToInt(lval, &li) ||
!coerceToInt(rval, &ri))
return false;
switch (func)
{
case PGBENCH_ADD:
if (pg_add_s64_overflow(li, ri, &res))
{
pg_log_error("bigint add out of range");
return false;
}
setIntValue(retval, res);
return true;
case PGBENCH_SUB:
if (pg_sub_s64_overflow(li, ri, &res))
{
pg_log_error("bigint sub out of range");
return false;
}
setIntValue(retval, res);
return true;
case PGBENCH_MUL:
if (pg_mul_s64_overflow(li, ri, &res))
{
pg_log_error("bigint mul out of range");
return false;
}
setIntValue(retval, res);
return true;
case PGBENCH_EQ:
setBoolValue(retval, li == ri);
return true;
case PGBENCH_NE:
setBoolValue(retval, li != ri);
return true;
case PGBENCH_LE:
setBoolValue(retval, li <= ri);
return true;
case PGBENCH_LT:
setBoolValue(retval, li < ri);
return true;
case PGBENCH_DIV:
case PGBENCH_MOD:
if (ri == 0)
{
pg_log_error("division by zero");
return false;
}
/* special handling of -1 divisor */
if (ri == -1)
{
if (func == PGBENCH_DIV)
{
/* overflow check (needed for INT64_MIN) */
if (li == PG_INT64_MIN)
{
pg_log_error("bigint div out of range");
return false;
}
else
setIntValue(retval, -li);
}
else
setIntValue(retval, 0);
return true;
}
/* else divisor is not -1 */
if (func == PGBENCH_DIV)
setIntValue(retval, li / ri);
else /* func == PGBENCH_MOD */
setIntValue(retval, li % ri);
return true;
default:
/* cannot get here */
Assert(0);
}
}
Assert(0);
return false; /* NOTREACHED */
}
/* integer bitwise operators */
case PGBENCH_BITAND:
case PGBENCH_BITOR:
case PGBENCH_BITXOR:
case PGBENCH_LSHIFT:
case PGBENCH_RSHIFT:
{
int64 li,
ri;
if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
return false;
if (func == PGBENCH_BITAND)
setIntValue(retval, li & ri);
else if (func == PGBENCH_BITOR)
setIntValue(retval, li | ri);
else if (func == PGBENCH_BITXOR)
setIntValue(retval, li ^ ri);
else if (func == PGBENCH_LSHIFT)
setIntValue(retval, li << ri);
else if (func == PGBENCH_RSHIFT)
setIntValue(retval, li >> ri);
else /* cannot get here */
Assert(0);
return true;
}
/* logical operators */
case PGBENCH_NOT:
{
bool b;
if (!coerceToBool(&vargs[0], &b))
return false;
setBoolValue(retval, !b);
return true;
}
/* no arguments */
case PGBENCH_PI:
setDoubleValue(retval, M_PI);
return true;
/* 1 overloaded argument */
case PGBENCH_ABS:
{
PgBenchValue *varg = &vargs[0];
Assert(nargs == 1);
if (varg->type == PGBT_INT)
{
int64 i = varg->u.ival;
setIntValue(retval, i < 0 ? -i : i);
}
else
{
double d = varg->u.dval;
Assert(varg->type == PGBT_DOUBLE);
setDoubleValue(retval, d < 0.0 ? -d : d);
}
return true;
}
case PGBENCH_DEBUG:
{
PgBenchValue *varg = &vargs[0];
Assert(nargs == 1);
fprintf(stderr, "debug(script=%d,command=%d): ",
st->use_file, st->command + 1);
if (varg->type == PGBT_NULL)
fprintf(stderr, "null\n");
else if (varg->type == PGBT_BOOLEAN)
fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
else if (varg->type == PGBT_INT)
fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
else if (varg->type == PGBT_DOUBLE)
fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
else /* internal error, unexpected type */
Assert(0);
*retval = *varg;
return true;
}
/* 1 double argument */
case PGBENCH_DOUBLE:
case PGBENCH_SQRT:
case PGBENCH_LN:
case PGBENCH_EXP:
{
double dval;
Assert(nargs == 1);
if (!coerceToDouble(&vargs[0], &dval))
return false;
if (func == PGBENCH_SQRT)
dval = sqrt(dval);
else if (func == PGBENCH_LN)
dval = log(dval);
else if (func == PGBENCH_EXP)
dval = exp(dval);
/* else is cast: do nothing */
setDoubleValue(retval, dval);
return true;
}
/* 1 int argument */
case PGBENCH_INT:
{
int64 ival;
Assert(nargs == 1);
if (!coerceToInt(&vargs[0], &ival))
return false;
setIntValue(retval, ival);
return true;
}
/* variable number of arguments */
case PGBENCH_LEAST:
case PGBENCH_GREATEST:
{
bool havedouble;
int i;
Assert(nargs >= 1);
/* need double result if any input is double */
havedouble = false;
for (i = 0; i < nargs; i++)
{
if (vargs[i].type == PGBT_DOUBLE)
{
havedouble = true;
break;
}
}
if (havedouble)
{
double extremum;
if (!coerceToDouble(&vargs[0], &extremum))
return false;
for (i = 1; i < nargs; i++)
{
double dval;
if (!coerceToDouble(&vargs[i], &dval))
return false;
if (func == PGBENCH_LEAST)
extremum = Min(extremum, dval);
else
extremum = Max(extremum, dval);
}
setDoubleValue(retval, extremum);
}
else
{
int64 extremum;
if (!coerceToInt(&vargs[0], &extremum))
return false;
for (i = 1; i < nargs; i++)
{
int64 ival;
if (!coerceToInt(&vargs[i], &ival))
return false;
if (func == PGBENCH_LEAST)
extremum = Min(extremum, ival);
else
extremum = Max(extremum, ival);
}
setIntValue(retval, extremum);
}
return true;
}
/* random functions */
case PGBENCH_RANDOM:
case PGBENCH_RANDOM_EXPONENTIAL:
case PGBENCH_RANDOM_GAUSSIAN:
case PGBENCH_RANDOM_ZIPFIAN:
{
int64 imin,
imax;
Assert(nargs >= 2);
if (!coerceToInt(&vargs[0], &imin) ||
!coerceToInt(&vargs[1], &imax))
return false;
/* check random range */
if (imin > imax)
{
pg_log_error("empty range given to random");
return false;
}
else if (imax - imin < 0 || (imax - imin) + 1 < 0)
{
/* prevent int overflows in random functions */
pg_log_error("random range is too large");
return false;
}
if (func == PGBENCH_RANDOM)
{
Assert(nargs == 2);
setIntValue(retval, getrand(&st->cs_func_rs, imin, imax));
}
else /* gaussian & exponential */
{
double param;
Assert(nargs == 3);
if (!coerceToDouble(&vargs[2], &param))
return false;
if (func == PGBENCH_RANDOM_GAUSSIAN)
{
if (param < MIN_GAUSSIAN_PARAM)
{
pg_log_error("gaussian parameter must be at least %f (not %f)",
MIN_GAUSSIAN_PARAM, param);
return false;
}
setIntValue(retval,
getGaussianRand(&st->cs_func_rs,
imin, imax, param));
}
else if (func == PGBENCH_RANDOM_ZIPFIAN)
{
if (param < MIN_ZIPFIAN_PARAM || param > MAX_ZIPFIAN_PARAM)
{
pg_log_error("zipfian parameter must be in range [%.3f, %.0f] (not %f)",
MIN_ZIPFIAN_PARAM, MAX_ZIPFIAN_PARAM, param);
return false;
}
setIntValue(retval,
getZipfianRand(&st->cs_func_rs, imin, imax, param));
}
else /* exponential */
{
if (param <= 0.0)
{
pg_log_error("exponential parameter must be greater than zero (not %f)",
param);
return false;
}
setIntValue(retval,
getExponentialRand(&st->cs_func_rs,
imin, imax, param));
}
}
return true;
}
case PGBENCH_POW:
{
PgBenchValue *lval = &vargs[0];
PgBenchValue *rval = &vargs[1];
double ld,
rd;
Assert(nargs == 2);
if (!coerceToDouble(lval, &ld) ||
!coerceToDouble(rval, &rd))
return false;
setDoubleValue(retval, pow(ld, rd));
return true;
}
case PGBENCH_IS:
{
Assert(nargs == 2);
/*
* note: this simple implementation is more permissive than
* SQL
*/
setBoolValue(retval,
vargs[0].type == vargs[1].type &&
vargs[0].u.bval == vargs[1].u.bval);
return true;
}
/* hashing */
case PGBENCH_HASH_FNV1A:
case PGBENCH_HASH_MURMUR2:
{
int64 val,
seed;
Assert(nargs == 2);
if (!coerceToInt(&vargs[0], &val) ||
!coerceToInt(&vargs[1], &seed))
return false;
if (func == PGBENCH_HASH_MURMUR2)
setIntValue(retval, getHashMurmur2(val, seed));
else if (func == PGBENCH_HASH_FNV1A)
setIntValue(retval, getHashFnv1a(val, seed));
else
/* cannot get here */
Assert(0);
return true;
}
default:
/* cannot get here */
Assert(0);
/* dead code to avoid a compiler warning */
return false;
}
}
/* evaluate some function */
static bool
evalFunc(CState *st,
PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
{
if (isLazyFunc(func))
return evalLazyFunc(st, func, args, retval);
else
return evalStandardFunc(st, func, args, retval);
}
/*
* Recursive evaluation of an expression in a pgbench script
* using the current state of variables.
* Returns whether the evaluation was ok,
* the value itself is returned through the retval pointer.
*/
static bool
evaluateExpr(CState *st, PgBenchExpr *expr, PgBenchValue *retval)
{
switch (expr->etype)
{
case ENODE_CONSTANT:
{
*retval = expr->u.constant;
return true;
}
case ENODE_VARIABLE:
{
Variable *var;
if ((var = lookupVariable(st, expr->u.variable.varname)) == NULL)
{
pg_log_error("undefined variable \"%s\"", expr->u.variable.varname);
return false;
}
if (!makeVariableValue(var))
return false;
*retval = var->value;
return true;
}
case ENODE_FUNCTION:
return evalFunc(st,
expr->u.function.function,
expr->u.function.args,
retval);
default:
/* internal error which should never occur */
pg_log_fatal("unexpected enode type in evaluation: %d", expr->etype);
exit(1);
}
}
/*
* Convert command name to meta-command enum identifier
*/
static MetaCommand
getMetaCommand(const char *cmd)
{
MetaCommand mc;
if (cmd == NULL)
mc = META_NONE;
else if (pg_strcasecmp(cmd, "set") == 0)
mc = META_SET;
else if (pg_strcasecmp(cmd, "setshell") == 0)
mc = META_SETSHELL;
else if (pg_strcasecmp(cmd, "shell") == 0)
mc = META_SHELL;
else if (pg_strcasecmp(cmd, "sleep") == 0)
mc = META_SLEEP;
else if (pg_strcasecmp(cmd, "if") == 0)
mc = META_IF;
else if (pg_strcasecmp(cmd, "elif") == 0)
mc = META_ELIF;
else if (pg_strcasecmp(cmd, "else") == 0)
mc = META_ELSE;
else if (pg_strcasecmp(cmd, "endif") == 0)
mc = META_ENDIF;
else if (pg_strcasecmp(cmd, "gset") == 0)
mc = META_GSET;
else
mc = META_NONE;
return mc;
}
/*
* Run a shell command. The result is assigned to the variable if not NULL.
* Return true if succeeded, or false on error.
*/
static bool
runShellCommand(CState *st, char *variable, char **argv, int argc)
{
char command[SHELL_COMMAND_SIZE];
int i,
len = 0;
FILE *fp;
char res[64];
char *endptr;
int retval;
/*----------
* Join arguments with whitespace separators. Arguments starting with
* exactly one colon are treated as variables:
* name - append a string "name"
* :var - append a variable named 'var'
* ::name - append a string ":name"
*----------
*/
for (i = 0; i < argc; i++)
{
char *arg;
int arglen;
if (argv[i][0] != ':')
{
arg = argv[i]; /* a string literal */
}
else if (argv[i][1] == ':')
{
arg = argv[i] + 1; /* a string literal starting with colons */
}
else if ((arg = getVariable(st, argv[i] + 1)) == NULL)
{
pg_log_error("%s: undefined variable \"%s\"", argv[0], argv[i]);
return false;
}
arglen = strlen(arg);
if (len + arglen + (i > 0 ? 1 : 0) >= SHELL_COMMAND_SIZE - 1)
{
pg_log_error("%s: shell command is too long", argv[0]);
return false;
}
if (i > 0)
command[len++] = ' ';
memcpy(command + len, arg, arglen);
len += arglen;
}
command[len] = '\0';
/* Fast path for non-assignment case */
if (variable == NULL)
{
if (system(command))
{
if (!timer_exceeded)
pg_log_error("%s: could not launch shell command", argv[0]);
return false;
}
return true;
}
/* Execute the command with pipe and read the standard output. */
if ((fp = popen(command, "r")) == NULL)
{
pg_log_error("%s: could not launch shell command", argv[0]);
return false;
}
if (fgets(res, sizeof(res), fp) == NULL)
{
if (!timer_exceeded)
pg_log_error("%s: could not read result of shell command", argv[0]);
(void) pclose(fp);
return false;
}
if (pclose(fp) < 0)
{
pg_log_error("%s: could not close shell command", argv[0]);
return false;
}
/* Check whether the result is an integer and assign it to the variable */
retval = (int) strtol(res, &endptr, 10);
while (*endptr != '\0' && isspace((unsigned char) *endptr))
endptr++;
if (*res == '\0' || *endptr != '\0')
{
pg_log_error("%s: shell command must return an integer (not \"%s\")", argv[0], res);
return false;
}
if (!putVariableInt(st, "setshell", variable, retval))
return false;
pg_log_debug("%s: shell parameter name: \"%s\", value: \"%s\"", argv[0], argv[1], res);
return true;
}
#define MAX_PREPARE_NAME 32
static void
preparedStatementName(char *buffer, int file, int state)
{
sprintf(buffer, "P%d_%d", file, state);
}
static void
commandFailed(CState *st, const char *cmd, const char *message)
{
pg_log_error("client %d aborted in command %d (%s) of script %d; %s",
st->id, st->command, cmd, st->use_file, message);
}
/* return a script number with a weighted choice. */
static int
chooseScript(TState *thread)
{
int i = 0;
int64 w;
if (num_scripts == 1)
return 0;
w = getrand(&thread->ts_choose_rs, 0, total_weight - 1);
do
{
w -= sql_script[i++].weight;
} while (w >= 0);
return i - 1;
}
/* Send a SQL command, using the chosen querymode */
static bool
sendCommand(CState *st, Command *command)
{
int r;
if (querymode == QUERY_SIMPLE)
{
char *sql;
sql = pg_strdup(command->argv[0]);
sql = assignVariables(st, sql);
pg_log_debug("client %d sending %s", st->id, sql);
r = PQsendQuery(st->con, sql);
free(sql);
}
else if (querymode == QUERY_EXTENDED)
{
const char *sql = command->argv[0];
const char *params[MAX_ARGS];
getQueryParams(st, command, params);
pg_log_debug("client %d sending %s", st->id, sql);
r = PQsendQueryParams(st->con, sql, command->argc - 1,
NULL, params, NULL, NULL, 0);
}
else if (querymode == QUERY_PREPARED)
{
char name[MAX_PREPARE_NAME];
const char *params[MAX_ARGS];
if (!st->prepared[st->use_file])
{
int j;
Command **commands = sql_script[st->use_file].commands;
for (j = 0; commands[j] != NULL; j++)
{
PGresult *res;
char name[MAX_PREPARE_NAME];
if (commands[j]->type != SQL_COMMAND)
continue;
preparedStatementName(name, st->use_file, j);
res = PQprepare(st->con, name,
commands[j]->argv[0], commands[j]->argc - 1, NULL);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
pg_log_error("%s", PQerrorMessage(st->con));
PQclear(res);
}
st->prepared[st->use_file] = true;
}
getQueryParams(st, command, params);
preparedStatementName(name, st->use_file, st->command);
pg_log_debug("client %d sending %s", st->id, name);
r = PQsendQueryPrepared(st->con, name, command->argc - 1,
params, NULL, NULL, 0);
}
else /* unknown sql mode */
r = 0;
if (r == 0)
{
pg_log_debug("client %d could not send %s", st->id, command->argv[0]);
st->ecnt++;
return false;
}
else
return true;
}
/*
* Process query response from the backend.
*
* If varprefix is not NULL, it's the variable name prefix where to store
* the results of the *last* command.
*
* Returns true if everything is A-OK, false if any error occurs.
*/
static bool
readCommandResponse(CState *st, char *varprefix)
{
PGresult *res;
PGresult *next_res;
int qrynum = 0;
res = PQgetResult(st->con);
while (res != NULL)
{
bool is_last;
/* peek at the next result to know whether the current is last */
next_res = PQgetResult(st->con);
is_last = (next_res == NULL);
switch (PQresultStatus(res))
{
case PGRES_COMMAND_OK: /* non-SELECT commands */
case PGRES_EMPTY_QUERY: /* may be used for testing no-op overhead */
if (is_last && varprefix != NULL)
{
pg_log_error("client %d script %d command %d query %d: expected one row, got %d",
st->id, st->use_file, st->command, qrynum, 0);
goto error;
}
break;
case PGRES_TUPLES_OK:
if (is_last && varprefix != NULL)
{
if (PQntuples(res) != 1)
{
pg_log_error("client %d script %d command %d query %d: expected one row, got %d",
st->id, st->use_file, st->command, qrynum, PQntuples(res));
goto error;
}
/* store results into variables */
for (int fld = 0; fld < PQnfields(res); fld++)
{
char *varname = PQfname(res, fld);
/* allocate varname only if necessary, freed below */