summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Munro2021-07-12 23:13:48 +0000
committerThomas Munro2021-07-12 23:43:21 +0000
commit7c09d2797ecdf779e5dc3289497be85675f3d134 (patch)
tree4b223d030bc675e57ae1afde13b0a9217a17e136
parentf014b1b9bb8eda4e82c1805969dbae2b07b7d54d (diff)
Add PSQL_WATCH_PAGER for psql's \watch command.
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
-rw-r--r--doc/src/sgml/ref/psql-ref.sgml28
-rw-r--r--src/bin/psql/command.c133
-rw-r--r--src/bin/psql/common.c11
-rw-r--r--src/bin/psql/common.h2
-rw-r--r--src/bin/psql/help.c6
-rw-r--r--src/bin/psql/startup.c19
6 files changed, 184 insertions, 15 deletions
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index a8dfc41e40..da53306857 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3003,6 +3003,16 @@ lo_import 152801
</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
<literal>on</literal>, the pager is used when appropriate, i.e., when the
@@ -4673,6 +4683,24 @@ PSQL_EDITOR_LINENUMBER_ARG='--line '
</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>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 543401c6d6..d704c4220c 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -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
@@ -4894,8 +4895,17 @@ 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)
{
@@ -4903,6 +4913,58 @@ do_watch(PQExpBuffer query_buf, double sleep)
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
@@ -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);
}
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 9a00499510..5640786678 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -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)
{
@@ -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:
@@ -668,7 +671,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
PQclear(res);
- fflush(pset.queryFout);
+ fflush(fout);
/* Possible microtiming output */
if (pset.timing)
diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h
index 041b2ac068..d8538a4e06 100644
--- a/src/bin/psql/common.h
+++ b/src/bin/psql/common.h
@@ -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);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3c250d11cf..d3fda67edd 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -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"));
@@ -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"
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 110906a4e9..5f36f0d1c6 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -110,6 +110,13 @@ log_locus_callback(const char **filename, uint64 *lineno)
}
}
+#ifndef WIN32
+static void
+empty_signal_handler(SIGNAL_ARGS)
+{
+}
+#endif
+
/*
*
* main
@@ -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();