char *copyright = "Copyright (c) 1984 through 2007, William LeFebvre"; /* * Top users/processes display for Unix * * This program may be freely redistributed, * but this entire comment MUST remain intact. * * Copyright (c) 1984, 1989, William LeFebvre, Rice University * Copyright (c) 1989 - 1994, William LeFebvre, Northwestern University * Copyright (c) 1994, 1995, William LeFebvre, Argonne National Laboratory * Copyright (c) 1996, William LeFebvre, Group sys Consulting * Copyright (c) 2007-2019, Mark Wong */ /* * See the file "HISTORY" for information on version-to-version changes. */ /* * This file contains "main" and other high-level routines. */ /* * The following preprocessor variables, when defined, are used to * distinguish between different Unix implementations: * * FD_SET - macros FD_SET and FD_ZERO are used when defined */ #include "os.h" #include #include #include #include #include /* determine which type of signal functions to use */ #ifdef HAVE_SIGACTION #undef HAVE_SIGHOLD #else #if !defined(HAVE_SIGHOLD) || !defined(HAVE_SIGRELSE) #define BSD_SIGNALS #endif #endif /* includes specific to top */ #include "pg_top.h" #include "remote.h" #include "commands.h" #include "display.h" /* interface to display package */ #include "screen.h" /* interface to screen package */ #include "boolean.h" #include "utils.h" #include "version.h" #ifdef ENABLE_COLOR #include "color.h" #endif #include "port.h" /* Size of the stdio buffer given to stdout */ #define Buffersize 2048 /* The buffer that stdio will use */ char stdoutbuf[Buffersize]; /* build signal masks */ #ifndef sigmask #define sigmask(s) (1 << ((s) - 1)) #endif /* for getopt: */ extern int optind; extern char *optarg; /* imported from screen.c */ extern int overstrike; /* values which need to be accessed by signal handlers */ int max_topn; /* maximum displayable processes */ /* miscellaneous things */ char *myname = "pg_top"; jmp_buf jmp_int; /* internal variables */ static const char *progname = "pg_top"; void process_commands(struct pg_top_context *); static void usage(const char *progname); /* List of all the options available */ static struct option long_options[] = { {"batch", no_argument, NULL, 'b'}, {"show-command", no_argument, NULL, 'c'}, {"color-mode", no_argument, NULL, 'C'}, {"interactive", no_argument, NULL, 'i'}, {"hide-idle", no_argument, NULL, 'I'}, {"non-interactive", no_argument, NULL, 'n'}, {"order-field", required_argument, NULL, 'o'}, {"remote-mode", no_argument, NULL, 'r'}, {"set-delay", required_argument, NULL, 's'}, {"show-tags", no_argument, NULL, 'T'}, {"version", no_argument, NULL, 'V'}, {"set-display", required_argument, NULL, 'x'}, {"show-username", required_argument, NULL, 'z'}, {"help", no_argument, NULL, '?'}, {"dbname", required_argument, NULL, 'd'}, {"host", required_argument, NULL, 'h'}, {"port", required_argument, NULL, 'p'}, {"username", required_argument, NULL, 'U'}, {"password", no_argument, NULL, 'W'}, {NULL, 0, NULL, 0} }; /* pointers to display routines */ void (*d_loadave) (int, double *) = i_loadave; void (*d_minibar) ( int (*) (char *, int)) = i_minibar; void (*d_uptime) (time_t *, time_t *) = i_uptime; void (*d_procstates) (int, int *) = i_procstates; void (*d_cpustates) (int64_t *) = i_cpustates; void (*d_memory) (long *) = i_memory; void (*d_swap) (long *) = i_swap; void (*d_db) (struct db_info *) = i_db; void (*d_io) (struct io_info *) = i_io; void (*d_message) () = i_message; void (*d_process) (int, char *) = i_process; /* * usage - print help message with details about commands */ static void usage(const char *progname) { printf("%s monitors a PostgreSQL database cluster.\n\n", progname); printf("Usage:\n"); printf(" %s [OPTION]... [COUNT]\n", progname); printf("\nGeneral options:\n"); printf(" -b, --batch use batch mode\n"); printf(" -c, --show-command display command name of each process\n"); printf(" -C, --color-mode turn off color mode\n"); printf(" -i, --interactive use interactive mode\n"); printf(" -I, --hide-idle hide idle processes\n"); printf(" -n, --non-interactive use non-interactive mode\n"); printf(" -o, --order-field=FIELD select sort order\n"); printf(" -r, --remote-mode activate remote mode\n"); printf(" -R display replication stats\n"); printf(" -s, --set-delay=SECOND set delay between screen updates\n"); printf(" -T, --show-tags show color tags\n"); printf(" -V, --version output version information, then exit\n"); printf(" -x, --set-display=COUNT set maximum number of displays\n"); printf(" exit once this number is reached\n"); printf(" -X display i/o stats\n"); printf(" -z, --show-username=NAME display only processes owned by given\n"); printf(" username\n"); printf(" -?, --help show this help, then exit\n"); printf("\nConnection options:\n"); printf(" -d, --dbname=DBNAME database to connect to\n"); printf(" -h, --host=HOSTNAME database server host or socket directory\n"); printf(" -p, --port=PORT database server port\n"); printf(" -U, --username=USERNAME user name to connect as\n"); printf(" -W, --password force password prompt, and persistent connection\n"); } RETSIGTYPE onalrm(int i) /* SIGALRM handler */ { /* this is only used in batch mode to break out of the pause() */ /* return; */ } void do_display(struct pg_top_context *pgtctx) { register int i = 0; register int active_procs; caddr_t processes; time_t curr_time; static struct ext_decl exts = {NULL, NULL}; /* get the current stats and processes */ if (pgtctx->mode_remote == 0) { get_system_info(&pgtctx->system_info); processes = get_process_info(&pgtctx->system_info, &pgtctx->ps, pgtctx->order_index, &pgtctx->conninfo, pgtctx->mode); } else { get_system_info_r(&pgtctx->system_info, &pgtctx->conninfo); processes = get_process_info_r(&pgtctx->system_info, &pgtctx->ps, pgtctx->order_index, &pgtctx->conninfo, pgtctx->mode); } /* Get database activity information */ get_database_info(&pgtctx->db_info, &pgtctx->conninfo); /* Get database I/O information */ get_io_info(&pgtctx->io_info); /* display the load averages */ (*d_loadave) (pgtctx->system_info.last_pid, pgtctx->system_info.load_avg); /* this method of getting the time SHOULD be fairly portable */ time(&curr_time); /* if we have a minibar extension, use it, otherwise show uptime */ if (exts.f_minibar != NULL) { (*d_minibar) (exts.f_minibar); } else { (*d_uptime) (&pgtctx->statics.boottime, &curr_time); } /* display the current time */ i_timeofday(&curr_time); /* display process state breakdown */ (*d_procstates) (pgtctx->system_info.p_total, pgtctx->system_info.procstates); /* display the cpu state percentage breakdown */ if (pgtctx->dostates) /* but not the first time */ { (*d_cpustates) (pgtctx->system_info.cpustates); } else { /* we'll do it next time */ if (smart_terminal) { z_cpustates(); } pgtctx->dostates = Yes; } /* display memory stats */ (*d_memory) (pgtctx->system_info.memory); /* display database activity */ (*d_db) (&pgtctx->db_info); /* display database I/O */ (*d_io) (&pgtctx->io_info); /* display swap stats */ (*d_swap) (pgtctx->system_info.swap); /* handle message area */ (*d_message) (); /* update the header area */ pgtctx->d_header(pgtctx->header_text); if (pgtctx->topn > 0) { /* determine number of processes to actually display */ /* * this number will be the smallest of: active processes, number user * requested, number current screen accomodates */ active_procs = pgtctx->system_info.P_ACTIVE; if (active_procs > pgtctx->topn) { active_procs = pgtctx->topn; } if (active_procs > max_topn) { active_procs = max_topn; } /* Now show the top "n" processes or other statistics. */ switch (pgtctx->mode) { #if defined(__linux__) || defined(__FreeBSD__) case MODE_IO_STATS: for (i = 0; i < active_procs; i++) { if (pgtctx->mode_remote == 0) (*d_process) (i, format_next_io(processes)); else (*d_process) (i, format_next_io_r(processes)); } break; #endif /* defined(__linux__) || defined(__FreeBSD__) */ case MODE_REPLICATION: for (i = 0; i < active_procs; i++) { if (pgtctx->mode_remote == 0) (*d_process) (i, format_next_replication(processes)); else (*d_process) (i, format_next_replication_r(processes)); } break; case MODE_PROCESSES: default: for (i = 0; i < active_procs; i++) { if (pgtctx->mode_remote == 0) (*d_process) (i, format_next_process(processes)); else (*d_process) (i, format_next_process_r(processes)); } } } else { i = 0; } /* do end-screen processing */ u_endscreen(i); /* now, flush the output buffer */ if (fflush(stdout) != 0) { new_message(MT_standout, " Write error on stdout"); putchar('\r'); quit(1); /* NOTREACHED */ } /* only do the rest if we have more displays to show */ if (pgtctx->displays) { /* switch out for new display on smart terminals */ if (smart_terminal) { if (overstrike) { reset_display(pgtctx); } else { d_loadave = u_loadave; d_minibar = u_minibar; d_uptime = u_uptime; d_procstates = u_procstates; d_cpustates = u_cpustates; d_memory = u_memory; d_db = u_db; d_io = u_io; d_swap = u_swap; d_message = u_message; pgtctx->d_header = u_header; d_process = u_process; } } if (!pgtctx->interactive) { /* set up alarm */ (void) signal(SIGALRM, onalrm); (void) alarm((unsigned) pgtctx->delay); /* wait for the rest of it .... */ pause(); } else process_commands(pgtctx); } } void process_arguments(struct pg_top_context *pgtctx, int ac, char **av) { int i; int option_index; while ((i = getopt_long(ac, av, "CDITbcinRrVh:s:d:U:o:Wp:Xx:z:", long_options, &option_index)) != EOF) { switch (i) { #ifdef ENABLE_COLOR case 'C': pgtctx->color_on = !pgtctx->color_on; break; #endif case 'D': debug_set(1); break; case 'V': /* show version number */ printf("pg_top %s\n", version_string()); exit(0); break; case 'z': /* display only username's processes */ strncpy(pgtctx->ps.usename, optarg, NAMEDATALEN); break; case 'I': /* show idle processes */ pgtctx->ps.idle = !pgtctx->ps.idle; break; case 'T': /* show color tags */ pgtctx->show_tags = 1; break; case 'i': /* go interactive regardless */ pgtctx->interactive = Yes; break; case 'c': pgtctx->ps.fullcmd = No; break; case 'n': /* batch, or non-interactive */ case 'b': pgtctx->interactive = No; break; case 'x': /* number of displays to show */ if ((i = atoiwi(optarg)) == Invalid || i == 0) { new_message(MT_standout | MT_delayed, " Bad display count (ignored)"); } else { pgtctx->displays = i; } break; case 's': if ((pgtctx->delay = atoi(optarg)) < 0 || (pgtctx->delay == 0 && getuid() != 0)) { new_message(MT_standout | MT_delayed, " Bad seconds delay (ignored)"); pgtctx->delay = Default_DELAY; } break; case 'o': /* select sort order */ pgtctx->order_name = optarg; break; case 'p': /* database port */ if ((i = atoiwi(optarg)) == Invalid || i == 0) { new_message(MT_standout | MT_delayed, " Bad port number (ignored)"); } else { pgtctx->conninfo.values[PG_PORT] = strdup(optarg); } break; case 'W': /* prompt for database password */ pgtctx->conninfo.persistent = 1; pgtctx->conninfo.values[PG_PASSWORD] = simple_prompt("Password: ", 1000, 0); break; case 'U': /* database user name */ pgtctx->conninfo.values[PG_USER] = strdup(optarg); break; case 'd': /* database name */ pgtctx->conninfo.values[PG_DBNAME] = strdup(optarg); break; case 'h': /* socket location */ pgtctx->conninfo.values[PG_HOST] = strdup(optarg); break; case 'R': /* replication mode */ pgtctx->mode = MODE_REPLICATION; break; case 'r': /* remote mode */ pgtctx->mode_remote = 1; break; case 'X': /* i/O mode */ pgtctx->mode = MODE_IO_STATS; break; default: fprintf(stderr, "Try \"%s --help\" for more information.\n", progname); exit(1); } } } void process_commands(struct pg_top_context *pgtctx) { int no_command; fd_set readfds; char ch; do { no_command = No; /* set up arguments for select with timeout */ FD_ZERO(&readfds); FD_SET(0, &readfds); /* for standard input */ pgtctx->timeout.tv_sec = pgtctx->delay; pgtctx->timeout.tv_usec = 0; /* wait for either input or the end of the delay period */ if (select(32, &readfds, (fd_set *) NULL, (fd_set *) NULL, &pgtctx->timeout) > 0) { /* something to read -- clear the message area first */ clear_message(); /* now read it and convert to command strchr */ /* (use "change" as a temporary to hold strchr) */ if (read(0, &ch, 1) != 1) { /* read error: either 0 or -1 */ new_message(MT_standout, " Read error on stdin"); putchar('\r'); quit(1); /* NOTREACHED */ } no_command = execute_command(pgtctx, ch); /* flush out stuff that may have been written */ fflush(stdout); } } while (no_command); } /* * reset_display() - reset all the display routine pointers so that entire * screen will get redrawn. */ void reset_display(struct pg_top_context *pgtctx) { d_loadave = i_loadave; d_minibar = i_minibar; d_uptime = i_uptime; d_procstates = i_procstates; d_cpustates = i_cpustates; d_memory = i_memory; d_swap = i_swap; d_db = i_db; d_io = i_io; d_message = i_message; pgtctx->d_header = i_header; d_process = i_process; } /* * signal handlers */ void set_signal(int sig, RETSIGTYPE(*handler) (int)) { #ifdef HAVE_SIGACTION struct sigaction action; action.sa_handler = handler; action.sa_flags = 0; (void) sigaction(sig, &action, NULL); #else (void) signal(sig, handler); #endif } RETSIGTYPE leave(int i) /* exit under normal conditions -- INT handler */ { end_screen(); exit(0); } RETSIGTYPE tstop(int i) /* SIGTSTP handler */ { #ifdef HAVE_SIGACTION sigset_t set; #endif /* move to the lower left */ end_screen(); fflush(stdout); /* default the signal handler action */ set_signal(SIGTSTP, SIG_DFL); /* unblock the TSTP signal */ #ifdef HAVE_SIGACTION sigemptyset(&set); sigaddset(&set, SIGTSTP); sigprocmask(SIG_UNBLOCK, &set, NULL); #endif #ifdef HAVE_SIGHOLD sigrelse(SIGTSTP); #endif #ifdef BSD_SIGNALS (void) sigsetmask(sigblock(0) & ~(sigmask(SIGTSTP))); #endif /* send ourselves a TSTP to stop the process */ (void) kill(0, SIGTSTP); /* reset the signal handler */ set_signal(SIGTSTP, tstop); /* reinit screen */ reinit_screen(); /* jump to appropriate place */ longjmp(jmp_int, 1); /* NOTREACHED */ } #ifdef SIGWINCH RETSIGTYPE winch(int i) /* SIGWINCH handler */ { /* reascertain the screen dimensions */ get_screensize(); /* tell display to resize */ max_topn = display_resize(); #ifndef HAVE_SIGACTION /* reset the signal handler */ set_signal(SIGWINCH, winch); #endif /* jump to appropriate place */ longjmp(jmp_int, 1); } #endif void quit(int status) /* exit under duress */ { end_screen(); exit(status); /* NOTREACHED */ } int main(int argc, char *argv[]) { register int i; struct pg_top_context pgtctx; #ifdef BSD_SIGNALS int old_sigmask; /* only used for BSD-style signals */ #endif /* BSD_SIGNALS */ char *uname_field = "USERNAME"; char *env_top; char **preset_argv; int preset_argc = 0; char **av; int ac; #ifndef FD_SET /* FD_SET and friends are not present: fake it */ typedef int fd_set; #define FD_ZERO(x) (*(x) = 0) #define FD_SET(f, x) (*(x) = 1< 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { usage(progname); exit(0); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { printf("pg_top %s\n", version_string()); exit(0); } } /* set the buffer for stdout */ #ifdef HAVE_SETVBUF setvbuf(stdout, stdoutbuf, _IOFBF, BUFFERSIZE); #else #ifdef HAVE_SETBUFFER setbuffer(stdout, stdoutbuf, Buffersize); #endif /* HAVE_SETBUFFER */ #endif /* HAVE_SETVBUF */ /* get our name */ if (argc > 0) { if ((myname = strrchr(argv[0], '/')) == 0) { myname = argv[0]; } else { myname++; } } /* get preset options from the environment */ if ((env_top = getenv("PG_TOP")) != NULL) { av = preset_argv = argparse(env_top, &preset_argc); ac = preset_argc; /* * set the dummy argument to an explanatory message, in case getopt * encounters a bad argument */ preset_argv[0] = "while processing environment"; } /* process options */ do { /* if we're done doing the presets, then process the real arguments */ if (preset_argc == 0) { ac = argc; av = argv; /* this should keep getopt happy... */ optind = 1; } process_arguments(&pgtctx, ac, av); /* get count of top processes to display (if any) */ if (optind < ac && *av[optind]) { if ((i = atoiwi(av[optind])) == Invalid) { new_message(MT_standout | MT_delayed, " Process count not a number (ignored)"); } else { pgtctx.displays = i; } } /* tricky: remember old value of preset_argc & set preset_argc = 0 */ i = preset_argc; preset_argc = 0; /* repeat only if we really did the preset arguments */ } while (i != 0); /* set constants for username/uid display correctly */ if (!pgtctx.do_unames) { uname_field = " UID "; } /* * in order to support forward compatability, we have to ensure that the * entire statics structure is set to a known value before we call * machine_init. This way fields that a module does not know about will * retain their default values */ memzero((void *) &pgtctx.statics, sizeof(pgtctx.statics)); pgtctx.statics.boottime = -1; #ifdef ENABLE_COLOR /* If colour has been turned on read in the settings. */ env_top = getenv("PG_TOPCOLOURS"); if (!env_top) { env_top = getenv("PG_TOPCOLORS"); } /* must do something about error messages */ color_env_parse(env_top); #endif /* call the platform-specific init */ if (pgtctx.mode_remote == 0) i = machine_init(&pgtctx.statics); else i = machine_init_r(&pgtctx.statics, &pgtctx.conninfo); if (i == -1) exit(1); /* determine sorting order index, if necessary */ if (pgtctx.order_name != NULL) { if (pgtctx.statics.order_names == NULL) { new_message(MT_standout | MT_delayed, " This platform does not support arbitrary ordering"); } else if ((pgtctx.order_index = string_index(pgtctx.order_name, pgtctx.statics.order_names)) == -1) { char **pp; fprintf(stderr, "%s: '%s' is not a recognized sorting order.\n", myname, pgtctx.order_name); fprintf(stderr, "\tTry one of these:"); pp = pgtctx.statics.order_names; while (*pp != NULL) { fprintf(stderr, " %s", *pp++); } fputc('\n', stderr); exit(1); } } #ifdef WITH_EXT /* initialize extensions */ init_ext(&exts); #endif /* initialize termcap */ init_termcap(pgtctx.interactive); /* 0 corresponds to machine headers definitions */ pgtctx.header_options[0][MODE_PROCESSES] = format_header(uname_field); #if defined(__linux__) || defined(__FreeBSD__) pgtctx.header_options[0][MODE_IO_STATS] = fmt_header_io; #endif /* defined(__linux__) || defined(__FreeBSD__) */ pgtctx.header_options[0][MODE_REPLICATION] = fmt_header_replication; /* 1 corresponds to headers definitions when remotely connecting to pg */ pgtctx.header_options[1][MODE_PROCESSES] = format_header_r(uname_field); pgtctx.header_options[1][MODE_IO_STATS] = fmt_header_io_r; pgtctx.header_options[1][MODE_REPLICATION] = fmt_header_replication_r; /* get the string to use for the process area header */ pgtctx.header_text = pgtctx.header_options[pgtctx.mode_remote][pgtctx.mode]; #ifdef ENABLE_COLOR /* Disable colours on non-smart terminals */ if (!smart_terminal) { pgtctx.color_on = 0; } #endif /* initialize display interface */ if ((max_topn = display_init(&pgtctx.statics)) == -1) { fprintf(stderr, "%s: can't allocate sufficient memory\n", myname); exit(4); } /* handle request for color tags */ if (pgtctx.show_tags) { color_dump(stdout); exit(0); } /* * Set topn based on the current screensize when starting up if it was not * specified on the command line. */ if (pgtctx.topn == 0) { get_screensize(); pgtctx.topn = display_resize(); } /* print warning if user requested more processes than we can display */ if (pgtctx.topn > max_topn) { new_message(MT_standout | MT_delayed, " This terminal can only display %d processes.", max_topn); } /* set header display accordingly */ display_header(pgtctx.topn > 0); /* determine interactive state */ if (pgtctx.interactive == Maybe) { pgtctx.interactive = smart_terminal; } /* if # of displays not specified, fill it in */ if (pgtctx.displays == 0) { pgtctx.displays = smart_terminal ? Infinity : 1; } /* hold interrupt signals while setting up the screen and the handlers */ #ifdef HAVE_SIGPROCMASK sigemptyset(&signalset); sigaddset(&signalset, SIGINT); sigaddset(&signalset, SIGQUIT); sigaddset(&signalset, SIGTSTP); #ifdef SIGWINCH sigaddset(&signalset, SIGWINCH); #endif sigprocmask(SIG_BLOCK, &signalset, NULL); #endif #ifdef HAVE_SIGHOLD sighold(SIGINT); sighold(SIGQUIT); sighold(SIGTSTP); #ifdef SIGWINCH sighold(SIGWINCH); #endif #endif #ifdef BSD_SIGNALS #ifdef SIGWINCH old_sigmask = sigblock(sigmask(SIGINT) | sigmask(SIGQUIT) | sigmask(SIGTSTP) | sigmask(SIGWINCH)); #else old_sigmask = sigblock(sigmask(SIGINT) | sigmask(SIGQUIT) | sigmask(SIGTSTP)); #endif #endif init_screen(); (void) set_signal(SIGINT, leave); (void) set_signal(SIGQUIT, leave); (void) set_signal(SIGTSTP, tstop); #ifdef SIGWINCH (void) set_signal(SIGWINCH, winch); #endif /* setup the jump buffer for stops */ if (setjmp(jmp_int) != 0) { /* control ends up here after an interrupt */ reset_display(&pgtctx); } /* * Ready to release the signals. This will also happen (needlessly) after * a longjmp, but that's okay. */ #ifdef HAVE_SIGPROCMASK sigprocmask(SIG_UNBLOCK, &signalset, NULL); #endif #ifdef HAVE_SIGHOLD sigrelse(SIGINT); sigrelse(SIGQUIT); sigrelse(SIGTSTP); #ifdef SIGWINCH sigrelse(SIGWINCH); #endif #endif #ifdef BSD_SIGNALS (void) sigsetmask(old_sigmask); #endif /* some systems require a warmup */ if (pgtctx.statics.flags.warmup) { if (pgtctx.mode_remote == 0) { get_system_info(&pgtctx.system_info); (void) get_process_info(&pgtctx.system_info, &pgtctx.ps, -1, &pgtctx.conninfo, pgtctx.mode); } else { get_system_info_r(&pgtctx.system_info, &pgtctx.conninfo); (void) get_process_info_r(&pgtctx.system_info, &pgtctx.ps, -1, &pgtctx.conninfo, -1); } /* Get database activity information */ get_database_info(&pgtctx.db_info, &pgtctx.conninfo); /* Get database I/O information */ get_io_info(&pgtctx.io_info); pgtctx.timeout.tv_sec = 1; pgtctx.timeout.tv_usec = 0; select(0, NULL, NULL, NULL, &pgtctx.timeout); /* if we've warmed up, then we can show good states too */ pgtctx.dostates = Yes; } /* * main loop -- repeat while display count is positive or while it * indicates infinity (by being -1) */ while ((pgtctx.displays == -1) || (pgtctx.displays-- > 0)) { do_display(&pgtctx); } quit(0); /* NOTREACHED */ return 0; }