Skip to content

Commit

Permalink
Add PSQL_WATCH_PAGER for psql's \watch command.
Browse files Browse the repository at this point in the history
Allow a pager to be used by the \watch command.  This works but isn't
very useful with traditional pagers like "less", so use a different
environment variable.  The popular open source tool "pspg" (also by
Pavel) knows how to display the output if you set PSQL_WATCH_PAGER="pspg
--stream".

To make \watch react quickly when the user quits the pager or presses
^C, and also to increase the accuracy of its timing and decrease the
rate of useless context switches, change the main loop of the \watch
command to use sigwait() rather than a sleeping/polling loop, on Unix.

Supported on Unix only for now (like pspg).

Author: Pavel Stehule <[email protected]>
Author: Thomas Munro <[email protected]>
Discussion: https://postgr.es/m/CAFj8pRBfzUUPz-3gN5oAzto9SDuRSq-TQPfXU_P6h0L7hO%2BEhg%40mail.gmail.com
  • Loading branch information
macdice committed Jul 12, 2021
1 parent f014b1b commit 7c09d27
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 15 deletions.
28 changes: 28 additions & 0 deletions doc/src/sgml/ref/psql-ref.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -3002,6 +3002,16 @@ lo_import 152801
(such as <filename>more</filename>) is used.
</para>

<para>
When using the <literal>\watch</literal> command to execute a query
repeatedly, the environment variable <envar>PSQL_WATCH_PAGER</envar>
is used to find the pager program instead, on Unix systems. This is
configured separately because it may confuse traditional pagers, but
can be used to send output to tools that understand
<application>psql</application>'s output format (such as
<filename>pspg --stream</filename>).
</para>

<para>
When the <literal>pager</literal> option is <literal>off</literal>, the pager
program is not used. When the <literal>pager</literal> option is
Expand Down Expand Up @@ -4672,6 +4682,24 @@ PSQL_EDITOR_LINENUMBER_ARG='--line '
</listitem>
</varlistentry>

<varlistentry>
<term><envar>PSQL_WATCH_PAGER</envar></term>

<listitem>
<para>
When a query is executed repeatedly with the <command>\watch</command>
command, a pager is not used by default. This behavior can be changed
by setting <envar>PSQL_WATCH_PAGER</envar> to a pager command, on Unix
systems. The <literal>pspg</literal> pager (not part of
<productname>PostgreSQL</productname> but available in many open source
software distributions) can display the output of
<command>\watch</command> if started with the option
<literal>--stream</literal>.
</para>

</listitem>
</varlistentry>

<varlistentry>
<term><envar>PSQLRC</envar></term>

Expand Down
133 changes: 124 additions & 9 deletions src/bin/psql/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <utime.h>
#ifndef WIN32
#include <sys/stat.h> /* for stat() */
#include <sys/time.h> /* for setitimer() */
#include <fcntl.h> /* open() flags */
#include <unistd.h> /* for geteuid(), getpid(), stat() */
#else
Expand Down Expand Up @@ -4894,15 +4895,76 @@ do_watch(PQExpBuffer query_buf, double sleep)
const char *strftime_fmt;
const char *user_title;
char *title;
const char *pagerprog = NULL;
FILE *pagerpipe = NULL;
int title_len;
int res = 0;
#ifndef WIN32
sigset_t sigalrm_sigchld_sigint;
sigset_t sigalrm_sigchld;
sigset_t sigint;
struct itimerval interval;
bool done = false;
#endif

if (!query_buf || query_buf->len <= 0)
{
pg_log_error("\\watch cannot be used with an empty query");
return false;
}

#ifndef WIN32
sigemptyset(&sigalrm_sigchld_sigint);
sigaddset(&sigalrm_sigchld_sigint, SIGCHLD);
sigaddset(&sigalrm_sigchld_sigint, SIGALRM);
sigaddset(&sigalrm_sigchld_sigint, SIGINT);

sigemptyset(&sigalrm_sigchld);
sigaddset(&sigalrm_sigchld, SIGCHLD);
sigaddset(&sigalrm_sigchld, SIGALRM);

sigemptyset(&sigint);
sigaddset(&sigint, SIGINT);

/*
* Block SIGALRM and SIGCHLD before we start the timer and the pager (if
* configured), to avoid races. sigwait() will receive them.
*/
sigprocmask(SIG_BLOCK, &sigalrm_sigchld, NULL);

/*
* Set a timer to interrupt sigwait() so we can run the query at the
* requested intervals.
*/
interval.it_value.tv_sec = sleep_ms / 1000;
interval.it_value.tv_usec = (sleep_ms % 1000) * 1000;
interval.it_interval = interval.it_value;
if (setitimer(ITIMER_REAL, &interval, NULL) < 0)
{
pg_log_error("could not set timer: %m");
done = true;
}
#endif

/*
* For \watch, we ignore the size of the result and always use the pager
* if PSQL_WATCH_PAGER is set. We also ignore the regular PSQL_PAGER or
* PAGER environment variables, because traditional pagers probably won't
* be very useful for showing a stream of results.
*/
#ifndef WIN32
pagerprog = getenv("PSQL_WATCH_PAGER");
#endif
if (pagerprog && myopt.topt.pager)
{
disable_sigpipe_trap();
pagerpipe = popen(pagerprog, "w");

if (!pagerpipe)
/* silently proceed without pager */
restore_sigpipe_trap();
}

/*
* Choose format for timestamps. We might eventually make this a \pset
* option. In the meantime, using a variable for the format suppresses
Expand All @@ -4911,10 +4973,12 @@ do_watch(PQExpBuffer query_buf, double sleep)
strftime_fmt = "%c";

/*
* Set up rendering options, in particular, disable the pager, because
* nobody wants to be prompted while watching the output of 'watch'.
* Set up rendering options, in particular, disable the pager unless
* PSQL_WATCH_PAGER was successfully launched.
*/
myopt.topt.pager = 0;
if (!pagerpipe)
myopt.topt.pager = 0;


/*
* If there's a title in the user configuration, make sure we have room
Expand All @@ -4929,7 +4993,6 @@ do_watch(PQExpBuffer query_buf, double sleep)
{
time_t timer;
char timebuf[128];
long i;

/*
* Prepare title for output. Note that we intentionally include a
Expand All @@ -4948,7 +5011,7 @@ do_watch(PQExpBuffer query_buf, double sleep)
myopt.title = title;

/* Run the query and print out the results */
res = PSQLexecWatch(query_buf->data, &myopt);
res = PSQLexecWatch(query_buf->data, &myopt, pagerpipe);

/*
* PSQLexecWatch handles the case where we can no longer repeat the
Expand All @@ -4957,6 +5020,11 @@ do_watch(PQExpBuffer query_buf, double sleep)
if (res <= 0)
break;

if (pagerpipe && ferror(pagerpipe))
break;

#ifdef WIN32

/*
* Set up cancellation of 'watch' via SIGINT. We redo this each time
* through the loop since it's conceivable something inside
Expand All @@ -4967,12 +5035,10 @@ do_watch(PQExpBuffer query_buf, double sleep)

/*
* Enable 'watch' cancellations and wait a while before running the
* query again. Break the sleep into short intervals (at most 1s)
* since pg_usleep isn't interruptible on some platforms.
* query again. Break the sleep into short intervals (at most 1s).
*/
sigint_interrupt_enabled = true;
i = sleep_ms;
while (i > 0)
for (long i = sleep_ms; i > 0;)
{
long s = Min(i, 1000L);

Expand All @@ -4982,8 +5048,57 @@ do_watch(PQExpBuffer query_buf, double sleep)
i -= s;
}
sigint_interrupt_enabled = false;
#else
/* sigwait() will handle SIGINT. */
sigprocmask(SIG_BLOCK, &sigint, NULL);
if (cancel_pressed)
done = true;

/* Wait for SIGINT, SIGCHLD or SIGALRM. */
while (!done)
{
int signal_received;

if (sigwait(&sigalrm_sigchld_sigint, &signal_received) < 0)
{
/* Some other signal arrived? */
if (errno == EINTR)
continue;
else
{
pg_log_error("could not wait for signals: %m");
done = true;
break;
}
}
/* On ^C or pager exit, it's time to stop running the query. */
if (signal_received == SIGINT || signal_received == SIGCHLD)
done = true;
/* Otherwise, we must have SIGALRM. Time to run the query again. */
break;
}

/* Unblock SIGINT so that slow queries can be interrupted. */
sigprocmask(SIG_UNBLOCK, &sigint, NULL);
if (done)
break;
#endif
}

if (pagerpipe)
{
pclose(pagerpipe);
restore_sigpipe_trap();
}

#ifndef WIN32
/* Disable the interval timer. */
memset(&interval, 0, sizeof(interval));
setitimer(ITIMER_REAL, &interval, NULL);
/* Unblock SIGINT, SIGCHLD and SIGALRM. */
sigprocmask(SIG_UNBLOCK, &sigalrm_sigchld_sigint, NULL);
#endif

pg_free(title);
return (res >= 0);
}
Expand Down
11 changes: 7 additions & 4 deletions src/bin/psql/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -592,12 +592,13 @@ PSQLexec(const char *query)
* e.g., because of the interrupt, -1 on error.
*/
int
PSQLexecWatch(const char *query, const printQueryOpt *opt)
PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
{
PGresult *res;
double elapsed_msec = 0;
instr_time before;
instr_time after;
FILE *fout;

if (!pset.db)
{
Expand Down Expand Up @@ -638,14 +639,16 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
return 0;
}

fout = printQueryFout ? printQueryFout : pset.queryFout;

switch (PQresultStatus(res))
{
case PGRES_TUPLES_OK:
printQuery(res, opt, pset.queryFout, false, pset.logfile);
printQuery(res, opt, fout, false, pset.logfile);
break;

case PGRES_COMMAND_OK:
fprintf(pset.queryFout, "%s\n%s\n\n", opt->title, PQcmdStatus(res));
fprintf(fout, "%s\n%s\n\n", opt->title, PQcmdStatus(res));
break;

case PGRES_EMPTY_QUERY:
Expand All @@ -668,7 +671,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)

PQclear(res);

fflush(pset.queryFout);
fflush(fout);

/* Possible microtiming output */
if (pset.timing)
Expand Down
2 changes: 1 addition & 1 deletion src/bin/psql/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extern sigjmp_buf sigint_interrupt_jmp;
extern void psql_setup_cancel_handler(void);

extern PGresult *PSQLexec(const char *query);
extern int PSQLexecWatch(const char *query, const printQueryOpt *opt);
extern int PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout);

extern bool SendQuery(const char *query);

Expand Down
6 changes: 5 additions & 1 deletion src/bin/psql/help.c
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ helpVariables(unsigned short int pager)
* Windows builds currently print one more line than non-Windows builds.
* Using the larger number is fine.
*/
output = PageOutput(158, pager ? &(pset.popt.topt) : NULL);
output = PageOutput(160, pager ? &(pset.popt.topt) : NULL);

fprintf(output, _("List of specially treated variables\n\n"));

Expand Down Expand Up @@ -505,6 +505,10 @@ helpVariables(unsigned short int pager)
" alternative location for the command history file\n"));
fprintf(output, _(" PSQL_PAGER, PAGER\n"
" name of external pager program\n"));
#ifndef WIN32
fprintf(output, _(" PSQL_WATCH_PAGER\n"
" name of external pager program used for \\watch\n"));
#endif
fprintf(output, _(" PSQLRC\n"
" alternative location for the user's .psqlrc file\n"));
fprintf(output, _(" SHELL\n"
Expand Down
19 changes: 19 additions & 0 deletions src/bin/psql/startup.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ log_locus_callback(const char **filename, uint64 *lineno)
}
}

#ifndef WIN32
static void
empty_signal_handler(SIGNAL_ARGS)
{
}
#endif

/*
*
* main
Expand Down Expand Up @@ -302,6 +309,18 @@ main(int argc, char *argv[])

psql_setup_cancel_handler();

#ifndef WIN32

/*
* do_watch() needs signal handlers installed (otherwise sigwait() will
* filter them out on some platforms), but doesn't need them to do
* anything, and they shouldn't ever run (unless perhaps a stray SIGALRM
* arrives due to a race when do_watch() cancels an itimer).
*/
pqsignal(SIGCHLD, empty_signal_handler);
pqsignal(SIGALRM, empty_signal_handler);
#endif

PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);

SyncVariables();
Expand Down

0 comments on commit 7c09d27

Please sign in to comment.