Commit 7d67e062 authored by Itagaki Takahiro's avatar Itagaki Takahiro

Add \shell and \setshell meta commands to pgbench.

\shell command runs an external shell command.
\setshell also does the same and sets the result to a variable.

original patch by Michael Paquier with some editorialization by Itagaki,
and reviewed by Greg Smith.
parent cddca5ec
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* A simple benchmark program for PostgreSQL * A simple benchmark program for PostgreSQL
* Originally written by Tatsuo Ishii and enhanced by many contributors. * Originally written by Tatsuo Ishii and enhanced by many contributors.
* *
* $PostgreSQL: pgsql/contrib/pgbench/pgbench.c,v 1.92 2009/12/11 21:50:06 tgl Exp $ * $PostgreSQL: pgsql/contrib/pgbench/pgbench.c,v 1.93 2009/12/15 07:17:57 itagaki Exp $
* Copyright (c) 2000-2009, PostgreSQL Global Development Group * Copyright (c) 2000-2009, PostgreSQL Global Development Group
* ALL RIGHTS RESERVED; * ALL RIGHTS RESERVED;
* *
...@@ -159,6 +159,7 @@ typedef struct ...@@ -159,6 +159,7 @@ typedef struct
} Variable; } Variable;
#define MAX_FILES 128 /* max number of SQL script files allowed */ #define MAX_FILES 128 /* max number of SQL script files allowed */
#define SHELL_COMMAND_SIZE 256 /* maximum size allowed for shell command */
/* /*
* structures used in custom query mode * structures used in custom query mode
...@@ -467,8 +468,8 @@ putVariable(CState *st, char *name, char *value) ...@@ -467,8 +468,8 @@ putVariable(CState *st, char *name, char *value)
var->name = NULL; var->name = NULL;
var->value = NULL; var->value = NULL;
if ((var->name = strdup(name)) == NULL if ((var->name = strdup(name)) == NULL ||
|| (var->value = strdup(value)) == NULL) (var->value = strdup(value)) == NULL)
{ {
free(var->name); free(var->name);
free(var->value); free(var->value);
...@@ -590,6 +591,114 @@ getQueryParams(CState *st, const Command *command, const char **params) ...@@ -590,6 +591,114 @@ 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]);
} }
/*
* 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 whilespace separaters. 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)
{
fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[i]);
return false;
}
arglen = strlen(arg);
if (len + arglen + (i > 0 ? 1 : 0) >= SHELL_COMMAND_SIZE - 1)
{
fprintf(stderr, "%s: too long shell command\n", 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)
fprintf(stderr, "%s: cannot launch shell command\n", argv[0]);
return false;
}
return true;
}
/* Execute the command with pipe and read the standard output. */
if ((fp = popen(command, "r")) == NULL)
{
fprintf(stderr, "%s: cannot launch shell command\n", argv[0]);
return false;
}
if (fgets(res, sizeof(res), fp) == NULL)
{
if (!timer_exceeded)
fprintf(stderr, "%s: cannot read the result\n", argv[0]);
return false;
}
if (pclose(fp) < 0)
{
fprintf(stderr, "%s: cannot close shell command\n", 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')
{
fprintf(stderr, "%s: must return an integer ('%s' returned)\n", argv[0], res);
return false;
}
snprintf(res, sizeof(res), "%d", retval);
if (!putVariable(st, variable, res))
{
fprintf(stderr, "%s: out of memory\n", argv[0]);
return false;
}
#ifdef DEBUG
printf("shell parameter name: %s, value: %s\n", argv[1], res);
#endif
return true;
}
#define MAX_PREPARE_NAME 32 #define MAX_PREPARE_NAME 32
static void static void
preparedStatementName(char *buffer, int file, int state) preparedStatementName(char *buffer, int file, int state)
...@@ -992,7 +1101,34 @@ top: ...@@ -992,7 +1101,34 @@ top:
st->listen = 1; st->listen = 1;
} }
else if (pg_strcasecmp(argv[0], "setshell") == 0)
{
bool ret = runShellCommand(st, argv[1], argv + 2, argc - 2);
if (timer_exceeded) /* timeout */
return clientDone(st, true);
else if (!ret) /* on error */
{
st->ecnt++;
return true;
}
else /* succeeded */
st->listen = 1;
}
else if (pg_strcasecmp(argv[0], "shell") == 0)
{
bool ret = runShellCommand(st, NULL, argv + 1, argc - 1);
if (timer_exceeded) /* timeout */
return clientDone(st, true);
else if (!ret) /* on error */
{
st->ecnt++;
return true;
}
else /* succeeded */
st->listen = 1;
}
goto top; goto top;
} }
...@@ -1081,8 +1217,8 @@ init(void) ...@@ -1081,8 +1217,8 @@ init(void)
for (i = 0; i < ntellers * scale; i++) for (i = 0; i < ntellers * scale; i++)
{ {
snprintf(sql, 256, "insert into pgbench_tellers(tid,bid,tbalance) values (%d,%d,0)" snprintf(sql, 256, "insert into pgbench_tellers(tid,bid,tbalance) values (%d,%d,0)",
,i + 1, i / ntellers + 1); i + 1, i / ntellers + 1);
executeStatement(con, sql); executeStatement(con, sql);
} }
...@@ -1313,6 +1449,22 @@ process_commands(char *buf) ...@@ -1313,6 +1449,22 @@ process_commands(char *buf)
fprintf(stderr, "%s: extra argument \"%s\" ignored\n", fprintf(stderr, "%s: extra argument \"%s\" ignored\n",
my_commands->argv[0], my_commands->argv[j]); my_commands->argv[0], my_commands->argv[j]);
} }
else if (pg_strcasecmp(my_commands->argv[0], "setshell") == 0)
{
if (my_commands->argc < 3)
{
fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
return NULL;
}
}
else if (pg_strcasecmp(my_commands->argv[0], "shell") == 0)
{
if (my_commands->argc < 1)
{
fprintf(stderr, "%s: missing command\n", my_commands->argv[0]);
return NULL;
}
}
else else
{ {
fprintf(stderr, "Invalid command %s\n", my_commands->argv[0]); fprintf(stderr, "Invalid command %s\n", my_commands->argv[0]);
......
<!-- $PostgreSQL: pgsql/doc/src/sgml/pgbench.sgml,v 1.10 2009/08/03 18:30:55 tgl Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/pgbench.sgml,v 1.11 2009/12/15 07:17:57 itagaki Exp $ -->
<sect1 id="pgbench"> <sect1 id="pgbench">
<title>pgbench</title> <title>pgbench</title>
...@@ -466,6 +466,56 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> ...@@ -466,6 +466,56 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
<varlistentry>
<term>
<literal>\setshell <replaceable>varname</> <replaceable>command</> [ <replaceable>argument</> ... ]</literal>
</term>
<listitem>
<para>
Sets variable <replaceable>varname</> to the result of the shell command
<replaceable>command</>. The command must return an integer value
through its standard output.
</para>
<para>
<replaceable>argument</> can be either a text constant or a
<literal>:</><replaceable>variablename</> reference to a variable of
any types. If you want to use <replaceable>argument</> starting with
colons, you need to add an additional colon at the beginning of
<replaceable>argument</>.
</para>
<para>
Example:
<programlisting>
\setshell variable_to_be_assigned command literal_argument :variable ::literal_starting_with_colon
</programlisting>
</para>
</listitem>
</varlistentry>
</variablelist>
<varlistentry>
<term>
<literal>\shell <replaceable>command</> [ <replaceable>argument</> ... ]</literal>
</term>
<listitem>
<para>
Same as <literal>\setshell</literal>, but the result is ignored.
</para>
<para>
Example:
<programlisting>
\shell command literal_argument :variable ::literal_starting_with_colon
</programlisting>
</para>
</listitem>
</varlistentry>
</variablelist>
<para> <para>
As an example, the full definition of the built-in TPC-B-like As an example, the full definition of the built-in TPC-B-like
transaction is: transaction is:
......
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