Commit 86c43f4e authored by Robert Haas's avatar Robert Haas

pgbench: Support double constants and functions.

The new functions are pi(), random(), random_exponential(),
random_gaussian(), and sqrt().  I was worried that this would be
slower than before, but, if anything, it actually turns out to be
slightly faster, because we now express the built-in pgbench scripts
using fewer lines; each \setrandom can be merged into a subsequent
\set.

Fabien Coelho
parent 9bd61311
...@@ -815,9 +815,10 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> ...@@ -815,9 +815,10 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
<listitem> <listitem>
<para> <para>
Sets variable <replaceable>varname</> to an integer value calculated Sets variable <replaceable>varname</> to a value calculated
from <replaceable>expression</>. from <replaceable>expression</>.
The expression may contain integer constants such as <literal>5432</>, The expression may contain integer constants such as <literal>5432</>,
double constants such as <literal>3.14159</>,
references to variables <literal>:</><replaceable>variablename</>, references to variables <literal>:</><replaceable>variablename</>,
unary operators (<literal>+</>, <literal>-</>) and binary operators unary operators (<literal>+</>, <literal>-</>) and binary operators
(<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>, (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
...@@ -830,7 +831,7 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> ...@@ -830,7 +831,7 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
Examples: Examples:
<programlisting> <programlisting>
\set ntellers 10 * :scale \set ntellers 10 * :scale
\set aid (1021 * :aid) % (100000 * :scale) + 1 \set aid (1021 * random(1, 100000 * :scale)) % (100000 * :scale) + 1
</programlisting></para> </programlisting></para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -850,66 +851,35 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> ...@@ -850,66 +851,35 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
</para> </para>
<para> <para>
By default, or when <literal>uniform</> is specified, all values in the <itemizedlist>
range are drawn with equal probability. Specifying <literal>gaussian</> <listitem>
or <literal>exponential</> options modifies this behavior; each <para>
requires a mandatory parameter which determines the precise shape of the <literal>\setrandom n 1 10</> or <literal>\setrandom n 1 10 uniform</>
distribution. is equivalent to <literal>\set n random(1, 10)</> and uses a uniform
</para> distribution.
</para>
</listitem>
<para> <listitem>
For a Gaussian distribution, the interval is mapped onto a standard <para>
normal distribution (the classical bell-shaped Gaussian curve) truncated <literal>\setrandom n 1 10 exponential 3.0</> is equivalent to
at <literal>-parameter</> on the left and <literal>+parameter</> <literal>\set n random_exponential(1, 10, 3.0)</> and uses an
on the right. exponential distribution.
Values in the middle of the interval are more likely to be drawn. </para>
To be precise, if <literal>PHI(x)</> is the cumulative distribution </listitem>
function of the standard normal distribution, with mean <literal>mu</>
defined as <literal>(max + min) / 2.0</>, with
<literallayout>
f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) /
(2.0 * PHI(parameter) - 1.0)
</literallayout>
then value <replaceable>i</> between <replaceable>min</> and
<replaceable>max</> inclusive is drawn with probability:
<literal>f(i + 0.5) - f(i - 0.5)</>.
Intuitively, the larger <replaceable>parameter</>, the more
frequently values close to the middle of the interval are drawn, and the
less frequently values close to the <replaceable>min</> and
<replaceable>max</> bounds. About 67% of values are drawn from the
middle <literal>1.0 / parameter</>, that is a relative
<literal>0.5 / parameter</> around the mean, and 95% in the middle
<literal>2.0 / parameter</>, that is a relative
<literal>1.0 / parameter</> around the mean; for instance, if
<replaceable>parameter</> is 4.0, 67% of values are drawn from the
middle quarter (1.0 / 4.0) of the interval (i.e. from
<literal>3.0 / 8.0</> to <literal>5.0 / 8.0</>) and 95% from
the middle half (<literal>2.0 / 4.0</>) of the interval (second and
third quartiles). The minimum <replaceable>parameter</> is 2.0 for
performance of the Box-Muller transform.
</para>
<para> <listitem>
For an exponential distribution, <replaceable>parameter</> <para>
controls the distribution by truncating a quickly-decreasing <literal>\setrandom n 1 10 gaussian 2.0</> is equivalent to
exponential distribution at <replaceable>parameter</>, and then <literal>\set n random_gaussian(1, 10, 2.0)</>, and uses a gaussian
projecting onto integers between the bounds. distribution.
To be precise, with </para>
<literallayout> </listitem>
f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1.0 - exp(-parameter)) </itemizedlist>
</literallayout>
Then value <replaceable>i</> between <replaceable>min</> and See the documentation of these functions below for further information
<replaceable>max</> inclusive is drawn with probability: about the precise shape of these distributions, depending on the value
<literal>f(x) - f(x + 1)</>. of the parameter.
Intuitively, the larger <replaceable>parameter</>, the more
frequently values close to <replaceable>min</> are accessed, and the
less frequently values close to <replaceable>max</> are accessed.
The closer to 0 <replaceable>parameter</>, the flatter (more uniform)
the access distribution.
A crude approximation of the distribution is that the most frequent 1%
values in the range, close to <replaceable>min</>, are drawn
<replaceable>parameter</>% of the time.
<replaceable>parameter</> value must be strictly positive.
</para> </para>
<para> <para>
...@@ -990,34 +960,6 @@ f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1.0 - exp(-parameter)) ...@@ -990,34 +960,6 @@ f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1.0 - exp(-parameter))
</listitem> </listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
<para>
As an example, the full definition of the built-in TPC-B-like
transaction is:
<programlisting>
\set nbranches :scale
\set ntellers 10 * :scale
\set naccounts 100000 * :scale
\setrandom aid 1 :naccounts
\setrandom bid 1 :nbranches
\setrandom tid 1 :ntellers
\setrandom delta -5000 5000
BEGIN;
UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
END;
</programlisting>
This script allows each iteration of the transaction to reference
different, randomly-chosen rows. (This example also shows why it's
important for each client session to have its own variables &mdash;
otherwise they'd not be independently touching different rows.)
</para>
</refsect2> </refsect2>
<refsect2 id="pgbench-builtin-functions"> <refsect2 id="pgbench-builtin-functions">
...@@ -1046,7 +988,7 @@ END; ...@@ -1046,7 +988,7 @@ END;
<row> <row>
<entry><literal><function>abs(<replaceable>a</>)</></></> <entry><literal><function>abs(<replaceable>a</>)</></></>
<entry>same as <replaceable>a</></> <entry>same as <replaceable>a</></>
<entry>integer value</> <entry>integer or double absolute value</>
<entry><literal>abs(-17)</></> <entry><literal>abs(-17)</></>
<entry><literal>17</></> <entry><literal>17</></>
</row> </row>
...@@ -1054,8 +996,22 @@ END; ...@@ -1054,8 +996,22 @@ END;
<entry><literal><function>debug(<replaceable>a</>)</></></> <entry><literal><function>debug(<replaceable>a</>)</></></>
<entry>same as <replaceable>a</> </> <entry>same as <replaceable>a</> </>
<entry>print to <systemitem>stderr</systemitem> the given argument</> <entry>print to <systemitem>stderr</systemitem> the given argument</>
<entry><literal>debug(5432)</></> <entry><literal>debug(5432.1)</></>
<entry><literal>5432</></> <entry><literal>5432.1</></>
</row>
<row>
<entry><literal><function>double(<replaceable>i</>)</></></>
<entry>double</>
<entry>cast to double</>
<entry><literal>double(5432)</></>
<entry><literal>5432.0</></>
</row>
<row>
<entry><literal><function>int(<replaceable>x</>)</></></>
<entry>integer</>
<entry>cast to int</>
<entry><literal>int(5.4 + 3.8)</></>
<entry><literal>9</></>
</row> </row>
<row> <row>
<entry><literal><function>max(<replaceable>i</> [, <replaceable>...</> ] )</></></> <entry><literal><function>max(<replaceable>i</> [, <replaceable>...</> ] )</></></>
...@@ -1071,9 +1027,143 @@ END; ...@@ -1071,9 +1027,143 @@ END;
<entry><literal>min(5, 4, 3, 2)</></> <entry><literal>min(5, 4, 3, 2)</></>
<entry><literal>2</></> <entry><literal>2</></>
</row> </row>
<row>
<entry><literal><function>pi()</></></>
<entry>double</>
<entry>value of the PI constant</>
<entry><literal>pi()</></>
<entry><literal>3.14159265358979323846</></>
</row>
<row>
<entry><literal><function>random(<replaceable>lb</>, <replaceable>ub</>)</></></>
<entry>integer</>
<entry>uniformly-distributed random integer in <literal>[lb, ub]</></>
<entry><literal>random(1, 10)</></>
<entry>an integer between <literal>1</> and <literal>10</></>
</row>
<row>
<entry><literal><function>random_exponential(<replaceable>lb</>, <replaceable>ub</>, <replaceable>parameter</>)</></></>
<entry>integer</>
<entry>exponentially-distributed random integer in <literal>[lb, ub]</>,
see below</>
<entry><literal>random_exponential(1, 10, 3.0)</></>
<entry>an integer between <literal>1</> and <literal>10</></>
</row>
<row>
<entry><literal><function>random_gaussian(<replaceable>lb</>, <replaceable>ub</>, <replaceable>parameter</>)</></></>
<entry>integer</>
<entry>gaussian-distributed random integer in <literal>[lb, ub]</>,
see below</>
<entry><literal>random_gaussian(1, 10, 2.5)</></>
<entry>an integer between <literal>1</> and <literal>10</></>
</row>
<row>
<entry><literal><function>sqrt(<replaceable>x</>)</></></>
<entry>double</>
<entry>square root</>
<entry><literal>sqrt(2.0)</></>
<entry><literal>1.414213562</></>
</row>
</tbody> </tbody>
</tgroup> </tgroup>
</table> </table>
<para>
The <literal>random</> function generates values using a uniform
distribution, that is all the values are drawn within the specified
range with equal probability. The <literal>random_exponential</> and
<literal>random_gaussian</> functions require an additional double
parameter which determines the precise shape of the distribution.
</para>
<itemizedlist>
<listitem>
<para>
For an exponential distribution, <replaceable>parameter</>
controls the distribution by truncating a quickly-decreasing
exponential distribution at <replaceable>parameter</>, and then
projecting onto integers between the bounds.
To be precise, with
<literallayout>
f(x) = exp(-parameter * (x - min) / (max - min + 1)) / (1 - exp(-parameter))
</literallayout>
Then value <replaceable>i</> between <replaceable>min</> and
<replaceable>max</> inclusive is drawn with probability:
<literal>f(x) - f(x + 1)</>.
</para>
<para>
Intuitively, the larger the <replaceable>parameter</>, the more
frequently values close to <replaceable>min</> are accessed, and the
less frequently values close to <replaceable>max</> are accessed.
The closer to 0 <replaceable>parameter</> is, the flatter (more
uniform) the access distribution.
A crude approximation of the distribution is that the most frequent 1%
values in the range, close to <replaceable>min</>, are drawn
<replaceable>parameter</>% of the time.
The <replaceable>parameter</> value must be strictly positive.
</para>
</listitem>
<listitem>
<para>
For a Gaussian distribution, the interval is mapped onto a standard
normal distribution (the classical bell-shaped Gaussian curve) truncated
at <literal>-parameter</> on the left and <literal>+parameter</>
on the right.
Values in the middle of the interval are more likely to be drawn.
To be precise, if <literal>PHI(x)</> is the cumulative distribution
function of the standard normal distribution, with mean <literal>mu</>
defined as <literal>(max + min) / 2.0</>, with
<literallayout>
f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) /
(2.0 * PHI(parameter) - 1)
</literallayout>
then value <replaceable>i</> between <replaceable>min</> and
<replaceable>max</> inclusive is drawn with probability:
<literal>f(i + 0.5) - f(i - 0.5)</>.
Intuitively, the larger the <replaceable>parameter</>, the more
frequently values close to the middle of the interval are drawn, and the
less frequently values close to the <replaceable>min</> and
<replaceable>max</> bounds. About 67% of values are drawn from the
middle <literal>1.0 / parameter</>, that is a relative
<literal>0.5 / parameter</> around the mean, and 95% in the middle
<literal>2.0 / parameter</>, that is a relative
<literal>1.0 / parameter</> around the mean; for instance, if
<replaceable>parameter</> is 4.0, 67% of values are drawn from the
middle quarter (1.0 / 4.0) of the interval (i.e. from
<literal>3.0 / 8.0</> to <literal>5.0 / 8.0</>) and 95% from
the middle half (<literal>2.0 / 4.0</>) of the interval (second and third
quartiles). The minimum <replaceable>parameter</> is 2.0 for performance
of the Box-Muller transform.
</para>
</listitem>
</itemizedlist>
<para>
As an example, the full definition of the built-in TPC-B-like
transaction is:
<programlisting>
\set aid random(1, 100000 * :scale)
\set bid random(1, 1 * :scale)
\set tid random(1, 10 * :scale)
\set delta random(-5000, 5000)
BEGIN;
UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
END;
</programlisting>
This script allows each iteration of the transaction to reference
different, randomly-chosen rows. (This example also shows why it's
important for each client session to have its own variables &mdash;
otherwise they'd not be independently touching different rows.)
</para>
</refsect2> </refsect2>
<refsect2> <refsect2>
...@@ -1223,13 +1313,10 @@ tps = 618.764555 (including connections establishing) ...@@ -1223,13 +1313,10 @@ tps = 618.764555 (including connections establishing)
tps = 622.977698 (excluding connections establishing) tps = 622.977698 (excluding connections establishing)
script statistics: script statistics:
- statement latencies in milliseconds: - statement latencies in milliseconds:
0.004386 \set nbranches 1 * :scale 0.002522 \set aid random(1, 100000 * :scale)
0.001343 \set ntellers 10 * :scale 0.005459 \set bid random(1, 1 * :scale)
0.001212 \set naccounts 100000 * :scale 0.002348 \set tid random(1, 10 * :scale)
0.001310 \setrandom aid 1 :naccounts 0.001078 \set delta random(-5000, 5000)
0.001073 \setrandom bid 1 :nbranches
0.001005 \setrandom tid 1 :ntellers
0.001078 \setrandom delta -5000 5000
0.326152 BEGIN; 0.326152 BEGIN;
0.603376 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; 0.603376 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
0.454643 SELECT abalance FROM pgbench_accounts WHERE aid = :aid; 0.454643 SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
......
...@@ -20,6 +20,7 @@ PgBenchExpr *expr_parse_result; ...@@ -20,6 +20,7 @@ PgBenchExpr *expr_parse_result;
static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list); static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
static PgBenchExpr *make_integer_constant(int64 ival); static PgBenchExpr *make_integer_constant(int64 ival);
static PgBenchExpr *make_double_constant(double dval);
static PgBenchExpr *make_variable(char *varname); static PgBenchExpr *make_variable(char *varname);
static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator, static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
PgBenchExpr *lexpr, PgBenchExpr *rexpr); PgBenchExpr *lexpr, PgBenchExpr *rexpr);
...@@ -38,6 +39,7 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList * ...@@ -38,6 +39,7 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
%union %union
{ {
int64 ival; int64 ival;
double dval;
char *str; char *str;
PgBenchExpr *expr; PgBenchExpr *expr;
PgBenchExprList *elist; PgBenchExprList *elist;
...@@ -46,9 +48,10 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList * ...@@ -46,9 +48,10 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
%type <elist> elist %type <elist> elist
%type <expr> expr %type <expr> expr
%type <ival> INTEGER function %type <ival> INTEGER function
%type <dval> DOUBLE
%type <str> VARIABLE FUNCTION %type <str> VARIABLE FUNCTION
%token INTEGER VARIABLE FUNCTION %token INTEGER DOUBLE VARIABLE FUNCTION
/* Precedence: lowest to highest */ /* Precedence: lowest to highest */
%left '+' '-' %left '+' '-'
...@@ -74,6 +77,7 @@ expr: '(' expr ')' { $$ = $2; } ...@@ -74,6 +77,7 @@ expr: '(' expr ')' { $$ = $2; }
| expr '/' expr { $$ = make_op(yyscanner, "/", $1, $3); } | expr '/' expr { $$ = make_op(yyscanner, "/", $1, $3); }
| expr '%' expr { $$ = make_op(yyscanner, "%", $1, $3); } | expr '%' expr { $$ = make_op(yyscanner, "%", $1, $3); }
| INTEGER { $$ = make_integer_constant($1); } | INTEGER { $$ = make_integer_constant($1); }
| DOUBLE { $$ = make_double_constant($1); }
| VARIABLE { $$ = make_variable($1); } | VARIABLE { $$ = make_variable($1); }
| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); } | function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
; ;
...@@ -88,8 +92,20 @@ make_integer_constant(int64 ival) ...@@ -88,8 +92,20 @@ make_integer_constant(int64 ival)
{ {
PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
expr->etype = ENODE_INTEGER_CONSTANT; expr->etype = ENODE_CONSTANT;
expr->u.integer_constant.ival = ival; expr->u.constant.type = PGBT_INT;
expr->u.constant.u.ival = ival;
return expr;
}
static PgBenchExpr *
make_double_constant(double dval)
{
PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
expr->etype = ENODE_CONSTANT;
expr->u.constant.type = PGBT_DOUBLE;
expr->u.constant.u.dval = dval;
return expr; return expr;
} }
...@@ -154,6 +170,27 @@ static const struct ...@@ -154,6 +170,27 @@ static const struct
{ {
"debug", 1, PGBENCH_DEBUG "debug", 1, PGBENCH_DEBUG
}, },
{
"pi", 0, PGBENCH_PI
},
{
"sqrt", 1, PGBENCH_SQRT
},
{
"int", 1, PGBENCH_INT
},
{
"double", 1, PGBENCH_DOUBLE
},
{
"random", 2, PGBENCH_RANDOM
},
{
"random_gaussian", 3, PGBENCH_RANDOM_GAUSSIAN
},
{
"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
},
/* keep as last array element */ /* keep as last array element */
{ {
NULL, 0, 0 NULL, 0, 0
......
...@@ -125,6 +125,11 @@ newline [\n] ...@@ -125,6 +125,11 @@ newline [\n]
yylval->ival = strtoint64(yytext); yylval->ival = strtoint64(yytext);
return INTEGER; return INTEGER;
} }
{digit}+(\.{digit}*)?([eE][-+]?{digit}+)? {
yycolumn += yyleng;
yylval->dval = atof(yytext);
return DOUBLE;
}
{alpha}{alnum}* { {alpha}{alnum}* {
yylval->str = pg_strdup(yytext); yylval->str = pg_strdup(yytext);
return FUNCTION; return FUNCTION;
......
...@@ -328,13 +328,10 @@ static const BuiltinScript builtin_script[] = ...@@ -328,13 +328,10 @@ static const BuiltinScript builtin_script[] =
{ {
"tpcb-like", "tpcb-like",
"<builtin: TPC-B (sort of)>", "<builtin: TPC-B (sort of)>",
"\\set nbranches " CppAsString2(nbranches) " * :scale\n" "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
"\\set ntellers " CppAsString2(ntellers) " * :scale\n" "\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n"
"\\set naccounts " CppAsString2(naccounts) " * :scale\n" "\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n"
"\\setrandom aid 1 :naccounts\n" "\\set delta random(-5000, 5000)\n"
"\\setrandom bid 1 :nbranches\n"
"\\setrandom tid 1 :ntellers\n"
"\\setrandom delta -5000 5000\n"
"BEGIN;\n" "BEGIN;\n"
"UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n" "UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n"
"SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
...@@ -346,13 +343,10 @@ static const BuiltinScript builtin_script[] = ...@@ -346,13 +343,10 @@ static const BuiltinScript builtin_script[] =
{ {
"simple-update", "simple-update",
"<builtin: simple update>", "<builtin: simple update>",
"\\set nbranches " CppAsString2(nbranches) " * :scale\n" "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
"\\set ntellers " CppAsString2(ntellers) " * :scale\n" "\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n"
"\\set naccounts " CppAsString2(naccounts) " * :scale\n" "\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n"
"\\setrandom aid 1 :naccounts\n" "\\set delta random(-5000, 5000)\n"
"\\setrandom bid 1 :nbranches\n"
"\\setrandom tid 1 :ntellers\n"
"\\setrandom delta -5000 5000\n"
"BEGIN;\n" "BEGIN;\n"
"UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n" "UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n"
"SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
...@@ -362,15 +356,14 @@ static const BuiltinScript builtin_script[] = ...@@ -362,15 +356,14 @@ static const BuiltinScript builtin_script[] =
{ {
"select-only", "select-only",
"<builtin: select only>", "<builtin: select only>",
"\\set naccounts " CppAsString2(naccounts) " * :scale\n" "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n"
"\\setrandom aid 1 :naccounts\n"
"SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n"
} }
}; };
/* Function prototypes */ /* Function prototypes */
static bool evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval); static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
static void doLog(TState *thread, CState *st, instr_time *now, static void doLog(TState *thread, CState *st, instr_time *now,
StatsData *agg, bool skipped, double latency, double lag); StatsData *agg, bool skipped, double latency, double lag);
static void processXactStats(TState *thread, CState *st, instr_time *now, static void processXactStats(TState *thread, CState *st, instr_time *now,
...@@ -446,6 +439,33 @@ usage(void) ...@@ -446,6 +439,33 @@ usage(void)
progname, progname); progname, progname);
} }
/* 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 * strtoint64 -- convert a string to 64-bit integer
* *
...@@ -542,6 +562,7 @@ getExponentialRand(TState *thread, int64 min, int64 max, double parameter) ...@@ -542,6 +562,7 @@ getExponentialRand(TState *thread, int64 min, int64 max, double parameter)
uniform, uniform,
rand; rand;
/* abort if wrong parameter, but must really be checked beforehand */
Assert(parameter > 0.0); Assert(parameter > 0.0);
cut = exp(-parameter); cut = exp(-parameter);
/* erand in [0, 1), uniform in (0, 1] */ /* erand in [0, 1), uniform in (0, 1] */
...@@ -563,6 +584,9 @@ getGaussianRand(TState *thread, int64 min, int64 max, double parameter) ...@@ -563,6 +584,9 @@ getGaussianRand(TState *thread, int64 min, int64 max, double parameter)
double stdev; double stdev;
double rand; 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 < * Get user specified random number from this loop, with -parameter <
* stdev <= parameter * stdev <= parameter
...@@ -1006,6 +1030,62 @@ getQueryParams(CState *st, const Command *command, const char **params) ...@@ -1006,6 +1030,62 @@ getQueryParams(CState *st, const Command *command, const char **params)
params[i] = getVariable(st, command->argv[i + 1]); params[i] = getVariable(st, command->argv[i + 1]);
} }
/* 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
{
double dval = pval->u.dval;
Assert(pval->type == PGBT_DOUBLE);
if (dval < INT64_MIN || INT64_MAX < dval)
{
fprintf(stderr, "double to int overflow for %f\n", dval);
return false;
}
*ival = (int64) dval;
return true;
}
}
/* 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
{
Assert(pval->type == PGBT_INT);
*dval = (double) pval->u.ival;
return true;
}
}
/* 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;
}
/* maximum number of function arguments */ /* maximum number of function arguments */
#define MAX_FARGS 16 #define MAX_FARGS 16
...@@ -1013,16 +1093,16 @@ getQueryParams(CState *st, const Command *command, const char **params) ...@@ -1013,16 +1093,16 @@ getQueryParams(CState *st, const Command *command, const char **params)
* Recursive evaluation of functions * Recursive evaluation of functions
*/ */
static bool static bool
evalFunc(CState *st, evalFunc(TState *thread, CState *st,
PgBenchFunction func, PgBenchExprLink *args, int64 *retval) PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
{ {
/* evaluate all function arguments */ /* evaluate all function arguments */
int nargs = 0; int nargs = 0;
int64 iargs[MAX_FARGS]; PgBenchValue vargs[MAX_FARGS];
PgBenchExprLink *l = args; PgBenchExprLink *l = args;
for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next) for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
if (!evaluateExpr(st, l->expr, &iargs[nargs])) if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
return false; return false;
if (l != NULL) if (l != NULL)
...@@ -1035,104 +1115,206 @@ evalFunc(CState *st, ...@@ -1035,104 +1115,206 @@ evalFunc(CState *st,
/* then evaluate function */ /* then evaluate function */
switch (func) switch (func)
{ {
/* overloaded operators */
case PGBENCH_ADD: case PGBENCH_ADD:
case PGBENCH_SUB: case PGBENCH_SUB:
case PGBENCH_MUL: case PGBENCH_MUL:
case PGBENCH_DIV: case PGBENCH_DIV:
case PGBENCH_MOD: case PGBENCH_MOD:
{ {
int64 lval = iargs[0], PgBenchValue *lval = &vargs[0],
rval = iargs[1]; *rval = &vargs[1];
Assert(nargs == 2); Assert(nargs == 2);
switch (func) /* overloaded type management, double if some double */
if ((lval->type == PGBT_DOUBLE ||
rval->type == PGBT_DOUBLE) && func != PGBENCH_MOD)
{ {
case PGBENCH_ADD: double ld, rd;
*retval = lval + rval;
return true;
case PGBENCH_SUB: if (!coerceToDouble(lval, &ld) ||
*retval = lval - rval; !coerceToDouble(rval, &rd))
return true; return false;
case PGBENCH_MUL: switch (func)
*retval = lval * rval; {
return true; case PGBENCH_ADD:
setDoubleValue(retval, ld + rd);
return true;
case PGBENCH_DIV: case PGBENCH_SUB:
case PGBENCH_MOD: setDoubleValue(retval, ld - rd);
if (rval == 0) return true;
{
fprintf(stderr, "division by zero\n"); case PGBENCH_MUL:
return false; setDoubleValue(retval, ld * rd);
} return true;
/* special handling of -1 divisor */
if (rval == -1) case PGBENCH_DIV:
{ setDoubleValue(retval, ld / rd);
if (func == PGBENCH_DIV) return true;
default:
/* cannot get here */
Assert(0);
}
}
else /* we have integer operands, or % */
{
int64 li, ri;
if (!coerceToInt(lval, &li) ||
!coerceToInt(rval, &ri))
return false;
switch (func)
{
case PGBENCH_ADD:
setIntValue(retval, li + ri);
return true;
case PGBENCH_SUB:
setIntValue(retval, li - ri);
return true;
case PGBENCH_MUL:
setIntValue(retval, li * ri);
return true;
case PGBENCH_DIV:
case PGBENCH_MOD:
if (ri == 0)
{
fprintf(stderr, "division by zero\n");
return false;
}
/* special handling of -1 divisor */
if (ri == -1)
{ {
/* overflow check (needed for INT64_MIN) */ if (func == PGBENCH_DIV)
if (lval == PG_INT64_MIN)
{ {
fprintf(stderr, "bigint out of range\n"); /* overflow check (needed for INT64_MIN) */
return false; if (li == PG_INT64_MIN)
{
fprintf(stderr, "bigint out of range\n");
return false;
}
else
setIntValue(retval, - li);
} }
else else
*retval = -lval; setIntValue(retval, 0);
return true;
} }
else /* else divisor is not -1 */
*retval = 0; if (func == PGBENCH_DIV)
setIntValue(retval, li / ri);
else /* func == PGBENCH_MOD */
setIntValue(retval, li % ri);
return true; return true;
}
/* divisor is not -1 */
if (func == PGBENCH_DIV)
*retval = lval / rval;
else /* func == PGBENCH_MOD */
*retval = lval % rval;
return true;
default: default:
/* cannot get here */ /* cannot get here */
Assert(0); Assert(0);
}
} }
} }
/* no arguments */
case PGBENCH_PI:
setDoubleValue(retval, M_PI);
return true;
/* 1 overloaded argument */
case PGBENCH_ABS: case PGBENCH_ABS:
{ {
PgBenchValue *varg = &vargs[0];
Assert(nargs == 1); Assert(nargs == 1);
if (iargs[0] < 0) if (varg->type == PGBT_INT)
*retval = -iargs[0]; {
int64 i = varg->u.ival;
setIntValue(retval, i < 0 ? -i : i);
}
else else
*retval = iargs[0]; {
double d = varg->u.dval;
Assert(varg->type == PGBT_DOUBLE);
setDoubleValue(retval, d < 0.0 ? -d: d);
}
return true; return true;
} }
case PGBENCH_DEBUG: case PGBENCH_DEBUG:
{ {
PgBenchValue *varg = &vargs[0];
Assert(nargs == 1);
fprintf(stderr, "debug(script=%d,command=%d): ",
st->use_file, st->state+1);
if (varg->type == PGBT_INT)
fprintf(stderr, "int "INT64_FORMAT"\n", varg->u.ival);
else
{
Assert(varg->type == PGBT_DOUBLE);
fprintf(stderr, "double %f\n", varg->u.dval);
}
*retval = *varg;
return true;
}
/* 1 double argument */
case PGBENCH_DOUBLE:
case PGBENCH_SQRT:
{
double dval;
Assert(nargs == 1); Assert(nargs == 1);
fprintf(stderr, "debug(script=%d,command=%d): " INT64_FORMAT "\n", if (!coerceToDouble(&vargs[0], &dval))
st->use_file, st->state + 1, iargs[0]); return false;
if (func == PGBENCH_SQRT)
dval = sqrt(dval);
setDoubleValue(retval, dval);
return true;
}
/* 1 int argument */
case PGBENCH_INT:
{
int64 ival;
Assert(nargs == 1);
*retval = iargs[0]; if (!coerceToInt(&vargs[0], &ival))
return false;
setIntValue(retval, ival);
return true; return true;
} }
/* variable number of int arguments */
case PGBENCH_MIN: case PGBENCH_MIN:
case PGBENCH_MAX: case PGBENCH_MAX:
{ {
int64 extremum = iargs[0]; int64 extremum;
int i; int i;
Assert(nargs >= 1); Assert(nargs >= 1);
if (!coerceToInt(&vargs[0], &extremum))
return false;
for (i = 1; i < nargs; i++) for (i = 1; i < nargs; i++)
{ {
int64 ival = iargs[i]; int64 ival;
if (!coerceToInt(&vargs[i], &ival))
return false;
if (func == PGBENCH_MIN) if (func == PGBENCH_MIN)
extremum = extremum < ival ? extremum : ival; extremum = extremum < ival ? extremum : ival;
...@@ -1140,13 +1322,84 @@ evalFunc(CState *st, ...@@ -1140,13 +1322,84 @@ evalFunc(CState *st,
extremum = extremum > ival ? extremum : ival; extremum = extremum > ival ? extremum : ival;
} }
*retval = extremum; setIntValue(retval, extremum);
return true; return true;
} }
/* random functions */
case PGBENCH_RANDOM:
case PGBENCH_RANDOM_EXPONENTIAL:
case PGBENCH_RANDOM_GAUSSIAN:
{
int64 imin, imax;
Assert(nargs >= 2);
if (!coerceToInt(&vargs[0], &imin) ||
!coerceToInt(&vargs[1], &imax))
return false;
/* check random range */
if (imin > imax)
{
fprintf(stderr, "empty range given to random\n");
return false;
}
else if (imax - imin < 0 || (imax - imin) + 1 < 0)
{
/* prevent int overflows in random functions */
fprintf(stderr, "random range is too large\n");
return false;
}
if (func == PGBENCH_RANDOM)
{
Assert(nargs == 2);
setIntValue(retval, getrand(thread, 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)
{
fprintf(stderr,
"gaussian parameter must be at least %f "
"(not %f)\n", MIN_GAUSSIAN_PARAM, param);
return false;
}
setIntValue(retval,
getGaussianRand(thread, imin, imax, param));
}
else /* exponential */
{
if (param <= 0.0)
{
fprintf(stderr,
"exponential parameter must be greater than zero"
" (got %f)\n", param);
return false;
}
setIntValue(retval,
getExponentialRand(thread, imin, imax, param));
}
}
return true;
}
default: default:
fprintf(stderr, "unexpected function tag: %d\n", func); /* cannot get here */
exit(1); Assert(0);
/* dead code to avoid a compiler warning */
return false;
} }
} }
...@@ -1157,13 +1410,13 @@ evalFunc(CState *st, ...@@ -1157,13 +1410,13 @@ evalFunc(CState *st,
* the value itself is returned through the retval pointer. * the value itself is returned through the retval pointer.
*/ */
static bool static bool
evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval)
{ {
switch (expr->etype) switch (expr->etype)
{ {
case ENODE_INTEGER_CONSTANT: case ENODE_CONSTANT:
{ {
*retval = expr->u.integer_constant.ival; *retval = expr->u.constant;
return true; return true;
} }
...@@ -1177,24 +1430,39 @@ evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) ...@@ -1177,24 +1430,39 @@ evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval)
expr->u.variable.varname); expr->u.variable.varname);
return false; return false;
} }
*retval = strtoint64(var);
if (is_an_int(var))
{
setIntValue(retval, strtoint64(var));
}
else /* type should be double */
{
double dv;
if (sscanf(var, "%lf", &dv) != 1)
{
fprintf(stderr,
"malformed variable \"%s\" value: \"%s\"\n",
expr->u.variable.varname, var);
return false;
}
setDoubleValue(retval, dv);
}
return true; return true;
} }
case ENODE_FUNCTION: case ENODE_FUNCTION:
return evalFunc(st, return evalFunc(thread, st,
expr->u.function.function, expr->u.function.function,
expr->u.function.args, expr->u.function.args,
retval); retval);
default: default:
/* internal error which should never occur */
fprintf(stderr, "unexpected enode type in evaluation: %d\n", fprintf(stderr, "unexpected enode type in evaluation: %d\n",
expr->etype); expr->etype);
exit(1); exit(1);
} }
fprintf(stderr, "bad expression\n");
return false;
} }
/* /*
...@@ -1673,6 +1941,10 @@ top: ...@@ -1673,6 +1941,10 @@ top:
fprintf(stderr, "\n"); fprintf(stderr, "\n");
} }
/*
* Note: this section could be removed, as the same functionnality
* is available through \set xxx random_gaussian(...)
*/
if (pg_strcasecmp(argv[0], "setrandom") == 0) if (pg_strcasecmp(argv[0], "setrandom") == 0)
{ {
char *var; char *var;
...@@ -1814,15 +2086,21 @@ top: ...@@ -1814,15 +2086,21 @@ top:
{ {
char res[64]; char res[64];
PgBenchExpr *expr = commands[st->state]->expr; PgBenchExpr *expr = commands[st->state]->expr;
int64 result; PgBenchValue result;
if (!evaluateExpr(st, expr, &result)) if (!evaluateExpr(thread, st, expr, &result))
{ {
st->ecnt++; st->ecnt++;
return true; return true;
} }
sprintf(res, INT64_FORMAT, result); if (result.type == PGBT_INT)
sprintf(res, INT64_FORMAT, result.u.ival);
else
{
Assert(result.type == PGBT_DOUBLE);
sprintf(res, "%.18e", result.u.dval);
}
if (!putVariable(st, argv[0], argv[1], res)) if (!putVariable(st, argv[0], argv[1], res))
{ {
......
...@@ -28,10 +28,31 @@ ...@@ -28,10 +28,31 @@
*/ */
union YYSTYPE; union YYSTYPE;
/*
* Variable types used in parser.
*/
typedef enum
{
PGBT_INT,
PGBT_DOUBLE
/* add other types here */
} PgBenchValueType;
typedef struct
{
PgBenchValueType type;
union
{
int64 ival;
double dval;
/* add other types here */
} u;
} PgBenchValue;
/* Types of expression nodes */ /* Types of expression nodes */
typedef enum PgBenchExprType typedef enum PgBenchExprType
{ {
ENODE_INTEGER_CONSTANT, ENODE_CONSTANT,
ENODE_VARIABLE, ENODE_VARIABLE,
ENODE_FUNCTION ENODE_FUNCTION
} PgBenchExprType; } PgBenchExprType;
...@@ -48,6 +69,13 @@ typedef enum PgBenchFunction ...@@ -48,6 +69,13 @@ typedef enum PgBenchFunction
PGBENCH_ABS, PGBENCH_ABS,
PGBENCH_MIN, PGBENCH_MIN,
PGBENCH_MAX, PGBENCH_MAX,
PGBENCH_INT,
PGBENCH_DOUBLE,
PGBENCH_PI,
PGBENCH_SQRT,
PGBENCH_RANDOM,
PGBENCH_RANDOM_GAUSSIAN,
PGBENCH_RANDOM_EXPONENTIAL
} PgBenchFunction; } PgBenchFunction;
typedef struct PgBenchExpr PgBenchExpr; typedef struct PgBenchExpr PgBenchExpr;
...@@ -59,10 +87,7 @@ struct PgBenchExpr ...@@ -59,10 +87,7 @@ struct PgBenchExpr
PgBenchExprType etype; PgBenchExprType etype;
union union
{ {
struct PgBenchValue constant;
{
int64 ival;
} integer_constant;
struct struct
{ {
char *varname; char *varname;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment