From df9f5f5b92cef88492b647f362c91f281c3b2e3b Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Wed, 8 Mar 2017 01:13:17 -0800 Subject: overlay: rename overlay.[ch] -> overlays.[ch] In preparation for vwm_overlays_* encapsulation of overlay global state and general cleanup therein. --- src/Makefile.am | 2 +- src/composite.c | 1 + src/overlay.c | 1021 ------------------------------------------------------- src/overlay.h | 37 -- src/overlays.c | 1020 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/overlays.h | 37 ++ src/xwindow.c | 1 + src/xwindow.h | 2 +- 8 files changed, 1061 insertions(+), 1060 deletions(-) delete mode 100644 src/overlay.c delete mode 100644 src/overlay.h create mode 100644 src/overlays.c create mode 100644 src/overlays.h diff --git a/src/Makefile.am b/src/Makefile.am index 7fff59f..6fa77c5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ SUBDIRS = libvmon bin_PROGRAMS = vwm -vwm_SOURCES = clickety.c composite.c context.c desktop.c key.c launch.c logo.c overlay.c screen.c vwm.c window.c xevent.c xserver.c xwindow.c clickety.h composite.h context.h desktop.h key.h launch.h list.h logo.h overlay.h screen.h util.h vwm.h window.h xevent.h xserver.h xwindow.h colors.def launchers.def +vwm_SOURCES = clickety.c composite.c context.c desktop.c key.c launch.c logo.c overlays.c screen.c vwm.c window.c xevent.c xserver.c xwindow.c clickety.h composite.h context.h desktop.h key.h launch.h list.h logo.h overlays.h screen.h util.h vwm.h window.h xevent.h xserver.h xwindow.h colors.def launchers.def vwm_LDADD = @VWM_LIBS@ libvmon/libvmon.a vwm_CPPFLAGS = @VWM_CFLAGS@ diff --git a/src/composite.c b/src/composite.c index 79a001c..bdb2b64 100644 --- a/src/composite.c +++ b/src/composite.c @@ -25,6 +25,7 @@ #include #include +#include "overlays.h" #include "xwindow.h" #include "vwm.h" diff --git a/src/overlay.c b/src/overlay.c deleted file mode 100644 index 34802c2..0000000 --- a/src/overlay.c +++ /dev/null @@ -1,1021 +0,0 @@ -/* - * \/\/\ - * - * Copyright (C) 2012-2016 Vito Caputo - - * - * 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 . - */ - -/* libvmon integration, warning: this gets a little crazy especially in the rendering. */ - -#include -#include -#include -#include -#include -#include - -#include "composite.h" -#include "libvmon/vmon.h" -#include "list.h" -#include "overlay.h" -#include "vwm.h" -#include "xwindow.h" - -/* TODO: move to overlay.h */ -#define OVERLAY_MASK_DEPTH 8 /* XXX: 1 would save memory, but Xorg isn't good at it */ -#define OVERLAY_MASK_FORMAT PictStandardA8 -#define OVERLAY_FIXED_FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1" -#define OVERLAY_ROW_HEIGHT 15 /* this should always be larger than the font height */ -#define OVERLAY_GRAPH_MIN_WIDTH 200 /* always create graphs at least this large */ -#define OVERLAY_GRAPH_MIN_HEIGHT (4 * OVERLAY_ROW_HEIGHT) -#define OVERLAY_ISTHREAD_ARGV "~" /* use this string to mark threads in the argv field */ -#define OVERLAY_NOCOMM_ARGV "#missed it!" /* use this string to substitute the command when missing in argv field */ -#define OVERLAY_MAX_ARGC 512 /* this is a huge amount */ - - -/* libvmon */ -static struct timeval maybe_sample, last_sample, this_sample = {0,0}; -static typeof(((vmon_sys_stat_t *)0)->user) last_user_cpu; -static typeof(((vmon_sys_stat_t *)0)->system) last_system_cpu; -static unsigned long long last_total, this_total, total_delta; -static unsigned long long last_idle, last_iowait, idle_delta, iowait_delta; -static vmon_t vmon; - -static float sampling_intervals[] = { - 1, /* ~1Hz */ - .1, /* ~10Hz */ - .05, /* ~20Hz */ - .025, /* ~40Hz */ - .01666}; /* ~60Hz */ -static int prev_sampling_interval = 1, sampling_interval = 1; - -/* space we need for every process being monitored */ -typedef struct _vwm_perproc_ctxt_t { - typeof(vmon.generation) generation; - typeof(((vmon_proc_stat_t *)0)->utime) last_utime; - typeof(((vmon_proc_stat_t *)0)->stime) last_stime; - typeof(((vmon_proc_stat_t *)0)->utime) utime_delta; - typeof(((vmon_proc_stat_t *)0)->stime) stime_delta; -} vwm_perproc_ctxt_t; - -/* Compositing / Overlays */ -static XFontStruct *overlay_font; -static GC text_gc; -static XRenderPictureAttributes pa_repeat = { .repeat = 1 }; -static XRenderPictureAttributes pa_no_repeat = { .repeat = 0 }; -static Picture overlay_shadow_fill, /* TODO: the repetition here smells like an XMacro waiting to happen */ - overlay_text_fill, - overlay_bg_fill, - overlay_snowflakes_text_fill, - overlay_grapha_fill, - overlay_graphb_fill, - overlay_finish_fill; -static XRenderColor overlay_visible_color = { 0xffff, 0xffff, 0xffff, 0xffff }, - overlay_shadow_color = { 0x0000, 0x0000, 0x0000, 0x8800}, - overlay_bg_color = { 0x0, 0x1000, 0x0, 0x9000}, - overlay_div_color = { 0x2000, 0x3000, 0x2000, 0x9000}, - overlay_snowflakes_visible_color = { 0xd000, 0xd000, 0xd000, 0x8000 }, - overlay_trans_color = {0x00, 0x00, 0x00, 0x00}, - overlay_grapha_color = { 0xff00, 0x0000, 0x0000, 0x3000 }, /* ~red */ - overlay_graphb_color = { 0x0000, 0xffff, 0xffff, 0x3000 }; /* ~cyan */ - - -/* moves what's below a given row up above it if specified, the row becoming discarded */ -static void snowflake_row(vwm_t *vwm, vwm_xwindow_t *xwin, Picture pic, int copy, int row) -{ - VWM_TRACE("pid=%i xwin=%p row=%i copy=%i heirarhcy_end=%i", xwin->monitor->pid, xwin, row, copy, xwin->overlay.heirarchy_end); - - if (copy) { - /* copy row to tmp */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, pic, None, xwin->overlay.tmp_picture, - 0, row * OVERLAY_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, 0, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ - } - - /* shift up */ - XRenderChangePicture(VWM_XDISPLAY(vwm), pic, CPRepeat, &pa_no_repeat); - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, pic, None, pic, - 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, row * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, (1 + xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ - XRenderChangePicture(VWM_XDISPLAY(vwm), pic, CPRepeat, &pa_repeat); - - if (copy) { - /* copy tmp to top of snowflakes */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.tmp_picture, None, pic, - 0, 0, /* src */ - 0, 0, /* mask */ - 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ - } else { - /* clear the snowflake row */ - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, pic, &overlay_trans_color, - 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ - } -} - -/* XXX TODO libvmon automagic children following races with explicit X client pid monitoring with different outcomes, it should be irrelevant which wins, - * currently the only visible difference is the snowflakes gap (heirarchy_end) varies, which is why I haven't bothered to fix it, I barely even notice. - */ - -/* shifts what's below a given row down a row, and clears the row, preparing it for populating */ -static void allocate_row(vwm_t *vwm, vwm_xwindow_t *xwin, Picture pic, int row) -{ - VWM_TRACE("pid=%i xwin=%p row=%i", xwin->monitor->pid, xwin, row); - - /* shift everything below the row down */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, pic, None, pic, - 0, row * OVERLAY_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, xwin->overlay.height - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ - /* fill the space created with transparent pixels */ - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, pic, &overlay_trans_color, - 0, row * OVERLAY_ROW_HEIGHT, /* dest */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ -} - - -/* shadow a row from the text layer in the shadow layer */ -static void shadow_row(vwm_t *vwm, vwm_xwindow_t *xwin, int row) -{ - /* the current technique for creating the shadow is to simply render the text at +1/-1 pixel offsets on both axis in translucent black */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - -1, row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); - - XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - 0, -1 + row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); - - XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - 1, row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); - - XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, - 0, 0, - 0, 1 + row * OVERLAY_ROW_HEIGHT, - 0, row * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, OVERLAY_ROW_HEIGHT); -} - - -/* simple helper to map the vmon per-proc argv array into an XTextItem array, deals with threads vs. processes and the possibility of the comm field not getting read in before the process exited... */ -static void argv2xtext(vmon_proc_t *proc, XTextItem *items, int max_items, int *nr_items) -{ - int i; - int nr = 0; - - if (proc->is_thread) { /* stick the thread marker at the start of threads */ - items[0].nchars = sizeof(OVERLAY_ISTHREAD_ARGV) - 1; - items[0].chars = OVERLAY_ISTHREAD_ARGV; - items[0].delta = 4; - items[0].font = None; - nr++; - } - - if (((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len) { - items[nr].nchars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1; - items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array; - } else { - /* sometimes a process is so ephemeral we don't manage to sample its comm, XXX TODO: we always have a pid, stringify it? */ - items[nr].nchars = sizeof(OVERLAY_NOCOMM_ARGV) - 1; - items[nr].chars = OVERLAY_NOCOMM_ARGV; - } - items[nr].delta = 4; - items[nr].font = None; - nr++; - - for (i = 1; nr < max_items && i < ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argc; nr++, i++) { - items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]; - items[nr].nchars = strlen(((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]); /* TODO: libvmon should inform us of the length */ - items[nr].delta = 4; - items[nr].font = None; - } - - (*nr_items) = nr; -} - - -/* helper for counting number of existing descendants subtrees */ -static int count_rows(vmon_proc_t *proc) { - int count = 1; /* XXX maybe suppress proc->is_new? */ - vmon_proc_t *child; - - if (!proc->is_thread) { - list_for_each_entry(child, &proc->threads, threads) { - count += count_rows(child); - } - } - - list_for_each_entry(child, &proc->children, siblings) { - count += count_rows(child); - } - - return count; -} - - -/* helper for detecting if any children/threads in the process heirarchy rooted @ proc are new/stale this sample */ -static int proc_heirarchy_changed(vmon_proc_t *proc) { - vmon_proc_t *child; - - if (proc->children_changed || proc->threads_changed) return 1; - - if (!proc->is_thread) { - list_for_each_entry(child, &proc->threads, threads) { - if (proc_heirarchy_changed(child)) return 1; - } - } - - list_for_each_entry(child, &proc->children, siblings) { - if (proc_heirarchy_changed(child)) return 1; - } - - return 0; -} - - -/* helper for drawing the vertical bars in the graph layers */ -static void draw_bars(vwm_t *vwm, vwm_xwindow_t *xwin, int row, double a_fraction, double a_total, double b_fraction, double b_total) -{ - int a_height, b_height; - - /* compute the bar heights for this sample */ - a_height = (a_fraction / a_total * (double)(OVERLAY_ROW_HEIGHT - 1)); /* give up 1 pixel for the div */ - b_height = (b_fraction / b_total * (double)(OVERLAY_ROW_HEIGHT - 1)); - - /* round up to 1 pixel when the scaled result is a fraction less than 1, - * I want to at least see 1 pixel blips for the slightest cpu utilization */ - if (a_fraction && !a_height) a_height = 1; - if (b_fraction && !b_height) b_height = 1; - - /* draw the two bars for this sample at the current phase in the graphs, note the first is ceiling-based, second floor-based */ - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.grapha_picture, &overlay_visible_color, - xwin->overlay.phase, row * OVERLAY_ROW_HEIGHT, /* dst x, y */ - 1, a_height); /* dst w, h */ - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.graphb_picture, &overlay_visible_color, - xwin->overlay.phase, row * OVERLAY_ROW_HEIGHT + (OVERLAY_ROW_HEIGHT - b_height) - 1, /* dst x, y */ - 1, b_height); /* dst w, h */ -} - - -/* draws proc in a row of the process heirarchy */ -static void draw_heirarchy_row(vwm_t *vwm, vwm_xwindow_t *xwin, vmon_proc_t *proc, int depth, int row, int heirarchy_changed) -{ - vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; - vmon_proc_t *child; - char str[256]; - int str_len, str_width; - XTextItem items[OVERLAY_MAX_ARGC]; - int nr_items; - -/* process heirarchy text and accompanying per-process details like wchan/pid/state... */ - - /* skip if obviously unnecessary (this can be further improved, but this makes a big difference as-is) */ - if (!xwin->overlay.redraw_needed && - !heirarchy_changed && - !BITTEST(proc_stat->changed, VMON_PROC_STAT_WCHAN) && - !BITTEST(proc_stat->changed, VMON_PROC_STAT_PID) && - !BITTEST(proc_stat->changed, VMON_PROC_STAT_STATE) && - !BITTEST(proc_stat->changed, VMON_PROC_STAT_ARGV)) return; - - /* TODO: make the columns interactively configurable @ runtime */ - if (!proc->is_new) { - /* XXX for now always clear the row, this should be capable of being optimized in the future (if the datums driving the text haven't changed...) */ - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, - 0, row * OVERLAY_ROW_HEIGHT, /* dst x, y */ - xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ - } - - /* put the process' wchan, state, and PID columns @ the far right */ - if (proc->is_thread || list_empty(&proc->threads)) { /* only threads or non-threaded processes include the wchan and state */ - snprintf(str, sizeof(str), " %.*s %5i %c %n", - proc_stat->wchan.len, - proc_stat->wchan.len == 1 && proc_stat->wchan.array[0] == '0' ? "-" : proc_stat->wchan.array, - proc->pid, - proc_stat->state, - &str_len); - } else { /* we're a process having threads, suppress the wchan and state, as they will be displayed for the thread of same pid */ - snprintf(str, sizeof(str), " %5i %n", proc->pid, &str_len); - } - str_width = XTextWidth(overlay_font, str, str_len); - - /* the process' comm label indented according to depth, followed with their respective argv's */ - argv2xtext(proc, items, NELEMS(items), &nr_items); - XDrawText(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, - depth * (OVERLAY_ROW_HEIGHT / 2), (row + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ - items, nr_items); - - /* ensure the area for the rest of the stuff is cleared, we don't put much text into thread rows so skip it for those. */ - if (!proc->is_thread) { - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, - xwin->attrs.width - str_width, row * OVERLAY_ROW_HEIGHT, /* dst x,y */ - xwin->overlay.width - (xwin->attrs.width - str_width), OVERLAY_ROW_HEIGHT); /* dst w,h */ - } - - XDrawString(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, - xwin->attrs.width - str_width, (row + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ - str, str_len); - - /* only if this process isn't the root process @ the window shall we consider all relational drawing conditions */ - if (proc != xwin->monitor) { - vmon_proc_t *ancestor, *sibling, *last_sibling = NULL; - struct list_head *rem; - int needs_tee = 0; - int bar_x = 0, bar_y = (row + 1) * OVERLAY_ROW_HEIGHT; - int sub; - - /* XXX: everything done in this code block only dirties _this_ process' row in the rendered overlay output */ - - /* walk up the ancestors until reaching xwin->monitor, any ancestors we encounter which have more siblings we draw a vertical bar for */ - /* this draws the |'s in something like: | | | | comm */ - for (sub = 1, ancestor = proc->parent; ancestor && ancestor != xwin->monitor; ancestor = ancestor->parent) { - sub++; - bar_x = (depth - sub) * (OVERLAY_ROW_HEIGHT / 2) + 4; - - /* determine if the ancestor has remaining siblings which are not stale, if so, draw a connecting bar at its depth */ - for (rem = ancestor->siblings.next; rem != &ancestor->parent->children; rem = rem->next) { - if (!(list_entry(rem, vmon_proc_t, siblings)->is_stale)) { - XDrawLine(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ - bar_x, bar_y); /* dst x2, y2 (vertical line) */ - break; /* stop looking for more siblings at this ancestor when we find one that isn't stale */ - } - } - } - - /* determine if _any_ of our siblings have children requiring us to draw a tee immediately before our comm string. - * The only sibling which doesn't cause this to happen is the last one in the children list, if it has children it has no impact on its remaining - * siblings, as there are none. - * - * This draws the + in something like: | | | | +comm - */ - - /* find the last sibling (this has to be done due to the potential for stale siblings at the tail, and we'd rather not repeatedly check for it) */ - list_for_each_entry(sibling, &proc->parent->children, siblings) { - if (!sibling->is_stale) last_sibling = sibling; - } - - /* now look for siblings with non-stale children to determine if a tee is needed, ignoring the last sibling */ - list_for_each_entry(sibling, &proc->parent->children, siblings) { - /* skip stale siblings, they aren't interesting as they're invisible, and the last sibling has no bearing on wether we tee or not. */ - if (sibling->is_stale || sibling == last_sibling) continue; - - /* if any of the other siblings have children which are not stale, put a tee in front of our name, but ignore stale children */ - list_for_each_entry(child, &sibling->children, siblings) { - if (!child->is_stale) { - needs_tee = 1; - break; - } - } - - /* if we still don't think we need a tee, check if there are threads */ - if (!needs_tee) { - list_for_each_entry(child, &sibling->threads, threads) { - if (!child->is_stale) { - needs_tee = 1; - break; - } - } - } - - /* found a tee is necessary, all that's left is to determine if the tee is a corner and draw it accordingly, stopping the search. */ - if (needs_tee) { - bar_x = (depth - 1) * (OVERLAY_ROW_HEIGHT / 2) + 4; - - /* if we're the last sibling, corner the tee by shortening the vbar */ - if (proc == last_sibling) { - XDrawLine(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ - bar_x, bar_y - 4); /* dst x2, y2 (vertical bar) */ - } else { - XDrawLine(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ - bar_x, bar_y); /* dst x2, y2 (vertical bar) */ - } - - XDrawLine(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, - bar_x, bar_y - 4, /* dst x1, y1 */ - bar_x + 2, bar_y - 4); /* dst x2, y2 (horizontal bar) */ - - /* terminate the outer sibling loop upon drawing the tee... */ - break; - } - } - } - - shadow_row(vwm, xwin, row); -} - - -/* recursive draw function for "rest" of overlay: the per-process rows (heirarchy, argv, state, wchan, pid...) */ -static void draw_overlay_rest(vwm_t *vwm, vwm_xwindow_t *xwin, vmon_proc_t *proc, int *depth, int *row, int heirarchy_changed) -{ - vmon_proc_t *child; - vwm_perproc_ctxt_t *proc_ctxt = proc->foo; - vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; - - /* graph variables */ - double utime_delta, stime_delta; - - /* text variables */ - XTextItem items[OVERLAY_MAX_ARGC]; - int nr_items; - - /* Some parts of this we must do on every sample to maintain coherence in the graphs, since they're incrementally kept - * in sync with the process heirarchy, allocating and shifting the rows as processes are created and destroyed. Everything - * else we should be able to skip doing unless overlay.redraw_needed or their contents changed. - */ - - if (proc->is_stale) { - /* what to do when a process (subtree) has gone away */ - static int in_stale = 0; - int in_stale_entrypoint = 0; - - /* I snowflake the stale processes from the leaves up for a more intuitive snowflake order... - * (I expect the command at the root of the subtree to appear at the top of the snowflakes...) */ - /* This does require that I do a separate forward recursion to determine the number of rows - * so I can correctly snowflake in reverse */ - if (!in_stale) { - VWM_TRACE("entered stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); - in_stale_entrypoint = in_stale = 1; - (*row) += count_rows(proc) - 1; - } - - (*depth)++; - list_for_each_entry_prev(child, &proc->children, siblings) { - draw_overlay_rest(vwm, xwin, child, depth, row, heirarchy_changed); - (*row)--; - } - - if (!proc->is_thread) { - list_for_each_entry_prev(child, &proc->threads, threads) { - draw_overlay_rest(vwm, xwin, child, depth, row, heirarchy_changed); - (*row)--; - } - } - (*depth)--; - - VWM_TRACE("%i (%.*s) is stale @ depth %i row %i is_thread=%i", proc->pid, - ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1, - ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array, - (*depth), (*row), proc->is_thread); - - /* stamp the graphs with the finish line */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, overlay_finish_fill, None, xwin->overlay.grapha_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - 1, OVERLAY_ROW_HEIGHT - 1); - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, overlay_finish_fill, None, xwin->overlay.graphb_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ - 1, OVERLAY_ROW_HEIGHT - 1); - - /* extract the row from the various layers */ - snowflake_row(vwm, xwin, xwin->overlay.grapha_picture, 1, (*row)); - snowflake_row(vwm, xwin, xwin->overlay.graphb_picture, 1, (*row)); - snowflake_row(vwm, xwin, xwin->overlay.text_picture, 0, (*row)); - snowflake_row(vwm, xwin, xwin->overlay.shadow_picture, 0, (*row)); - xwin->overlay.snowflakes_cnt++; - - /* stamp the name (and whatever else we include) into overlay.text_picture */ - argv2xtext(proc, items, NELEMS(items), &nr_items); - XDrawText(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, - 5, (xwin->overlay.heirarchy_end + 1) * OVERLAY_ROW_HEIGHT - 3,/* dst x, y */ - items, nr_items); - shadow_row(vwm, xwin, xwin->overlay.heirarchy_end); - - xwin->overlay.heirarchy_end--; - - if (in_stale_entrypoint) { - VWM_TRACE("exited stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); - in_stale = 0; - } - - return; - } else if (proc->is_new) { - /* what to do when a process has been introduced */ - VWM_TRACE("%i is new", proc->pid); - - allocate_row(vwm, xwin, xwin->overlay.grapha_picture, (*row)); - allocate_row(vwm, xwin, xwin->overlay.graphb_picture, (*row)); - allocate_row(vwm, xwin, xwin->overlay.text_picture, (*row)); - allocate_row(vwm, xwin, xwin->overlay.shadow_picture, (*row)); - - xwin->overlay.heirarchy_end++; - } - -/* CPU utilization graphs */ - /* use the generation number to avoid recomputing this stuff for callbacks recurring on the same process in the same sample */ - if (proc_ctxt->generation != vmon.generation) { - proc_ctxt->stime_delta = proc_stat->stime - proc_ctxt->last_stime; - proc_ctxt->utime_delta = proc_stat->utime - proc_ctxt->last_utime; - proc_ctxt->last_utime = proc_stat->utime; - proc_ctxt->last_stime = proc_stat->stime; - - proc_ctxt->generation = vmon.generation; - } - - if (proc->is_new) { - /* we need a minimum of two samples before we can compute a delta to plot, - * so we suppress that and instead mark the start of monitoring with an impossible 100% of both graph contexts, a starting line. */ - stime_delta = utime_delta = total_delta; - } else { - stime_delta = proc_ctxt->stime_delta; - utime_delta = proc_ctxt->utime_delta; - } - - draw_bars(vwm, xwin, *row, stime_delta, total_delta, utime_delta, total_delta); - - draw_heirarchy_row(vwm, xwin, proc, *depth, *row, heirarchy_changed); - - (*row)++; - - /* recur any threads first, then any children processes */ - (*depth)++; - if (!proc->is_thread) { /* XXX: the threads member serves as the list head only when not a thread */ - list_for_each_entry(child, &proc->threads, threads) { - draw_overlay_rest(vwm, xwin, child, depth, row, heirarchy_changed); - } - } - - list_for_each_entry(child, &proc->children, siblings) { - draw_overlay_rest(vwm, xwin, child, depth, row, heirarchy_changed); - } - (*depth)--; -} - - - -/* recursive draw function entrypoint, draws the IOWait/Idle/HZ row, then enters draw_overlay_rest() */ -static void draw_overlay(vwm_t *vwm, vwm_xwindow_t *xwin, vmon_proc_t *proc, int *depth, int *row) -{ - vmon_proc_t *child; - vwm_perproc_ctxt_t *proc_ctxt = proc->foo; - vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; - - /* text variables */ - char str[256]; - int str_len, str_width; - - int heirarchy_changed = 0; - -/* CPU utilization graphs */ - /* IOWait and Idle % @ row 0 */ - draw_bars(vwm, xwin, *row, iowait_delta, total_delta, idle_delta, total_delta); - - /* only draw the \/\/\ and HZ if necessary */ - if (xwin->overlay.redraw_needed || prev_sampling_interval != sampling_interval) { - snprintf(str, sizeof(str), "\\/\\/\\ %2iHz %n", (int)(sampling_interval < 0 ? 0 : 1 / sampling_intervals[sampling_interval]), &str_len); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, - 0, 0, /* dst x, y */ - xwin->attrs.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ - str_width = XTextWidth(overlay_font, str, str_len); - XDrawString(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, - xwin->attrs.width - str_width, OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ - str, str_len); - shadow_row(vwm, xwin, 0); - } - (*row)++; - - if (!xwin->overlay.redraw_needed) heirarchy_changed = proc_heirarchy_changed(proc); - - - draw_overlay_rest(vwm, xwin, proc, depth, row, heirarchy_changed); - - xwin->overlay.redraw_needed = 0; - - return; -} - - -/* consolidated version of overlay text and graph rendering, makes snowflakes integration cleaner, this always gets called regadless of the overlays mode */ -static void maintain_overlay(vwm_t *vwm, vwm_xwindow_t *xwin) -{ - int row = 0, depth = 0; - - if (!xwin->monitor || !xwin->monitor->stores[VMON_STORE_PROC_STAT]) return; - - /* TODO: - * I side effect of responding to window resizes in this function is there's a latency proportional to the current sample_interval. - * Something to fix is to resize the overlays when the window resizes. - * However, simply resizing the overlays is insufficient. Their contents need to be redrawn in the new dimensions, this is where it - * gets annoying. The current maintain/draw_overlay makes assumptions about being run from the periodic vmon per-process callback. - * There needs to be a redraw mode added where draw_overlay is just reconstructing the current state, which requires that we suppress - * the phase advance and in maintain_overlay() and just enter draw_overlay() to redraw everything for the same generation. - * So this probably requires some tweaking of draw_overlay() as well as maintain_overlay(). I want to be able tocall mainta_overlays() - * from anywhere, and have it detect if it's being called on the same generation or if the generation has advanced. - * For now, the monitors will just be a little latent in window resizes which is pretty harmless artifact. - */ - - if (xwin->attrs.width != xwin->overlay.width || xwin->attrs.height != xwin->overlay.height) xwin->overlay.redraw_needed = 1; - - /* if the window is larger than the overlays currently are, enlarge them */ - if (xwin->attrs.width > xwin->overlay.width || xwin->attrs.height > xwin->overlay.height) { - vwm_overlay_t existing; - Pixmap pixmap; - - existing = xwin->overlay; - - xwin->overlay.width = MAX(xwin->overlay.width, MAX(xwin->attrs.width, OVERLAY_GRAPH_MIN_WIDTH)); - xwin->overlay.height = MAX(xwin->overlay.height, MAX(xwin->attrs.height, OVERLAY_GRAPH_MIN_HEIGHT)); - - pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.grapha_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); - XFreePixmap(VWM_XDISPLAY(vwm), pixmap); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.graphb_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); - XFreePixmap(VWM_XDISPLAY(vwm), pixmap); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, OVERLAY_ROW_HEIGHT, OVERLAY_MASK_DEPTH); - xwin->overlay.tmp_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), 0, NULL); - XFreePixmap(VWM_XDISPLAY(vwm), pixmap); - - /* keep the text_pixmap reference around for XDrawText usage */ - xwin->overlay.text_pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.text_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), 0, NULL); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); - xwin->overlay.shadow_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), 0, NULL); - XFreePixmap(VWM_XDISPLAY(vwm), pixmap); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.shadow_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); - - pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, 32); - xwin->overlay.picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), 0, NULL); - XFreePixmap(VWM_XDISPLAY(vwm), pixmap); - - if (existing.width) { - /* XXX: note the graph pictures are copied from their current phase in the x dimension */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.grapha_picture, None, xwin->overlay.grapha_picture, - existing.phase, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.graphb_picture, None, xwin->overlay.graphb_picture, - existing.phase, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.text_picture, None, xwin->overlay.text_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.shadow_picture, None, xwin->overlay.shadow_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.picture, None, xwin->overlay.picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - existing.width, existing.height); - xwin->overlay.phase = 0; /* having unrolled the existing graph[ab] pictures into the larger ones, phase is reset to 0 */ - XRenderFreePicture(VWM_XDISPLAY(vwm), existing.grapha_picture); - XRenderFreePicture(VWM_XDISPLAY(vwm), existing.graphb_picture); - XRenderFreePicture(VWM_XDISPLAY(vwm), existing.tmp_picture); - XRenderFreePicture(VWM_XDISPLAY(vwm), existing.text_picture); - XFreePixmap(VWM_XDISPLAY(vwm), existing.text_pixmap); - XRenderFreePicture(VWM_XDISPLAY(vwm), existing.shadow_picture); - XRenderFreePicture(VWM_XDISPLAY(vwm), existing.picture); - } - } - - xwin->overlay.phase += (xwin->overlay.width - 1); /* simply change this to .phase++ to scroll the other direction */ - xwin->overlay.phase %= xwin->overlay.width; - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); - - /* recursively draw the monitored processes to the overlay */ - draw_overlay(vwm, xwin, xwin->monitor, &depth, &row); -} - - -/* this callback gets invoked at sample time for every process we've explicitly monitored (not autofollowed children/threads) - * It's where we update the cumulative data for all windows, including the graph masks, regardless of their visibility - * It's also where we compose the graphs and text for visible windows into a picture ready for compositing with the window contents */ -static void proc_sample_callback(vmon_t *vmon, void *sys_cb_arg, vmon_proc_t *proc, void *proc_cb_arg) -{ - vwm_t *vwm = sys_cb_arg; - vwm_xwindow_t *xwin = proc_cb_arg; - //VWM_TRACE("proc=%p xwin=%p", proc, xwin); - /* render the various always-updated overlays, this is the component we do regardless of the overlays mode and window visibility, - * essentially the incrementally rendered/historic components */ - maintain_overlay(vwm, xwin); - - /* if we've updated overlays for a mapped window, kick the compositor to do the costly parts of overlay drawing and compositing. */ - if (vwm_xwin_is_mapped(vwm, xwin)) vwm_composite_repaint_needed(vwm); -} - - -/* this callback gets invoked at sample time once "per sys" */ -static void sample_callback(vmon_t *_vmon, void *sys_cb_arg) -{ - vmon_sys_stat_t *sys_stat = vmon.stores[VMON_STORE_SYS_STAT]; - this_total = sys_stat->user + sys_stat->nice + sys_stat->system + - sys_stat->idle + sys_stat->iowait + sys_stat->irq + - sys_stat->softirq + sys_stat->steal + sys_stat->guest; - - total_delta = this_total - last_total; - idle_delta = sys_stat->idle - last_idle; - iowait_delta = sys_stat->iowait - last_iowait; -} - - -/* these callbacks are invoked by the vmon library when process instances become monitored/unmonitored */ -static void vmon_ctor_cb(vmon_t *vmon, vmon_proc_t *proc) -{ - VWM_TRACE("proc->pid=%i", proc->pid); - proc->foo = calloc(1, sizeof(vwm_perproc_ctxt_t)); -} - - -static void vmon_dtor_cb(vmon_t *vmon, vmon_proc_t *proc) -{ - VWM_TRACE("proc->pid=%i", proc->pid); - if (proc->foo) { - free(proc->foo); - proc->foo = NULL; - } -} - - -/* return the composed height of the overlay */ -int vwm_overlay_xwin_composed_height(vwm_t *vwm, vwm_xwindow_t *xwin) -{ - int snowflakes = xwin->overlay.snowflakes_cnt ? 1 + xwin->overlay.snowflakes_cnt : 0; /* don't include the separator row if there are no snowflakes */ - - return MIN((xwin->overlay.heirarchy_end + snowflakes) * OVERLAY_ROW_HEIGHT, xwin->attrs.height); -} - -/* reset snowflakes on the specified window */ -void vwm_overlay_xwin_reset_snowflakes(vwm_t *vwm, vwm_xwindow_t *xwin) { - if (xwin->overlay.snowflakes_cnt) { - xwin->overlay.snowflakes_cnt = 0; - vwm_composite_damage_win(vwm, xwin); - } -} - -static void init_overlay(vwm_t *vwm) { - static int initialized; - Window bitmask; - - if (initialized) return; - initialized = 1; - - /* initialize libvmon */ - vmon_init(&vmon, VMON_FLAG_2PASS, VMON_WANT_SYS_STAT, (VMON_WANT_PROC_STAT | VMON_WANT_PROC_FOLLOW_CHILDREN | VMON_WANT_PROC_FOLLOW_THREADS)); - vmon.proc_ctor_cb = vmon_ctor_cb; - vmon.proc_dtor_cb = vmon_dtor_cb; - vmon.sample_cb = sample_callback; - vmon.sample_cb_arg = vwm; - gettimeofday(&this_sample, NULL); - - /* get all the text and graphics stuff setup for overlays */ - overlay_font = XLoadQueryFont(VWM_XDISPLAY(vwm), OVERLAY_FIXED_FONT); - - /* create a GC for rendering the text using Xlib into the text overlay stencils */ - bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, OVERLAY_MASK_DEPTH); - text_gc = XCreateGC(VWM_XDISPLAY(vwm), bitmask, 0, NULL); - XSetForeground(VWM_XDISPLAY(vwm), text_gc, WhitePixel(VWM_XDISPLAY(vwm), VWM_XSCREENNUM(vwm))); - XFreePixmap(VWM_XDISPLAY(vwm), bitmask); - - /* create some repeating source fill pictures for drawing through the text and graph stencils */ - bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); - overlay_text_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_text_fill, &overlay_visible_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); - overlay_shadow_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_shadow_fill, &overlay_shadow_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, OVERLAY_ROW_HEIGHT, 32); - overlay_bg_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_bg_fill, &overlay_bg_color, 0, 0, 1, OVERLAY_ROW_HEIGHT); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_bg_fill, &overlay_div_color, 0, OVERLAY_ROW_HEIGHT - 1, 1, 1); - - bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); - overlay_snowflakes_text_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_snowflakes_text_fill, &overlay_snowflakes_visible_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); - overlay_grapha_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_grapha_fill, &overlay_grapha_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); - overlay_graphb_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_graphb_fill, &overlay_graphb_color, 0, 0, 1, 1); - - bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 2, 32); - overlay_finish_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_finish_fill, &overlay_visible_color, 0, 0, 1, 1); - XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_finish_fill, &overlay_trans_color, 0, 1, 1, 1); -} - - -/* install a monitor on the window if it doesn't already have one and has _NET_WM_PID set */ -void vwm_overlay_xwin_create(vwm_t *vwm, vwm_xwindow_t *xwin) -{ - Atom type; - int fmt; - unsigned long nitems; - unsigned long nbytes; - long *foo = NULL; - int pid = -1; - - init_overlay(vwm); - - if (xwin->monitor) return; - - if (XGetWindowProperty(VWM_XDISPLAY(vwm), xwin->id, vwm->wm_pid_atom, 0, 1, False, XA_CARDINAL, - &type, &fmt, &nitems, &nbytes, (unsigned char **)&foo) != Success || !foo) return; - - pid = *foo; - XFree(foo); - - /* add the client process to the monitoring heirarchy */ - /* XXX note libvmon here maintains a unique callback for each unique callback+xwin pair, so multi-window processes work */ - xwin->monitor = vmon_proc_monitor(&vmon, NULL, pid, VMON_WANT_PROC_INHERIT, (void (*)(vmon_t *, void *, vmon_proc_t *, void *))proc_sample_callback, xwin); - /* FIXME: count_rows() isn't returning the right count sometimes (off by ~1), it seems to be related to racing with the automatic child monitoring */ - /* the result is an extra row sometimes appearing below the process heirarchy */ - xwin->overlay.heirarchy_end = 1 + count_rows(xwin->monitor); - xwin->overlay.snowflakes_cnt = 0; -} - - -/* remove monitoring on the window if installed */ -void vwm_overlay_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin) -{ - if (xwin->monitor) vmon_proc_unmonitor(&vmon, xwin->monitor, (void (*)(vmon_t *, void *, vmon_proc_t *, void *))proc_sample_callback, xwin); -} - - -/* this composes the maintained overlay into the window's overlay picture, this gets called from paint_all() on every repaint of xwin */ -/* we noop the call if the gen_last_composed and monitor->proc.generation numbers match, indicating there's nothing new to compose. */ -void vwm_overlay_xwin_compose(vwm_t *vwm, vwm_xwindow_t *xwin) -{ - XserverRegion region; - XRectangle damage; - int height; - - if (!xwin->overlay.width) return; /* prevent winning race with maintain_overlay() and using an unready overlay... */ - - if (xwin->overlay.gen_last_composed == xwin->monitor->generation) return; /* noop if no sampling occurred since last compose */ - xwin->overlay.gen_last_composed = xwin->monitor->generation; /* remember this generation */ - - //VWM_TRACE("composing %p", xwin); - - height = vwm_overlay_xwin_composed_height(vwm, xwin); - - /* fill the overlay picture with the background */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, overlay_bg_fill, None, xwin->overlay.picture, - 0, 0, - 0, 0, - 0, 0, - xwin->attrs.width, height); - - /* draw the graphs into the overlay through the stencils being maintained by the sample callbacks */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_grapha_fill, xwin->overlay.grapha_picture, xwin->overlay.picture, - 0, 0, - xwin->overlay.phase, 0, - 0, 0, - xwin->attrs.width, height); - XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_graphb_fill, xwin->overlay.graphb_picture, xwin->overlay.picture, - 0, 0, - xwin->overlay.phase, 0, - 0, 0, - xwin->attrs.width, height); - - /* draw the shadow into the overlay picture using a translucent black source drawn through the shadow mask */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_shadow_fill, xwin->overlay.shadow_picture, xwin->overlay.picture, - 0, 0, - 0, 0, - 0, 0, - xwin->attrs.width, height); - - /* render overlay text into the overlay picture using a white source drawn through the overlay text as a mask, on top of everything */ - XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, - 0, 0, - 0, 0, - 0, 0, - xwin->attrs.width, (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); - - XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_snowflakes_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, - 0, 0, - 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, - 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, - xwin->attrs.width, height - (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); - - /* damage the window to ensure the updated overlay is drawn (TODO: this can be done more selectively/efficiently) */ - damage.x = xwin->attrs.x + xwin->attrs.border_width; - damage.y = xwin->attrs.y + xwin->attrs.border_width; - damage.width = xwin->attrs.width; - damage.height = height; - region = XFixesCreateRegion(VWM_XDISPLAY(vwm), &damage, 1); - vwm_composite_damage_add(vwm, region); -} - -void vwm_overlay_rate_increase(vwm_t *vwm) { - if (sampling_interval + 1 < sizeof(sampling_intervals) / sizeof(sampling_intervals[0])) sampling_interval++; -} - -void vwm_overlay_rate_decrease(vwm_t *vwm) { - if (sampling_interval >= 0) sampling_interval--; -} - - -/* comvenience function for returning the time delta as a seconds.fraction float */ -static float delta(struct timeval *cur, struct timeval *prev) -{ - struct timeval res; - float delta; - - /* determine the # of whole.fractional seconds between prev and cur */ - timersub(cur, prev, &res); - - delta = res.tv_sec; - delta += (float)((float)res.tv_usec) / 1000000.0; - - return delta; -} - - -void vwm_overlay_update(vwm_t *vwm, int *desired_delay) { - static int sampling_paused = 0; - static int contiguous_drops = 0; - float this_delta; - - init_overlay(vwm); - - gettimeofday(&maybe_sample, NULL); - if ((sampling_interval == -1 && !sampling_paused) || /* XXX this is kind of a kludge to get the 0 Hz indicator drawn before pausing */ - (sampling_interval != -1 && ((this_delta = delta(&maybe_sample, &this_sample)) >= sampling_intervals[sampling_interval]))) { - vmon_sys_stat_t *sys_stat; - - /* automatically lower the sample rate if we can't keep up with the current sample rate */ - if (sampling_interval != -1 && sampling_interval <= prev_sampling_interval && - this_delta >= (sampling_intervals[sampling_interval] * 1.5)) { - contiguous_drops++; - /* require > 1 contiguous drops before lowering the rate, tolerates spurious one-off stalls */ - if (contiguous_drops > 2) sampling_interval--; - } else contiguous_drops = 0; - - /* age the sys-wide sample data into "last" variables, before the new sample overwrites them. */ - last_sample = this_sample; - this_sample = maybe_sample; - if ((sys_stat = vmon.stores[VMON_STORE_SYS_STAT])) { - last_user_cpu = sys_stat->user; - last_system_cpu = sys_stat->system; - last_total = sys_stat->user + - sys_stat->nice + - sys_stat->system + - sys_stat->idle + - sys_stat->iowait + - sys_stat->irq + - sys_stat->softirq + - sys_stat->steal + - sys_stat->guest; - - last_idle = sys_stat->idle; - last_iowait = sys_stat->iowait; - } - - vmon_sample(&vmon); /* XXX: calls proc_sample_callback() for explicitly monitored processes after sampling their descendants */ - /* XXX: also calls sample_callback() per invocation after sampling the sys wants */ - sampling_paused = (sampling_interval == -1); - prev_sampling_interval = sampling_interval; - } - - - /* TODO: make some effort to compute how long to sleep, but this is perfectly fine for now. */ - *desired_delay = sampling_interval != -1 ? sampling_intervals[sampling_interval] * 300.0 : -1; -} diff --git a/src/overlay.h b/src/overlay.h deleted file mode 100644 index dd60319..0000000 --- a/src/overlay.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef _OVERLAY_H -#define _OVERLAY_H - -#include -#include - -typedef struct _vwm_t vwm_t; -typedef struct _vwm_xwindow_t vwm_xwindow_t; - -/* everything needed by the per-window overlay's context */ -typedef struct _vwm_overlay_t { - Pixmap text_pixmap; /* pixmap for overlayed text (kept around for XDrawText usage) */ - Picture text_picture; /* picture representation of text_pixmap */ - Picture shadow_picture; /* text shadow layer */ - Picture grapha_picture; /* graph A layer */ - Picture graphb_picture; /* graph B layer */ - Picture tmp_picture; /* 1 row worth of temporary picture space */ - Picture picture; /* overlay picture derived from the pixmap, for render compositing */ - int width; /* current width of the overlay */ - int height; /* current height of the overlay */ - int phase; /* current position within the (horizontally scrolling) graphs */ - int heirarchy_end; /* row where the process heirarchy currently ends */ - int snowflakes_cnt; /* count of snowflaked rows (reset to zero to truncate snowflakes display) */ - int gen_last_composed; /* the last composed vmon generation */ - int redraw_needed; /* if a redraw is required (like when the window is resized...) */ -} vwm_overlay_t; - -int vwm_overlay_xwin_composed_height(vwm_t *vwm, vwm_xwindow_t *xwin); -void vwm_overlay_xwin_reset_snowflakes(vwm_t *vwm, vwm_xwindow_t *xwin); -void vwm_overlay_xwin_create(vwm_t *vwm, vwm_xwindow_t *xwin); -void vwm_overlay_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin); -void vwm_overlay_xwin_compose(vwm_t *vwm, vwm_xwindow_t *xwin); -void vwm_overlay_rate_increase(vwm_t *vwm); -void vwm_overlay_rate_decrease(vwm_t *vwm); -void vwm_overlay_update(vwm_t *vwm, int *desired_delay); - -#endif diff --git a/src/overlays.c b/src/overlays.c new file mode 100644 index 0000000..defeae2 --- /dev/null +++ b/src/overlays.c @@ -0,0 +1,1020 @@ +/* + * \/\/\ + * + * Copyright (C) 2012-2016 Vito Caputo - + * + * 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 . + */ + +/* libvmon integration, warning: this gets a little crazy especially in the rendering. */ + +#include +#include +#include +#include +#include +#include + +#include "composite.h" +#include "libvmon/vmon.h" +#include "list.h" +#include "overlays.h" +#include "vwm.h" +#include "xwindow.h" + +#define OVERLAY_MASK_DEPTH 8 /* XXX: 1 would save memory, but Xorg isn't good at it */ +#define OVERLAY_MASK_FORMAT PictStandardA8 +#define OVERLAY_FIXED_FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1" +#define OVERLAY_ROW_HEIGHT 15 /* this should always be larger than the font height */ +#define OVERLAY_GRAPH_MIN_WIDTH 200 /* always create graphs at least this large */ +#define OVERLAY_GRAPH_MIN_HEIGHT (4 * OVERLAY_ROW_HEIGHT) +#define OVERLAY_ISTHREAD_ARGV "~" /* use this string to mark threads in the argv field */ +#define OVERLAY_NOCOMM_ARGV "#missed it!" /* use this string to substitute the command when missing in argv field */ +#define OVERLAY_MAX_ARGC 512 /* this is a huge amount */ + + +/* libvmon */ +static struct timeval maybe_sample, last_sample, this_sample = {0,0}; +static typeof(((vmon_sys_stat_t *)0)->user) last_user_cpu; +static typeof(((vmon_sys_stat_t *)0)->system) last_system_cpu; +static unsigned long long last_total, this_total, total_delta; +static unsigned long long last_idle, last_iowait, idle_delta, iowait_delta; +static vmon_t vmon; + +static float sampling_intervals[] = { + 1, /* ~1Hz */ + .1, /* ~10Hz */ + .05, /* ~20Hz */ + .025, /* ~40Hz */ + .01666}; /* ~60Hz */ +static int prev_sampling_interval = 1, sampling_interval = 1; + +/* space we need for every process being monitored */ +typedef struct _vwm_perproc_ctxt_t { + typeof(vmon.generation) generation; + typeof(((vmon_proc_stat_t *)0)->utime) last_utime; + typeof(((vmon_proc_stat_t *)0)->stime) last_stime; + typeof(((vmon_proc_stat_t *)0)->utime) utime_delta; + typeof(((vmon_proc_stat_t *)0)->stime) stime_delta; +} vwm_perproc_ctxt_t; + +/* Compositing / Overlays */ +static XFontStruct *overlay_font; +static GC text_gc; +static XRenderPictureAttributes pa_repeat = { .repeat = 1 }; +static XRenderPictureAttributes pa_no_repeat = { .repeat = 0 }; +static Picture overlay_shadow_fill, /* TODO: the repetition here smells like an XMacro waiting to happen */ + overlay_text_fill, + overlay_bg_fill, + overlay_snowflakes_text_fill, + overlay_grapha_fill, + overlay_graphb_fill, + overlay_finish_fill; +static XRenderColor overlay_visible_color = { 0xffff, 0xffff, 0xffff, 0xffff }, + overlay_shadow_color = { 0x0000, 0x0000, 0x0000, 0x8800}, + overlay_bg_color = { 0x0, 0x1000, 0x0, 0x9000}, + overlay_div_color = { 0x2000, 0x3000, 0x2000, 0x9000}, + overlay_snowflakes_visible_color = { 0xd000, 0xd000, 0xd000, 0x8000 }, + overlay_trans_color = {0x00, 0x00, 0x00, 0x00}, + overlay_grapha_color = { 0xff00, 0x0000, 0x0000, 0x3000 }, /* ~red */ + overlay_graphb_color = { 0x0000, 0xffff, 0xffff, 0x3000 }; /* ~cyan */ + + +/* moves what's below a given row up above it if specified, the row becoming discarded */ +static void snowflake_row(vwm_t *vwm, vwm_xwindow_t *xwin, Picture pic, int copy, int row) +{ + VWM_TRACE("pid=%i xwin=%p row=%i copy=%i heirarhcy_end=%i", xwin->monitor->pid, xwin, row, copy, xwin->overlay.heirarchy_end); + + if (copy) { + /* copy row to tmp */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, pic, None, xwin->overlay.tmp_picture, + 0, row * OVERLAY_ROW_HEIGHT, /* src */ + 0, 0, /* mask */ + 0, 0, /* dest */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ + } + + /* shift up */ + XRenderChangePicture(VWM_XDISPLAY(vwm), pic, CPRepeat, &pa_no_repeat); + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, pic, None, pic, + 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* src */ + 0, 0, /* mask */ + 0, row * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, (1 + xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ + XRenderChangePicture(VWM_XDISPLAY(vwm), pic, CPRepeat, &pa_repeat); + + if (copy) { + /* copy tmp to top of snowflakes */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.tmp_picture, None, pic, + 0, 0, /* src */ + 0, 0, /* mask */ + 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ + } else { + /* clear the snowflake row */ + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, pic, &overlay_trans_color, + 0, (xwin->overlay.heirarchy_end) * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ + } +} + +/* XXX TODO libvmon automagic children following races with explicit X client pid monitoring with different outcomes, it should be irrelevant which wins, + * currently the only visible difference is the snowflakes gap (heirarchy_end) varies, which is why I haven't bothered to fix it, I barely even notice. + */ + +/* shifts what's below a given row down a row, and clears the row, preparing it for populating */ +static void allocate_row(vwm_t *vwm, vwm_xwindow_t *xwin, Picture pic, int row) +{ + VWM_TRACE("pid=%i xwin=%p row=%i", xwin->monitor->pid, xwin, row); + + /* shift everything below the row down */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, pic, None, pic, + 0, row * OVERLAY_ROW_HEIGHT, /* src */ + 0, 0, /* mask */ + 0, (1 + row) * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, xwin->overlay.height - (1 + row) * OVERLAY_ROW_HEIGHT); /* dimensions */ + /* fill the space created with transparent pixels */ + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, pic, &overlay_trans_color, + 0, row * OVERLAY_ROW_HEIGHT, /* dest */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dimensions */ +} + + +/* shadow a row from the text layer in the shadow layer */ +static void shadow_row(vwm_t *vwm, vwm_xwindow_t *xwin, int row) +{ + /* the current technique for creating the shadow is to simply render the text at +1/-1 pixel offsets on both axis in translucent black */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, + 0, 0, + -1, row * OVERLAY_ROW_HEIGHT, + 0, row * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, OVERLAY_ROW_HEIGHT); + + XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, + 0, 0, + 0, -1 + row * OVERLAY_ROW_HEIGHT, + 0, row * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, OVERLAY_ROW_HEIGHT); + + XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, + 0, 0, + 1, row * OVERLAY_ROW_HEIGHT, + 0, row * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, OVERLAY_ROW_HEIGHT); + + XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_shadow_fill, xwin->overlay.text_picture, xwin->overlay.shadow_picture, + 0, 0, + 0, 1 + row * OVERLAY_ROW_HEIGHT, + 0, row * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, OVERLAY_ROW_HEIGHT); +} + + +/* simple helper to map the vmon per-proc argv array into an XTextItem array, deals with threads vs. processes and the possibility of the comm field not getting read in before the process exited... */ +static void argv2xtext(vmon_proc_t *proc, XTextItem *items, int max_items, int *nr_items) +{ + int i; + int nr = 0; + + if (proc->is_thread) { /* stick the thread marker at the start of threads */ + items[0].nchars = sizeof(OVERLAY_ISTHREAD_ARGV) - 1; + items[0].chars = OVERLAY_ISTHREAD_ARGV; + items[0].delta = 4; + items[0].font = None; + nr++; + } + + if (((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len) { + items[nr].nchars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1; + items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array; + } else { + /* sometimes a process is so ephemeral we don't manage to sample its comm, XXX TODO: we always have a pid, stringify it? */ + items[nr].nchars = sizeof(OVERLAY_NOCOMM_ARGV) - 1; + items[nr].chars = OVERLAY_NOCOMM_ARGV; + } + items[nr].delta = 4; + items[nr].font = None; + nr++; + + for (i = 1; nr < max_items && i < ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argc; nr++, i++) { + items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]; + items[nr].nchars = strlen(((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]); /* TODO: libvmon should inform us of the length */ + items[nr].delta = 4; + items[nr].font = None; + } + + (*nr_items) = nr; +} + + +/* helper for counting number of existing descendants subtrees */ +static int count_rows(vmon_proc_t *proc) { + int count = 1; /* XXX maybe suppress proc->is_new? */ + vmon_proc_t *child; + + if (!proc->is_thread) { + list_for_each_entry(child, &proc->threads, threads) { + count += count_rows(child); + } + } + + list_for_each_entry(child, &proc->children, siblings) { + count += count_rows(child); + } + + return count; +} + + +/* helper for detecting if any children/threads in the process heirarchy rooted @ proc are new/stale this sample */ +static int proc_heirarchy_changed(vmon_proc_t *proc) { + vmon_proc_t *child; + + if (proc->children_changed || proc->threads_changed) return 1; + + if (!proc->is_thread) { + list_for_each_entry(child, &proc->threads, threads) { + if (proc_heirarchy_changed(child)) return 1; + } + } + + list_for_each_entry(child, &proc->children, siblings) { + if (proc_heirarchy_changed(child)) return 1; + } + + return 0; +} + + +/* helper for drawing the vertical bars in the graph layers */ +static void draw_bars(vwm_t *vwm, vwm_xwindow_t *xwin, int row, double a_fraction, double a_total, double b_fraction, double b_total) +{ + int a_height, b_height; + + /* compute the bar heights for this sample */ + a_height = (a_fraction / a_total * (double)(OVERLAY_ROW_HEIGHT - 1)); /* give up 1 pixel for the div */ + b_height = (b_fraction / b_total * (double)(OVERLAY_ROW_HEIGHT - 1)); + + /* round up to 1 pixel when the scaled result is a fraction less than 1, + * I want to at least see 1 pixel blips for the slightest cpu utilization */ + if (a_fraction && !a_height) a_height = 1; + if (b_fraction && !b_height) b_height = 1; + + /* draw the two bars for this sample at the current phase in the graphs, note the first is ceiling-based, second floor-based */ + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.grapha_picture, &overlay_visible_color, + xwin->overlay.phase, row * OVERLAY_ROW_HEIGHT, /* dst x, y */ + 1, a_height); /* dst w, h */ + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.graphb_picture, &overlay_visible_color, + xwin->overlay.phase, row * OVERLAY_ROW_HEIGHT + (OVERLAY_ROW_HEIGHT - b_height) - 1, /* dst x, y */ + 1, b_height); /* dst w, h */ +} + + +/* draws proc in a row of the process heirarchy */ +static void draw_heirarchy_row(vwm_t *vwm, vwm_xwindow_t *xwin, vmon_proc_t *proc, int depth, int row, int heirarchy_changed) +{ + vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; + vmon_proc_t *child; + char str[256]; + int str_len, str_width; + XTextItem items[OVERLAY_MAX_ARGC]; + int nr_items; + +/* process heirarchy text and accompanying per-process details like wchan/pid/state... */ + + /* skip if obviously unnecessary (this can be further improved, but this makes a big difference as-is) */ + if (!xwin->overlay.redraw_needed && + !heirarchy_changed && + !BITTEST(proc_stat->changed, VMON_PROC_STAT_WCHAN) && + !BITTEST(proc_stat->changed, VMON_PROC_STAT_PID) && + !BITTEST(proc_stat->changed, VMON_PROC_STAT_STATE) && + !BITTEST(proc_stat->changed, VMON_PROC_STAT_ARGV)) return; + + /* TODO: make the columns interactively configurable @ runtime */ + if (!proc->is_new) { + /* XXX for now always clear the row, this should be capable of being optimized in the future (if the datums driving the text haven't changed...) */ + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, + 0, row * OVERLAY_ROW_HEIGHT, /* dst x, y */ + xwin->overlay.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ + } + + /* put the process' wchan, state, and PID columns @ the far right */ + if (proc->is_thread || list_empty(&proc->threads)) { /* only threads or non-threaded processes include the wchan and state */ + snprintf(str, sizeof(str), " %.*s %5i %c %n", + proc_stat->wchan.len, + proc_stat->wchan.len == 1 && proc_stat->wchan.array[0] == '0' ? "-" : proc_stat->wchan.array, + proc->pid, + proc_stat->state, + &str_len); + } else { /* we're a process having threads, suppress the wchan and state, as they will be displayed for the thread of same pid */ + snprintf(str, sizeof(str), " %5i %n", proc->pid, &str_len); + } + str_width = XTextWidth(overlay_font, str, str_len); + + /* the process' comm label indented according to depth, followed with their respective argv's */ + argv2xtext(proc, items, NELEMS(items), &nr_items); + XDrawText(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, + depth * (OVERLAY_ROW_HEIGHT / 2), (row + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ + items, nr_items); + + /* ensure the area for the rest of the stuff is cleared, we don't put much text into thread rows so skip it for those. */ + if (!proc->is_thread) { + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, + xwin->attrs.width - str_width, row * OVERLAY_ROW_HEIGHT, /* dst x,y */ + xwin->overlay.width - (xwin->attrs.width - str_width), OVERLAY_ROW_HEIGHT); /* dst w,h */ + } + + XDrawString(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, + xwin->attrs.width - str_width, (row + 1) * OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ + str, str_len); + + /* only if this process isn't the root process @ the window shall we consider all relational drawing conditions */ + if (proc != xwin->monitor) { + vmon_proc_t *ancestor, *sibling, *last_sibling = NULL; + struct list_head *rem; + int needs_tee = 0; + int bar_x = 0, bar_y = (row + 1) * OVERLAY_ROW_HEIGHT; + int sub; + + /* XXX: everything done in this code block only dirties _this_ process' row in the rendered overlay output */ + + /* walk up the ancestors until reaching xwin->monitor, any ancestors we encounter which have more siblings we draw a vertical bar for */ + /* this draws the |'s in something like: | | | | comm */ + for (sub = 1, ancestor = proc->parent; ancestor && ancestor != xwin->monitor; ancestor = ancestor->parent) { + sub++; + bar_x = (depth - sub) * (OVERLAY_ROW_HEIGHT / 2) + 4; + + /* determine if the ancestor has remaining siblings which are not stale, if so, draw a connecting bar at its depth */ + for (rem = ancestor->siblings.next; rem != &ancestor->parent->children; rem = rem->next) { + if (!(list_entry(rem, vmon_proc_t, siblings)->is_stale)) { + XDrawLine(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, + bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ + bar_x, bar_y); /* dst x2, y2 (vertical line) */ + break; /* stop looking for more siblings at this ancestor when we find one that isn't stale */ + } + } + } + + /* determine if _any_ of our siblings have children requiring us to draw a tee immediately before our comm string. + * The only sibling which doesn't cause this to happen is the last one in the children list, if it has children it has no impact on its remaining + * siblings, as there are none. + * + * This draws the + in something like: | | | | +comm + */ + + /* find the last sibling (this has to be done due to the potential for stale siblings at the tail, and we'd rather not repeatedly check for it) */ + list_for_each_entry(sibling, &proc->parent->children, siblings) { + if (!sibling->is_stale) last_sibling = sibling; + } + + /* now look for siblings with non-stale children to determine if a tee is needed, ignoring the last sibling */ + list_for_each_entry(sibling, &proc->parent->children, siblings) { + /* skip stale siblings, they aren't interesting as they're invisible, and the last sibling has no bearing on wether we tee or not. */ + if (sibling->is_stale || sibling == last_sibling) continue; + + /* if any of the other siblings have children which are not stale, put a tee in front of our name, but ignore stale children */ + list_for_each_entry(child, &sibling->children, siblings) { + if (!child->is_stale) { + needs_tee = 1; + break; + } + } + + /* if we still don't think we need a tee, check if there are threads */ + if (!needs_tee) { + list_for_each_entry(child, &sibling->threads, threads) { + if (!child->is_stale) { + needs_tee = 1; + break; + } + } + } + + /* found a tee is necessary, all that's left is to determine if the tee is a corner and draw it accordingly, stopping the search. */ + if (needs_tee) { + bar_x = (depth - 1) * (OVERLAY_ROW_HEIGHT / 2) + 4; + + /* if we're the last sibling, corner the tee by shortening the vbar */ + if (proc == last_sibling) { + XDrawLine(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, + bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ + bar_x, bar_y - 4); /* dst x2, y2 (vertical bar) */ + } else { + XDrawLine(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, + bar_x, bar_y - OVERLAY_ROW_HEIGHT, /* dst x1, y1 */ + bar_x, bar_y); /* dst x2, y2 (vertical bar) */ + } + + XDrawLine(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, + bar_x, bar_y - 4, /* dst x1, y1 */ + bar_x + 2, bar_y - 4); /* dst x2, y2 (horizontal bar) */ + + /* terminate the outer sibling loop upon drawing the tee... */ + break; + } + } + } + + shadow_row(vwm, xwin, row); +} + + +/* recursive draw function for "rest" of overlay: the per-process rows (heirarchy, argv, state, wchan, pid...) */ +static void draw_overlay_rest(vwm_t *vwm, vwm_xwindow_t *xwin, vmon_proc_t *proc, int *depth, int *row, int heirarchy_changed) +{ + vmon_proc_t *child; + vwm_perproc_ctxt_t *proc_ctxt = proc->foo; + vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; + + /* graph variables */ + double utime_delta, stime_delta; + + /* text variables */ + XTextItem items[OVERLAY_MAX_ARGC]; + int nr_items; + + /* Some parts of this we must do on every sample to maintain coherence in the graphs, since they're incrementally kept + * in sync with the process heirarchy, allocating and shifting the rows as processes are created and destroyed. Everything + * else we should be able to skip doing unless overlay.redraw_needed or their contents changed. + */ + + if (proc->is_stale) { + /* what to do when a process (subtree) has gone away */ + static int in_stale = 0; + int in_stale_entrypoint = 0; + + /* I snowflake the stale processes from the leaves up for a more intuitive snowflake order... + * (I expect the command at the root of the subtree to appear at the top of the snowflakes...) */ + /* This does require that I do a separate forward recursion to determine the number of rows + * so I can correctly snowflake in reverse */ + if (!in_stale) { + VWM_TRACE("entered stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); + in_stale_entrypoint = in_stale = 1; + (*row) += count_rows(proc) - 1; + } + + (*depth)++; + list_for_each_entry_prev(child, &proc->children, siblings) { + draw_overlay_rest(vwm, xwin, child, depth, row, heirarchy_changed); + (*row)--; + } + + if (!proc->is_thread) { + list_for_each_entry_prev(child, &proc->threads, threads) { + draw_overlay_rest(vwm, xwin, child, depth, row, heirarchy_changed); + (*row)--; + } + } + (*depth)--; + + VWM_TRACE("%i (%.*s) is stale @ depth %i row %i is_thread=%i", proc->pid, + ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1, + ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array, + (*depth), (*row), proc->is_thread); + + /* stamp the graphs with the finish line */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, overlay_finish_fill, None, xwin->overlay.grapha_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ + 1, OVERLAY_ROW_HEIGHT - 1); + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, overlay_finish_fill, None, xwin->overlay.graphb_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + xwin->overlay.phase, (*row) * OVERLAY_ROW_HEIGHT, /* dst x, y */ + 1, OVERLAY_ROW_HEIGHT - 1); + + /* extract the row from the various layers */ + snowflake_row(vwm, xwin, xwin->overlay.grapha_picture, 1, (*row)); + snowflake_row(vwm, xwin, xwin->overlay.graphb_picture, 1, (*row)); + snowflake_row(vwm, xwin, xwin->overlay.text_picture, 0, (*row)); + snowflake_row(vwm, xwin, xwin->overlay.shadow_picture, 0, (*row)); + xwin->overlay.snowflakes_cnt++; + + /* stamp the name (and whatever else we include) into overlay.text_picture */ + argv2xtext(proc, items, NELEMS(items), &nr_items); + XDrawText(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, + 5, (xwin->overlay.heirarchy_end + 1) * OVERLAY_ROW_HEIGHT - 3,/* dst x, y */ + items, nr_items); + shadow_row(vwm, xwin, xwin->overlay.heirarchy_end); + + xwin->overlay.heirarchy_end--; + + if (in_stale_entrypoint) { + VWM_TRACE("exited stale at xwin=%p depth=%i row=%i", xwin, *depth, *row); + in_stale = 0; + } + + return; + } else if (proc->is_new) { + /* what to do when a process has been introduced */ + VWM_TRACE("%i is new", proc->pid); + + allocate_row(vwm, xwin, xwin->overlay.grapha_picture, (*row)); + allocate_row(vwm, xwin, xwin->overlay.graphb_picture, (*row)); + allocate_row(vwm, xwin, xwin->overlay.text_picture, (*row)); + allocate_row(vwm, xwin, xwin->overlay.shadow_picture, (*row)); + + xwin->overlay.heirarchy_end++; + } + +/* CPU utilization graphs */ + /* use the generation number to avoid recomputing this stuff for callbacks recurring on the same process in the same sample */ + if (proc_ctxt->generation != vmon.generation) { + proc_ctxt->stime_delta = proc_stat->stime - proc_ctxt->last_stime; + proc_ctxt->utime_delta = proc_stat->utime - proc_ctxt->last_utime; + proc_ctxt->last_utime = proc_stat->utime; + proc_ctxt->last_stime = proc_stat->stime; + + proc_ctxt->generation = vmon.generation; + } + + if (proc->is_new) { + /* we need a minimum of two samples before we can compute a delta to plot, + * so we suppress that and instead mark the start of monitoring with an impossible 100% of both graph contexts, a starting line. */ + stime_delta = utime_delta = total_delta; + } else { + stime_delta = proc_ctxt->stime_delta; + utime_delta = proc_ctxt->utime_delta; + } + + draw_bars(vwm, xwin, *row, stime_delta, total_delta, utime_delta, total_delta); + + draw_heirarchy_row(vwm, xwin, proc, *depth, *row, heirarchy_changed); + + (*row)++; + + /* recur any threads first, then any children processes */ + (*depth)++; + if (!proc->is_thread) { /* XXX: the threads member serves as the list head only when not a thread */ + list_for_each_entry(child, &proc->threads, threads) { + draw_overlay_rest(vwm, xwin, child, depth, row, heirarchy_changed); + } + } + + list_for_each_entry(child, &proc->children, siblings) { + draw_overlay_rest(vwm, xwin, child, depth, row, heirarchy_changed); + } + (*depth)--; +} + + + +/* recursive draw function entrypoint, draws the IOWait/Idle/HZ row, then enters draw_overlay_rest() */ +static void draw_overlay(vwm_t *vwm, vwm_xwindow_t *xwin, vmon_proc_t *proc, int *depth, int *row) +{ + vmon_proc_t *child; + vwm_perproc_ctxt_t *proc_ctxt = proc->foo; + vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; + + /* text variables */ + char str[256]; + int str_len, str_width; + + int heirarchy_changed = 0; + +/* CPU utilization graphs */ + /* IOWait and Idle % @ row 0 */ + draw_bars(vwm, xwin, *row, iowait_delta, total_delta, idle_delta, total_delta); + + /* only draw the \/\/\ and HZ if necessary */ + if (xwin->overlay.redraw_needed || prev_sampling_interval != sampling_interval) { + snprintf(str, sizeof(str), "\\/\\/\\ %2iHz %n", (int)(sampling_interval < 0 ? 0 : 1 / sampling_intervals[sampling_interval]), &str_len); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, + 0, 0, /* dst x, y */ + xwin->attrs.width, OVERLAY_ROW_HEIGHT); /* dst w, h */ + str_width = XTextWidth(overlay_font, str, str_len); + XDrawString(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, text_gc, + xwin->attrs.width - str_width, OVERLAY_ROW_HEIGHT - 3, /* dst x, y */ + str, str_len); + shadow_row(vwm, xwin, 0); + } + (*row)++; + + if (!xwin->overlay.redraw_needed) heirarchy_changed = proc_heirarchy_changed(proc); + + + draw_overlay_rest(vwm, xwin, proc, depth, row, heirarchy_changed); + + xwin->overlay.redraw_needed = 0; + + return; +} + + +/* consolidated version of overlay text and graph rendering, makes snowflakes integration cleaner, this always gets called regadless of the overlays mode */ +static void maintain_overlay(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + int row = 0, depth = 0; + + if (!xwin->monitor || !xwin->monitor->stores[VMON_STORE_PROC_STAT]) return; + + /* TODO: + * I side effect of responding to window resizes in this function is there's a latency proportional to the current sample_interval. + * Something to fix is to resize the overlays when the window resizes. + * However, simply resizing the overlays is insufficient. Their contents need to be redrawn in the new dimensions, this is where it + * gets annoying. The current maintain/draw_overlay makes assumptions about being run from the periodic vmon per-process callback. + * There needs to be a redraw mode added where draw_overlay is just reconstructing the current state, which requires that we suppress + * the phase advance and in maintain_overlay() and just enter draw_overlay() to redraw everything for the same generation. + * So this probably requires some tweaking of draw_overlay() as well as maintain_overlay(). I want to be able tocall mainta_overlays() + * from anywhere, and have it detect if it's being called on the same generation or if the generation has advanced. + * For now, the monitors will just be a little latent in window resizes which is pretty harmless artifact. + */ + + if (xwin->attrs.width != xwin->overlay.width || xwin->attrs.height != xwin->overlay.height) xwin->overlay.redraw_needed = 1; + + /* if the window is larger than the overlays currently are, enlarge them */ + if (xwin->attrs.width > xwin->overlay.width || xwin->attrs.height > xwin->overlay.height) { + vwm_overlay_t existing; + Pixmap pixmap; + + existing = xwin->overlay; + + xwin->overlay.width = MAX(xwin->overlay.width, MAX(xwin->attrs.width, OVERLAY_GRAPH_MIN_WIDTH)); + xwin->overlay.height = MAX(xwin->overlay.height, MAX(xwin->attrs.height, OVERLAY_GRAPH_MIN_HEIGHT)); + + pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); + xwin->overlay.grapha_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); + XFreePixmap(VWM_XDISPLAY(vwm), pixmap); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); + + pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); + xwin->overlay.graphb_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), CPRepeat, &pa_repeat); + XFreePixmap(VWM_XDISPLAY(vwm), pixmap); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); + + pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, OVERLAY_ROW_HEIGHT, OVERLAY_MASK_DEPTH); + xwin->overlay.tmp_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), 0, NULL); + XFreePixmap(VWM_XDISPLAY(vwm), pixmap); + + /* keep the text_pixmap reference around for XDrawText usage */ + xwin->overlay.text_pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); + xwin->overlay.text_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), xwin->overlay.text_pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), 0, NULL); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.text_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); + + pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, OVERLAY_MASK_DEPTH); + xwin->overlay.shadow_picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), OVERLAY_MASK_FORMAT), 0, NULL); + XFreePixmap(VWM_XDISPLAY(vwm), pixmap); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.shadow_picture, &overlay_trans_color, 0, 0, xwin->overlay.width, xwin->overlay.height); + + pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), xwin->overlay.width, xwin->overlay.height, 32); + xwin->overlay.picture = XRenderCreatePicture(VWM_XDISPLAY(vwm), pixmap, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), 0, NULL); + XFreePixmap(VWM_XDISPLAY(vwm), pixmap); + + if (existing.width) { + /* XXX: note the graph pictures are copied from their current phase in the x dimension */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.grapha_picture, None, xwin->overlay.grapha_picture, + existing.phase, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.graphb_picture, None, xwin->overlay.graphb_picture, + existing.phase, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.text_picture, None, xwin->overlay.text_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.shadow_picture, None, xwin->overlay.shadow_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, existing.picture, None, xwin->overlay.picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + existing.width, existing.height); + xwin->overlay.phase = 0; /* having unrolled the existing graph[ab] pictures into the larger ones, phase is reset to 0 */ + XRenderFreePicture(VWM_XDISPLAY(vwm), existing.grapha_picture); + XRenderFreePicture(VWM_XDISPLAY(vwm), existing.graphb_picture); + XRenderFreePicture(VWM_XDISPLAY(vwm), existing.tmp_picture); + XRenderFreePicture(VWM_XDISPLAY(vwm), existing.text_picture); + XFreePixmap(VWM_XDISPLAY(vwm), existing.text_pixmap); + XRenderFreePicture(VWM_XDISPLAY(vwm), existing.shadow_picture); + XRenderFreePicture(VWM_XDISPLAY(vwm), existing.picture); + } + } + + xwin->overlay.phase += (xwin->overlay.width - 1); /* simply change this to .phase++ to scroll the other direction */ + xwin->overlay.phase %= xwin->overlay.width; + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.grapha_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, xwin->overlay.graphb_picture, &overlay_trans_color, xwin->overlay.phase, 0, 1, xwin->overlay.height); + + /* recursively draw the monitored processes to the overlay */ + draw_overlay(vwm, xwin, xwin->monitor, &depth, &row); +} + + +/* this callback gets invoked at sample time for every process we've explicitly monitored (not autofollowed children/threads) + * It's where we update the cumulative data for all windows, including the graph masks, regardless of their visibility + * It's also where we compose the graphs and text for visible windows into a picture ready for compositing with the window contents */ +static void proc_sample_callback(vmon_t *vmon, void *sys_cb_arg, vmon_proc_t *proc, void *proc_cb_arg) +{ + vwm_t *vwm = sys_cb_arg; + vwm_xwindow_t *xwin = proc_cb_arg; + //VWM_TRACE("proc=%p xwin=%p", proc, xwin); + /* render the various always-updated overlays, this is the component we do regardless of the overlays mode and window visibility, + * essentially the incrementally rendered/historic components */ + maintain_overlay(vwm, xwin); + + /* if we've updated overlays for a mapped window, kick the compositor to do the costly parts of overlay drawing and compositing. */ + if (vwm_xwin_is_mapped(vwm, xwin)) vwm_composite_repaint_needed(vwm); +} + + +/* this callback gets invoked at sample time once "per sys" */ +static void sample_callback(vmon_t *_vmon, void *sys_cb_arg) +{ + vmon_sys_stat_t *sys_stat = vmon.stores[VMON_STORE_SYS_STAT]; + this_total = sys_stat->user + sys_stat->nice + sys_stat->system + + sys_stat->idle + sys_stat->iowait + sys_stat->irq + + sys_stat->softirq + sys_stat->steal + sys_stat->guest; + + total_delta = this_total - last_total; + idle_delta = sys_stat->idle - last_idle; + iowait_delta = sys_stat->iowait - last_iowait; +} + + +/* these callbacks are invoked by the vmon library when process instances become monitored/unmonitored */ +static void vmon_ctor_cb(vmon_t *vmon, vmon_proc_t *proc) +{ + VWM_TRACE("proc->pid=%i", proc->pid); + proc->foo = calloc(1, sizeof(vwm_perproc_ctxt_t)); +} + + +static void vmon_dtor_cb(vmon_t *vmon, vmon_proc_t *proc) +{ + VWM_TRACE("proc->pid=%i", proc->pid); + if (proc->foo) { + free(proc->foo); + proc->foo = NULL; + } +} + + +/* return the composed height of the overlay */ +int vwm_overlay_xwin_composed_height(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + int snowflakes = xwin->overlay.snowflakes_cnt ? 1 + xwin->overlay.snowflakes_cnt : 0; /* don't include the separator row if there are no snowflakes */ + + return MIN((xwin->overlay.heirarchy_end + snowflakes) * OVERLAY_ROW_HEIGHT, xwin->attrs.height); +} + +/* reset snowflakes on the specified window */ +void vwm_overlay_xwin_reset_snowflakes(vwm_t *vwm, vwm_xwindow_t *xwin) { + if (xwin->overlay.snowflakes_cnt) { + xwin->overlay.snowflakes_cnt = 0; + vwm_composite_damage_win(vwm, xwin); + } +} + +static void init_overlay(vwm_t *vwm) { + static int initialized; + Window bitmask; + + if (initialized) return; + initialized = 1; + + /* initialize libvmon */ + vmon_init(&vmon, VMON_FLAG_2PASS, VMON_WANT_SYS_STAT, (VMON_WANT_PROC_STAT | VMON_WANT_PROC_FOLLOW_CHILDREN | VMON_WANT_PROC_FOLLOW_THREADS)); + vmon.proc_ctor_cb = vmon_ctor_cb; + vmon.proc_dtor_cb = vmon_dtor_cb; + vmon.sample_cb = sample_callback; + vmon.sample_cb_arg = vwm; + gettimeofday(&this_sample, NULL); + + /* get all the text and graphics stuff setup for overlays */ + overlay_font = XLoadQueryFont(VWM_XDISPLAY(vwm), OVERLAY_FIXED_FONT); + + /* create a GC for rendering the text using Xlib into the text overlay stencils */ + bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, OVERLAY_MASK_DEPTH); + text_gc = XCreateGC(VWM_XDISPLAY(vwm), bitmask, 0, NULL); + XSetForeground(VWM_XDISPLAY(vwm), text_gc, WhitePixel(VWM_XDISPLAY(vwm), VWM_XSCREENNUM(vwm))); + XFreePixmap(VWM_XDISPLAY(vwm), bitmask); + + /* create some repeating source fill pictures for drawing through the text and graph stencils */ + bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); + overlay_text_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_text_fill, &overlay_visible_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); + overlay_shadow_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_shadow_fill, &overlay_shadow_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, OVERLAY_ROW_HEIGHT, 32); + overlay_bg_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_bg_fill, &overlay_bg_color, 0, 0, 1, OVERLAY_ROW_HEIGHT); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_bg_fill, &overlay_div_color, 0, OVERLAY_ROW_HEIGHT - 1, 1, 1); + + bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); + overlay_snowflakes_text_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_snowflakes_text_fill, &overlay_snowflakes_visible_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); + overlay_grapha_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_grapha_fill, &overlay_grapha_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 1, 32); + overlay_graphb_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_graphb_fill, &overlay_graphb_color, 0, 0, 1, 1); + + bitmask = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), 1, 2, 32); + overlay_finish_fill = XRenderCreatePicture(VWM_XDISPLAY(vwm), bitmask, XRenderFindStandardFormat(VWM_XDISPLAY(vwm), PictStandardARGB32), CPRepeat, &pa_repeat); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_finish_fill, &overlay_visible_color, 0, 0, 1, 1); + XRenderFillRectangle(VWM_XDISPLAY(vwm), PictOpSrc, overlay_finish_fill, &overlay_trans_color, 0, 1, 1, 1); +} + + +/* install a monitor on the window if it doesn't already have one and has _NET_WM_PID set */ +void vwm_overlay_xwin_create(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + Atom type; + int fmt; + unsigned long nitems; + unsigned long nbytes; + long *foo = NULL; + int pid = -1; + + init_overlay(vwm); + + if (xwin->monitor) return; + + if (XGetWindowProperty(VWM_XDISPLAY(vwm), xwin->id, vwm->wm_pid_atom, 0, 1, False, XA_CARDINAL, + &type, &fmt, &nitems, &nbytes, (unsigned char **)&foo) != Success || !foo) return; + + pid = *foo; + XFree(foo); + + /* add the client process to the monitoring heirarchy */ + /* XXX note libvmon here maintains a unique callback for each unique callback+xwin pair, so multi-window processes work */ + xwin->monitor = vmon_proc_monitor(&vmon, NULL, pid, VMON_WANT_PROC_INHERIT, (void (*)(vmon_t *, void *, vmon_proc_t *, void *))proc_sample_callback, xwin); + /* FIXME: count_rows() isn't returning the right count sometimes (off by ~1), it seems to be related to racing with the automatic child monitoring */ + /* the result is an extra row sometimes appearing below the process heirarchy */ + xwin->overlay.heirarchy_end = 1 + count_rows(xwin->monitor); + xwin->overlay.snowflakes_cnt = 0; +} + + +/* remove monitoring on the window if installed */ +void vwm_overlay_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + if (xwin->monitor) vmon_proc_unmonitor(&vmon, xwin->monitor, (void (*)(vmon_t *, void *, vmon_proc_t *, void *))proc_sample_callback, xwin); +} + + +/* this composes the maintained overlay into the window's overlay picture, this gets called from paint_all() on every repaint of xwin */ +/* we noop the call if the gen_last_composed and monitor->proc.generation numbers match, indicating there's nothing new to compose. */ +void vwm_overlay_xwin_compose(vwm_t *vwm, vwm_xwindow_t *xwin) +{ + XserverRegion region; + XRectangle damage; + int height; + + if (!xwin->overlay.width) return; /* prevent winning race with maintain_overlay() and using an unready overlay... */ + + if (xwin->overlay.gen_last_composed == xwin->monitor->generation) return; /* noop if no sampling occurred since last compose */ + xwin->overlay.gen_last_composed = xwin->monitor->generation; /* remember this generation */ + + //VWM_TRACE("composing %p", xwin); + + height = vwm_overlay_xwin_composed_height(vwm, xwin); + + /* fill the overlay picture with the background */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpSrc, overlay_bg_fill, None, xwin->overlay.picture, + 0, 0, + 0, 0, + 0, 0, + xwin->attrs.width, height); + + /* draw the graphs into the overlay through the stencils being maintained by the sample callbacks */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_grapha_fill, xwin->overlay.grapha_picture, xwin->overlay.picture, + 0, 0, + xwin->overlay.phase, 0, + 0, 0, + xwin->attrs.width, height); + XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_graphb_fill, xwin->overlay.graphb_picture, xwin->overlay.picture, + 0, 0, + xwin->overlay.phase, 0, + 0, 0, + xwin->attrs.width, height); + + /* draw the shadow into the overlay picture using a translucent black source drawn through the shadow mask */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_shadow_fill, xwin->overlay.shadow_picture, xwin->overlay.picture, + 0, 0, + 0, 0, + 0, 0, + xwin->attrs.width, height); + + /* render overlay text into the overlay picture using a white source drawn through the overlay text as a mask, on top of everything */ + XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, + 0, 0, + 0, 0, + 0, 0, + xwin->attrs.width, (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); + + XRenderComposite(VWM_XDISPLAY(vwm), PictOpOver, overlay_snowflakes_text_fill, xwin->overlay.text_picture, xwin->overlay.picture, + 0, 0, + 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, + 0, xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT, + xwin->attrs.width, height - (xwin->overlay.heirarchy_end * OVERLAY_ROW_HEIGHT)); + + /* damage the window to ensure the updated overlay is drawn (TODO: this can be done more selectively/efficiently) */ + damage.x = xwin->attrs.x + xwin->attrs.border_width; + damage.y = xwin->attrs.y + xwin->attrs.border_width; + damage.width = xwin->attrs.width; + damage.height = height; + region = XFixesCreateRegion(VWM_XDISPLAY(vwm), &damage, 1); + vwm_composite_damage_add(vwm, region); +} + +void vwm_overlay_rate_increase(vwm_t *vwm) { + if (sampling_interval + 1 < sizeof(sampling_intervals) / sizeof(sampling_intervals[0])) sampling_interval++; +} + +void vwm_overlay_rate_decrease(vwm_t *vwm) { + if (sampling_interval >= 0) sampling_interval--; +} + + +/* comvenience function for returning the time delta as a seconds.fraction float */ +static float delta(struct timeval *cur, struct timeval *prev) +{ + struct timeval res; + float delta; + + /* determine the # of whole.fractional seconds between prev and cur */ + timersub(cur, prev, &res); + + delta = res.tv_sec; + delta += (float)((float)res.tv_usec) / 1000000.0; + + return delta; +} + + +void vwm_overlay_update(vwm_t *vwm, int *desired_delay) { + static int sampling_paused = 0; + static int contiguous_drops = 0; + float this_delta; + + init_overlay(vwm); + + gettimeofday(&maybe_sample, NULL); + if ((sampling_interval == -1 && !sampling_paused) || /* XXX this is kind of a kludge to get the 0 Hz indicator drawn before pausing */ + (sampling_interval != -1 && ((this_delta = delta(&maybe_sample, &this_sample)) >= sampling_intervals[sampling_interval]))) { + vmon_sys_stat_t *sys_stat; + + /* automatically lower the sample rate if we can't keep up with the current sample rate */ + if (sampling_interval != -1 && sampling_interval <= prev_sampling_interval && + this_delta >= (sampling_intervals[sampling_interval] * 1.5)) { + contiguous_drops++; + /* require > 1 contiguous drops before lowering the rate, tolerates spurious one-off stalls */ + if (contiguous_drops > 2) sampling_interval--; + } else contiguous_drops = 0; + + /* age the sys-wide sample data into "last" variables, before the new sample overwrites them. */ + last_sample = this_sample; + this_sample = maybe_sample; + if ((sys_stat = vmon.stores[VMON_STORE_SYS_STAT])) { + last_user_cpu = sys_stat->user; + last_system_cpu = sys_stat->system; + last_total = sys_stat->user + + sys_stat->nice + + sys_stat->system + + sys_stat->idle + + sys_stat->iowait + + sys_stat->irq + + sys_stat->softirq + + sys_stat->steal + + sys_stat->guest; + + last_idle = sys_stat->idle; + last_iowait = sys_stat->iowait; + } + + vmon_sample(&vmon); /* XXX: calls proc_sample_callback() for explicitly monitored processes after sampling their descendants */ + /* XXX: also calls sample_callback() per invocation after sampling the sys wants */ + sampling_paused = (sampling_interval == -1); + prev_sampling_interval = sampling_interval; + } + + + /* TODO: make some effort to compute how long to sleep, but this is perfectly fine for now. */ + *desired_delay = sampling_interval != -1 ? sampling_intervals[sampling_interval] * 300.0 : -1; +} diff --git a/src/overlays.h b/src/overlays.h new file mode 100644 index 0000000..bfcf0da --- /dev/null +++ b/src/overlays.h @@ -0,0 +1,37 @@ +#ifndef _OVERLAYS_H +#define _OVERLAYS_H + +#include +#include + +typedef struct _vwm_t vwm_t; +typedef struct _vwm_xwindow_t vwm_xwindow_t; + +/* everything needed by the per-window overlay's context */ +typedef struct _vwm_overlay_t { + Pixmap text_pixmap; /* pixmap for overlayed text (kept around for XDrawText usage) */ + Picture text_picture; /* picture representation of text_pixmap */ + Picture shadow_picture; /* text shadow layer */ + Picture grapha_picture; /* graph A layer */ + Picture graphb_picture; /* graph B layer */ + Picture tmp_picture; /* 1 row worth of temporary picture space */ + Picture picture; /* overlay picture derived from the pixmap, for render compositing */ + int width; /* current width of the overlay */ + int height; /* current height of the overlay */ + int phase; /* current position within the (horizontally scrolling) graphs */ + int heirarchy_end; /* row where the process heirarchy currently ends */ + int snowflakes_cnt; /* count of snowflaked rows (reset to zero to truncate snowflakes display) */ + int gen_last_composed; /* the last composed vmon generation */ + int redraw_needed; /* if a redraw is required (like when the window is resized...) */ +} vwm_overlay_t; + +int vwm_overlay_xwin_composed_height(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_xwin_reset_snowflakes(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_xwin_create(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_xwin_destroy(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_xwin_compose(vwm_t *vwm, vwm_xwindow_t *xwin); +void vwm_overlay_rate_increase(vwm_t *vwm); +void vwm_overlay_rate_decrease(vwm_t *vwm); +void vwm_overlay_update(vwm_t *vwm, int *desired_delay); + +#endif diff --git a/src/xwindow.c b/src/xwindow.c index 76d758e..f19180e 100644 --- a/src/xwindow.c +++ b/src/xwindow.c @@ -23,6 +23,7 @@ #include "composite.h" #include "list.h" +#include "overlays.h" #include "vwm.h" #include "window.h" #include "xwindow.h" diff --git a/src/xwindow.h b/src/xwindow.h index f3be681..c72abc4 100644 --- a/src/xwindow.h +++ b/src/xwindow.h @@ -7,7 +7,7 @@ #include "libvmon/vmon.h" #include "list.h" -#include "overlay.h" +#include "overlays.h" typedef struct _vwm_t vwm_t; typedef struct _vwm_window_t vwm_window_t; -- cgit v1.2.3