Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 7d67e06

Browse files
committed
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.
1 parent cddca5e commit 7d67e06

File tree

2 files changed

+208
-6
lines changed

2 files changed

+208
-6
lines changed

contrib/pgbench/pgbench.c

Lines changed: 157 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* A simple benchmark program for PostgreSQL
55
* Originally written by Tatsuo Ishii and enhanced by many contributors.
66
*
7-
* $PostgreSQL: pgsql/contrib/pgbench/pgbench.c,v 1.92 2009/12/11 21:50:06 tgl Exp $
7+
* $PostgreSQL: pgsql/contrib/pgbench/pgbench.c,v 1.93 2009/12/15 07:17:57 itagaki Exp $
88
* Copyright (c) 2000-2009, PostgreSQL Global Development Group
99
* ALL RIGHTS RESERVED;
1010
*
@@ -159,6 +159,7 @@ typedef struct
159159
} Variable;
160160

161161
#define MAX_FILES 128 /* max number of SQL script files allowed */
162+
#define SHELL_COMMAND_SIZE 256 /* maximum size allowed for shell command */
162163

163164
/*
164165
* structures used in custom query mode
@@ -467,8 +468,8 @@ putVariable(CState *st, char *name, char *value)
467468
var->name = NULL;
468469
var->value = NULL;
469470

470-
if ((var->name = strdup(name)) == NULL
471-
|| (var->value = strdup(value)) == NULL)
471+
if ((var->name = strdup(name)) == NULL ||
472+
(var->value = strdup(value)) == NULL)
472473
{
473474
free(var->name);
474475
free(var->value);
@@ -590,6 +591,114 @@ getQueryParams(CState *st, const Command *command, const char **params)
590591
params[i] = getVariable(st, command->argv[i + 1]);
591592
}
592593

594+
/*
595+
* Run a shell command. The result is assigned to the variable if not NULL.
596+
* Return true if succeeded, or false on error.
597+
*/
598+
static bool
599+
runShellCommand(CState *st, char *variable, char **argv, int argc)
600+
{
601+
char command[SHELL_COMMAND_SIZE];
602+
int i,
603+
len = 0;
604+
FILE *fp;
605+
char res[64];
606+
char *endptr;
607+
int retval;
608+
609+
/*
610+
* Join arguments with whilespace separaters. Arguments starting with
611+
* exactly one colon are treated as variables:
612+
* name - append a string "name"
613+
* :var - append a variable named 'var'.
614+
* ::name - append a string ":name"
615+
*/
616+
for (i = 0; i < argc; i++)
617+
{
618+
char *arg;
619+
int arglen;
620+
621+
if (argv[i][0] != ':')
622+
{
623+
arg = argv[i]; /* a string literal */
624+
}
625+
else if (argv[i][1] == ':')
626+
{
627+
arg = argv[i] + 1; /* a string literal starting with colons */
628+
}
629+
else if ((arg = getVariable(st, argv[i] + 1)) == NULL)
630+
{
631+
fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[i]);
632+
return false;
633+
}
634+
635+
arglen = strlen(arg);
636+
if (len + arglen + (i > 0 ? 1 : 0) >= SHELL_COMMAND_SIZE - 1)
637+
{
638+
fprintf(stderr, "%s: too long shell command\n", argv[0]);
639+
return false;
640+
}
641+
642+
if (i > 0)
643+
command[len++] = ' ';
644+
memcpy(command + len, arg, arglen);
645+
len += arglen;
646+
}
647+
648+
command[len] = '\0';
649+
650+
/* Fast path for non-assignment case */
651+
if (variable == NULL)
652+
{
653+
if (system(command))
654+
{
655+
if (!timer_exceeded)
656+
fprintf(stderr, "%s: cannot launch shell command\n", argv[0]);
657+
return false;
658+
}
659+
return true;
660+
}
661+
662+
/* Execute the command with pipe and read the standard output. */
663+
if ((fp = popen(command, "r")) == NULL)
664+
{
665+
fprintf(stderr, "%s: cannot launch shell command\n", argv[0]);
666+
return false;
667+
}
668+
if (fgets(res, sizeof(res), fp) == NULL)
669+
{
670+
if (!timer_exceeded)
671+
fprintf(stderr, "%s: cannot read the result\n", argv[0]);
672+
return false;
673+
}
674+
if (pclose(fp) < 0)
675+
{
676+
fprintf(stderr, "%s: cannot close shell command\n", argv[0]);
677+
return false;
678+
}
679+
680+
/* Check whether the result is an integer and assign it to the variable */
681+
retval = (int) strtol(res, &endptr, 10);
682+
while (*endptr != '\0' && isspace((unsigned char) *endptr))
683+
endptr++;
684+
if (*res == '\0' || *endptr != '\0')
685+
{
686+
fprintf(stderr, "%s: must return an integer ('%s' returned)\n", argv[0], res);
687+
return false;
688+
}
689+
snprintf(res, sizeof(res), "%d", retval);
690+
if (!putVariable(st, variable, res))
691+
{
692+
fprintf(stderr, "%s: out of memory\n", argv[0]);
693+
return false;
694+
}
695+
696+
#ifdef DEBUG
697+
printf("shell parameter name: %s, value: %s\n", argv[1], res);
698+
#endif
699+
return true;
700+
}
701+
593702
#define MAX_PREPARE_NAME 32
594703
static void
595704
preparedStatementName(char *buffer, int file, int state)
@@ -992,7 +1101,34 @@ doCustom(CState *st, instr_time *conn_time)
9921101

9931102
st->listen = 1;
9941103
}
1104+
else if (pg_strcasecmp(argv[0], "setshell") == 0)
1105+
{
1106+
bool ret = runShellCommand(st, argv[1], argv + 2, argc - 2);
9951107

1108+
if (timer_exceeded) /* timeout */
1109+
return clientDone(st, true);
1110+
else if (!ret) /* on error */
1111+
{
1112+
st->ecnt++;
1113+
return true;
1114+
}
1115+
else /* succeeded */
1116+
st->listen = 1;
1117+
}
1118+
else if (pg_strcasecmp(argv[0], "shell") == 0)
1119+
{
1120+
bool ret = runShellCommand(st, NULL, argv + 1, argc - 1);
1121+
1122+
if (timer_exceeded) /* timeout */
1123+
return clientDone(st, true);
1124+
else if (!ret) /* on error */
1125+
{
1126+
st->ecnt++;
1127+
return true;
1128+
}
1129+
else /* succeeded */
1130+
st->listen = 1;
1131+
}
9961132
goto top;
9971133
}
9981134

@@ -1081,8 +1217,8 @@ init(void)
10811217

10821218
for (i = 0; i < ntellers * scale; i++)
10831219
{
1084-
snprintf(sql, 256, "insert into pgbench_tellers(tid,bid,tbalance) values (%d,%d,0)"
1085-
,i + 1, i / ntellers + 1);
1220+
snprintf(sql, 256, "insert into pgbench_tellers(tid,bid,tbalance) values (%d,%d,0)",
1221+
i + 1, i / ntellers + 1);
10861222
executeStatement(con, sql);
10871223
}
10881224

@@ -1313,6 +1449,22 @@ process_commands(char *buf)
13131449
fprintf(stderr, "%s: extra argument \"%s\" ignored\n",
13141450
my_commands->argv[0], my_commands->argv[j]);
13151451
}
1452+
else if (pg_strcasecmp(my_commands->argv[0], "setshell") == 0)
1453+
{
1454+
if (my_commands->argc < 3)
1455+
{
1456+
fprintf(stderr, "%s: missing argument\n", my_commands->argv[0]);
1457+
return NULL;
1458+
}
1459+
}
1460+
else if (pg_strcasecmp(my_commands->argv[0], "shell") == 0)
1461+
{
1462+
if (my_commands->argc < 1)
1463+
{
1464+
fprintf(stderr, "%s: missing command\n", my_commands->argv[0]);
1465+
return NULL;
1466+
}
1467+
}
13161468
else
13171469
{
13181470
fprintf(stderr, "Invalid command %s\n", my_commands->argv[0]);

doc/src/sgml/pgbench.sgml

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- $PostgreSQL: pgsql/doc/src/sgml/pgbench.sgml,v 1.10 2009/08/03 18:30:55 tgl Exp $ -->
1+
<!-- $PostgreSQL: pgsql/doc/src/sgml/pgbench.sgml,v 1.11 2009/12/15 07:17:57 itagaki Exp $ -->
22

33
<sect1 id="pgbench">
44
<title>pgbench</title>
@@ -466,6 +466,56 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
466466
</varlistentry>
467467
</variablelist>
468468

469+
<varlistentry>
470+
<term>
471+
<literal>\setshell <replaceable>varname</> <replaceable>command</> [ <replaceable>argument</> ... ]</literal>
472+
</term>
473+
474+
<listitem>
475+
<para>
476+
Sets variable <replaceable>varname</> to the result of the shell command
477+
<replaceable>command</>. The command must return an integer value
478+
through its standard output.
479+
</para>
480+
481+
<para>
482+
<replaceable>argument</> can be either a text constant or a
483+
<literal>:</><replaceable>variablename</> reference to a variable of
484+
any types. If you want to use <replaceable>argument</> starting with
485+
colons, you need to add an additional colon at the beginning of
486+
<replaceable>argument</>.
487+
</para>
488+
489+
<para>
490+
Example:
491+
<programlisting>
492+
\setshell variable_to_be_assigned command literal_argument :variable ::literal_starting_with_colon
493+
</programlisting>
494+
</para>
495+
</listitem>
496+
</varlistentry>
497+
</variablelist>
498+
499+
<varlistentry>
500+
<term>
501+
<literal>\shell <replaceable>command</> [ <replaceable>argument</> ... ]</literal>
502+
</term>
503+
504+
<listitem>
505+
<para>
506+
Same as <literal>\setshell</literal>, but the result is ignored.
507+
</para>
508+
509+
<para>
510+
Example:
511+
<programlisting>
512+
\shell command literal_argument :variable ::literal_starting_with_colon
513+
</programlisting>
514+
</para>
515+
</listitem>
516+
</varlistentry>
517+
</variablelist>
518+
469519
<para>
470520
As an example, the full definition of the built-in TPC-B-like
471521
transaction is:

0 commit comments

Comments
 (0)