diff options
author | Vito Caputo <vcaputo@gnugeneration.com> | 2017-03-07 15:06:16 -0800 |
---|---|---|
committer | Vito Caputo <vcaputo@gnugeneration.com> | 2017-03-21 01:11:53 -0700 |
commit | 12823bbb015269658217b20950568dec58d78692 (patch) | |
tree | 2b3c414aff904bf9c40b4372e58a8c3b48189e52 /src/vmon.c | |
parent | 32e13594d13767fc0687c43ddcd68cae9ac555d0 (diff) |
vmon: introduce vmon utility
vmon exposes the monitoring overlays of vwm through a standalone
commandline utility by creating dedicated window for presenting the
overlay.
At this time a single preexisting PID may be specified, forming the root of
a recursively monitored heirarchy. Alternatively, a command may specified
which vmon will then fork and execute on your behalf, automatically
monitoring the child and its descendants for you, in a style similar to
strace.
Examples:
Monitor a linux kernel build in fullscreen mode, note the --:
`vmon --fullscreen -- make -C /usr/src/linux -j8`
Monitor the entire system:
`vmon --fullscreen --pid 1`
For convenience, omitting --pid and a command to run assumes PID 1:
`vmon --fullscreen` is analogous to `vmon --fullscreen --pid 1`
Monitor a linux kernel build fullscreen at 50Hz:
`vmon --fullscreen --hertz 50 -- make -C /usr/src/linux -j8`
Do the same thing but don't exit when the make completes:
`vmon --linger --fullscreen --hertz 50 -- make -C /usr/src/linux -j8`
Help is provided via `vmon --help`, where you'll find all flags described
with their short and long forms.
Some important TODO items include:
- Support for specifying multiple top-level processes
- Support for mixing --pid and running a command
(useful for watching specific system processes while you're running
something specific)
- Support for scrolling within the window. The overlays in general need
to evolve a bit to better support the vmon use case. In vwm there
wasn't any intention of accomodating the entire space if it exceeded
what was naturally available in the existing window's dimensions.
That makes sense for vwm, but vmon not so much.
You can achieve the same thing more or less by resizing the window to be
larger than the screen (easy to do in vwm using a combination of
Mod1-Right-Click to resize, then Mod1-Left-Click to drag the window,
repeatedly. Then just Mod1-Left-Click to grab the window and "scroll"
it by moving the desired part on-screen, repeatedly. Cumbersome, but
works fine in a pinch. Not all window managers can do this though...
Of course it would be less costly to only render what's visible, like a
scrollable window would achieve. This is probably the top priority
TODO.
- Support for monitoring of memory use, files, per-process IO activity.
libvmon supports substantially more than is being visualized currently.
- Support for changing the font.
Diffstat (limited to 'src/vmon.c')
-rw-r--r-- | src/vmon.c | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/src/vmon.c b/src/vmon.c new file mode 100644 index 0000000..9cb797b --- /dev/null +++ b/src/vmon.c @@ -0,0 +1,443 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2017 Vito Caputo - <vcaputo@gnugeneration.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <X11/Xlib.h> +#include <assert.h> +#include <limits.h> +#include <poll.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "overlays.h" +#include "util.h" +#include "xserver.h" + +/* vmon exposes the monitoring overlays to the shell in an strace-like cli */ + +typedef struct vmon_t { + vwm_xserver_t *xserver; + vwm_overlays_t *overlays; + vwm_overlay_t *overlay; + Window window; + int width, height; + Picture picture; + int pid; + int done; + int linger; +} vmon_t; + + +/* these are fairly arbitrarily chosen to be sane */ +#define WIDTH_DEFAULT 800 +#define HEIGHT_DEFAULT 600 +#define WIDTH_MIN 200 +#define HEIGHT_MIN 28 + +static volatile int got_sigchld; + +/* return if arg == flag or altflag if provided */ +static inline int is_flag(const char *arg, const char *flag, const char *altflag) +{ + assert(arg); + assert(flag); + + if (!strcmp(arg, flag) || (altflag && !strcmp(arg, altflag))) + return 1; + + return 0; +} + + +/* parse integer out of opt, stores parsed opt in *res on success */ +static int parse_flag_int(char * const *flag, char * const *end, char * const *opt, int min, int max, int *res) +{ + long int num; + char *endp; + + assert(flag); + assert(end); + assert(opt); + assert(res); + + if (flag == end || **opt == '\0') { + VWM_ERROR("flag \"%s\" expects an integer argument", *flag); + return 0; + } + + errno = 0; + num = strtol(*opt, &endp, 10); + if (errno) { + VWM_PERROR("flag \"%s\" integer argument parse error, got \"%s\"", *flag, *opt); + return 0; + } + + if (*endp != '\0') { + VWM_ERROR("flag \"%s\" integer argument invalid at \"%s\"", *flag, endp); + return 0; + } + + if (num < min) { + VWM_ERROR("flag \"%s\" integer argument must be >= %i, got \"%s\"", *flag, min, *opt); + return 0; + } + + if (num > max) { + VWM_ERROR("flag \"%s\" integer argument must be <= %i, got \"%s\"", *flag, max, *opt); + return 0; + } + + *res = num; + + return 1; +} + + +/* set vmon->{width,height} to fullscreen dimensions */ +static int set_fullscreen(vmon_t *vmon) +{ + XWindowAttributes wattr; + + assert(vmon); + assert(vmon->xserver); + + if (!XGetWindowAttributes(vmon->xserver->display, XSERVER_XROOT(vmon->xserver), &wattr)) { + VWM_ERROR("unable to get root window attributes"); + return 0; + } + + vmon->width = wattr.width; + vmon->height = wattr.height; + + return 1; +} + + +/* print commandline help */ +static void print_help(void) +{ + puts( + "\n" + "-----------------------------------------------------------------------------\n" + " Flag Description\n" + "-----------------------------------------------------------------------------\n" + " -- Sentinel, subsequent arguments form command to execute\n" + " -f --fullscreen Fullscreen window\n" + " -h --help Show this help\n" + " -l --linger Don't exit when top-level process exits\n" + " -p --pid PID of the top-level process to monitor (1 if unspecified)\n" + " -x --width Window width\n" + " -y --height Window height\n" + " -z --hertz Sample rate in hertz\n" + "-----------------------------------------------------------------------------" + ); +} + + +/* print copyright */ +static void print_copyright(void) +{ + puts( + "\n" + "Copyright (C) 2012-2017 Vito Caputo <vcaputo@gnugeneration.com>\n" + "\n" + "This program comes with ABSOLUTELY NO WARRANTY. This is free software, and\n" + "you are welcome to redistribute it under certain conditions. For details\n" + "please see the LICENSE file included with this program." + ); +} + + +/* collect status of child */ +static void handle_sigchld(int signum) +{ + got_sigchld = 1; +} + + +/* parse and apply argv, implementing an strace-like cli, mutates vmon. */ +static int vmon_handle_argv(vmon_t *vmon, int argc, char * const argv[]) +{ + int i; + char *const*end = &argv[argc - 1], *const*last = argv; + + assert(vmon); + assert(!vmon->pid); + assert(argc > 0); + assert(argv); + + for (argv++; argv <= end; argv++) { + if (is_flag(*argv, "-p", "--pid")) { + if (vmon->pid) { + VWM_ERROR("--pid may only be specified once currently (TODO)", *argv); + return 0; + } + + if (!parse_flag_int(argv, end, argv + 1, 0, INT_MAX, &vmon->pid)) + return 0; + + last = ++argv; + } else if (is_flag(*argv, "-x", "--width")) { + if (!parse_flag_int(argv, end, argv + 1, WIDTH_MIN, INT_MAX, &vmon->width)) + return 0; + + last = ++argv; + } else if (is_flag(*argv, "-y", "--height")) { + if (!parse_flag_int(argv, end, argv + 1, HEIGHT_MIN, INT_MAX, &vmon->height)) + return 0; + + last = ++argv; + } else if (is_flag(*argv, "-f", "--fullscreen")) { + if (!set_fullscreen(vmon)) { + VWM_ERROR("unable to set fullscreen dimensions"); + return 0; + } + + last = argv; + } else if (is_flag(*argv, "-l", "--linger")) { + vmon->linger = 1; + last = argv; + } else if (is_flag(*argv, "--", NULL)) { + /* stop looking for more flags, anything remaining will be used as a command. */ + last = argv; + break; + } else if (is_flag(*argv, "-z", "--hertz")) { + int hertz; + + if (!parse_flag_int(argv, end, argv + 1, 1, 1000, &hertz)) + return 0; + + vwm_overlays_rate_set(vmon->overlays, hertz); + + last = ++argv; + } else if (is_flag(*argv, "-h", "--help")) { + print_help(); + exit(EXIT_SUCCESS); + } else { + /* stop looking for more flags on first unrecognized thing, assume we're in command territory now */ + break; + } + } + + /* if more argv remains, treat as a command to run */ + if (last != end) { + pid_t pid; + + if (vmon->pid) { + VWM_ERROR("combining --pid with a command to run is not yet supported (TODO)\n"); + return 0; + } + + last++; + + if (signal(SIGCHLD, handle_sigchld) == SIG_ERR) { + VWM_PERROR("unable to set SIGCHLD handler"); + return 0; + } + + pid = fork(); + if (pid == -1) { + VWM_PERROR("unable to fork"); + return 0; + } + + if (pid == 0) { + if (prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) { + VWM_PERROR("unable to prctl(PR_SET_PDEATHSIG, SIGKILL)"); + exit(EXIT_FAILURE); + } + + /* TODO: would be better to synchronize with the monitoring loop starting before the execvp() occurs, + * very early program start can be an interesting thing to observe. */ + if (execvp(*last, last) == -1) { + VWM_PERROR("unable to exec \"%s\"", *last); + exit(EXIT_FAILURE); + } + } + + vmon->pid = pid; + } + + /* default to PID 1 when no PID or command was supplied */ + if (!vmon->pid) + vmon->pid = 1; + + return 1; +} + + +/* parse argv, connect to X, create window, attach libvmon to monitored process */ +static vmon_t * vmon_startup(int argc, char * const argv[]) +{ + vmon_t *vmon; + XRenderPictureAttributes pattr = {}; + XWindowAttributes wattr; + + assert(argv); + + print_copyright(); + + vmon = calloc(1, sizeof(vmon_t)); + if (!vmon) { + VWM_PERROR("unable to allocate vmon_t"); + goto _err; + } + + vmon->width = WIDTH_DEFAULT; + vmon->height = HEIGHT_DEFAULT; + + vmon->xserver = vwm_xserver_open(); + if (!vmon->xserver) { + VWM_ERROR("unable to open xserver"); + goto _err_free; + } + + vmon->overlays = vwm_overlays_create(vmon->xserver); + if (!vmon->overlays) { + VWM_ERROR("unable to create overlays instance"); + goto _err_xserver; + } + + if (!vmon_handle_argv(vmon, argc, argv)) { + VWM_ERROR("unable to handle arguments"); + goto _err_overlays; + } + + vmon->overlay = vwm_overlay_create(vmon->overlays, vmon->pid, vmon->width, vmon->height); + if (!vmon->overlay) { + VWM_ERROR("unable to create overlay"); + goto _err_overlays; + } + + vmon->window = XCreateSimpleWindow(vmon->xserver->display, XSERVER_XROOT(vmon->xserver), 0, 0, vmon->width, vmon->height, 1, 0, 0); + XGetWindowAttributes(vmon->xserver->display, vmon->window, &wattr); + vmon->picture = XRenderCreatePicture(vmon->xserver->display, vmon->window, XRenderFindVisualFormat(vmon->xserver->display, wattr.visual), 0, &pattr); + + XMapWindow(vmon->xserver->display, vmon->window); + + XSelectInput(vmon->xserver->display, vmon->window, StructureNotifyMask|ExposureMask); + + return vmon; + +_err_overlays: + vwm_overlays_destroy(vmon->overlays); +_err_xserver: + vwm_xserver_close(vmon->xserver); +_err_free: + free(vmon); +_err: + return NULL; +} + + +/* tear everything down */ +static void vmon_shutdown(vmon_t *vmon) +{ + assert(vmon); + assert(vmon->xserver); + + XDestroyWindow(vmon->xserver->display, vmon->window); + vwm_overlay_destroy(vmon->overlays, vmon->overlay); + vwm_overlays_destroy(vmon->overlays); + vwm_xserver_close(vmon->xserver); + free(vmon); +} + + +/* resize the overlay (in response to configure events) */ +static void vmon_resize(vmon_t *vmon, int width, int height) +{ + assert(vmon); + + vmon->width = width; + vmon->height = height; + vwm_overlay_set_visible_size(vmon->overlays, vmon->overlay, width, height); +} + + +/* handle the next X event, may block */ +static void vmon_process_event(vmon_t *vmon) +{ + XEvent ev; + + assert(vmon); + + XNextEvent(vmon->xserver->display, &ev); + + switch (ev.type) { + case ConfigureNotify: + vmon_resize(vmon, ev.xconfigure.width, ev.xconfigure.height); + vwm_overlay_compose(vmon->overlays, vmon->overlay, NULL); + vwm_overlay_render(vmon->overlays, vmon->overlay, PictOpSrc, vmon->picture, 0, 0, vmon->width, vmon->height); + break; + case Expose: + vwm_overlay_render(vmon->overlays, vmon->overlay, PictOpSrc, vmon->picture, 0, 0, vmon->width, vmon->height); + break; + default: + VWM_TRACE("unhandled event: %x\n", ev.type); + } +} + + +int main(int argc, char * const argv[]) +{ + vmon_t *vmon; + struct pollfd pfd = { .events = POLLIN }; + int ret = EXIT_SUCCESS; + + vmon = vmon_startup(argc, argv); + if (!vmon) { + VWM_ERROR("error starting vmon"); + return EXIT_FAILURE; + } + + pfd.fd = ConnectionNumber(vmon->xserver->display); + + while (!vmon->done) { + int delay; + + if (vwm_overlays_update(vmon->overlays, &delay)) { + vwm_overlay_compose(vmon->overlays, vmon->overlay, NULL); + vwm_overlay_render(vmon->overlays, vmon->overlay, PictOpSrc, vmon->picture, 0, 0, vmon->width, vmon->height); + } + + if (XPending(vmon->xserver->display) || poll(&pfd, 1, delay) > 0) + vmon_process_event(vmon); + + if (got_sigchld) { + int status; + + got_sigchld = 0; + wait(&status); + + if (WIFEXITED(status)) { + ret = WEXITSTATUS(status); + + if (!vmon->linger) + vmon->done = 1; + } + } + } + + vmon_shutdown(vmon); + + return ret; +} |