mirror of
https://github.com/postgres/postgres.git
synced 2025-12-21 05:21:08 +03:00
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 <pavel.stehule@gmail.com> Author: Thomas Munro <thomas.munro@gmail.com> Discussion: https://postgr.es/m/CAFj8pRBfzUUPz-3gN5oAzto9SDuRSq-TQPfXU_P6h0L7hO%2BEhg%40mail.gmail.com
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user