summaryrefslogtreecommitdiff
path: root/src/charts.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/charts.c')
-rw-r--r--src/charts.c1252
1 files changed, 1252 insertions, 0 deletions
diff --git a/src/charts.c b/src/charts.c
new file mode 100644
index 0000000..4deb846
--- /dev/null
+++ b/src/charts.c
@@ -0,0 +1,1252 @@
+/*
+ * \/\/\
+ *
+ * Copyright (C) 2012-2017 Vito Caputo - <vcaputo@gnugeneration.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* libvmon integration, warning: this gets a little crazy especially in the rendering. */
+
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/Xrender.h>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include "charts.h"
+#include "composite.h"
+#include "libvmon/vmon.h"
+#include "list.h"
+#include "vwm.h"
+#include "xwindow.h"
+
+#define CHART_MASK_DEPTH 8 /* XXX: 1 would save memory, but Xorg isn't good at it */
+#define CHART_FIXED_FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1"
+#define CHART_ROW_HEIGHT 15 /* this should always be larger than the font height */
+#define CHART_GRAPH_MIN_WIDTH 200 /* always create graphs at least this large */
+#define CHART_GRAPH_MIN_HEIGHT (4 * CHART_ROW_HEIGHT)
+#define CHART_ISTHREAD_ARGV "~" /* use this string to mark threads in the argv field */
+#define CHART_NOCOMM_ARGV "#missed it!" /* use this string to substitute the command when missing in argv field */
+#define CHART_MAX_ARGC 512 /* this is a huge amount */
+#define CHART_VMON_PROC_WANTS (VMON_WANT_PROC_STAT | VMON_WANT_PROC_FOLLOW_CHILDREN | VMON_WANT_PROC_FOLLOW_THREADS)
+#define CHART_VMON_SYS_WANTS (VMON_WANT_SYS_STAT)
+
+/* the global charts state, supplied to vwm_chart_create() which keeps a reference for future use. */
+typedef struct _vwm_charts_t {
+ vwm_xserver_t *xserver; /* xserver supplied to vwm_charts_init() */
+
+ /* libvmon */
+ struct timeval maybe_sample, last_sample, this_sample;
+ typeof(((vmon_sys_stat_t *)0)->user) last_user_cpu;
+ typeof(((vmon_sys_stat_t *)0)->system) last_system_cpu;
+ unsigned long long last_total, this_total, total_delta;
+ unsigned long long last_idle, last_iowait, idle_delta, iowait_delta;
+ vmon_t vmon;
+ float prev_sampling_interval, sampling_interval;
+ int sampling_paused, contiguous_drops;
+
+ /* X */
+ XFontStruct *chart_font;
+ GC text_gc;
+ Picture shadow_fill,
+ text_fill,
+ bg_fill,
+ snowflakes_text_fill,
+ grapha_fill,
+ graphb_fill,
+ finish_fill;
+} vwm_charts_t;
+
+/* everything needed by the per-window chart's context */
+typedef struct _vwm_chart_t {
+ vmon_proc_t *monitor; /* vmon process monitor handle */
+ Pixmap text_pixmap; /* pixmap for charted 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; /* chart picture derived from the pixmap, for render compositing */
+ int width; /* current width of the chart */
+ int height; /* current height of the chart */
+ int visible_width; /* currently visible width of the chart */
+ int visible_height; /* currently visible height of the chart */
+ 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_chart_t;
+
+/* space we need for every process being monitored */
+typedef struct _vwm_perproc_ctxt_t {
+ typeof(((vmon_t *)0)->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;
+
+
+static float sampling_intervals[] = {
+ INFINITY, /* STOPPED */
+ 1, /* ~1Hz */
+ .1, /* ~10Hz */
+ .05, /* ~20Hz */
+ .025, /* ~40Hz */
+ .01666}; /* ~60Hz */
+
+static XRenderColor chart_visible_color = { 0xffff, 0xffff, 0xffff, 0xffff },
+ chart_shadow_color = { 0x0000, 0x0000, 0x0000, 0x8800},
+ chart_bg_color = { 0x0, 0x1000, 0x0, 0x9000},
+ chart_div_color = { 0x2000, 0x3000, 0x2000, 0x9000},
+ chart_snowflakes_visible_color = { 0xd000, 0xd000, 0xd000, 0x8000 },
+ chart_trans_color = {0x00, 0x00, 0x00, 0x00},
+ chart_grapha_color = { 0xff00, 0x0000, 0x0000, 0x3000 }, /* ~red */
+ chart_graphb_color = { 0x0000, 0xffff, 0xffff, 0x3000 }; /* ~cyan */
+static XRenderPictureAttributes pa_repeat = { .repeat = 1 };
+static XRenderPictureAttributes pa_no_repeat = { .repeat = 0 };
+
+
+/* this callback gets invoked at sample time once "per sys" */
+static void sample_callback(vmon_t *vmon, void *arg)
+{
+ vwm_charts_t *charts = arg;
+ vmon_sys_stat_t *sys_stat = vmon->stores[VMON_STORE_SYS_STAT];
+
+ charts->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;
+
+ charts->total_delta = charts->this_total - charts->last_total;
+ charts->idle_delta = sys_stat->idle - charts->last_idle;
+ charts->iowait_delta = sys_stat->iowait - charts->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;
+ }
+}
+
+
+/* convenience helper for creating a pixmap */
+static Pixmap create_pixmap(vwm_charts_t *charts, unsigned width, unsigned height, unsigned depth)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+
+ return XCreatePixmap(xserver->display, XSERVER_XROOT(xserver), width, height, depth);
+}
+
+
+/* convenience helper for creating a picture, supply res_pixmap to keep a reference to the pixmap drawable. */
+static Picture create_picture(vwm_charts_t *charts, unsigned width, unsigned height, unsigned depth, unsigned long attr_mask, XRenderPictureAttributes *attr, Pixmap *res_pixmap)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+ Pixmap pixmap;
+ Picture picture;
+ int format;
+
+ /* FIXME this pixmap->picture dance seems silly, investigate further. TODO */
+ switch (depth) {
+ case 8:
+ format = PictStandardA8;
+ break;
+ case 32:
+ format = PictStandardARGB32;
+ break;
+ default:
+ assert(0);
+ }
+
+ pixmap = create_pixmap(charts, width, height, depth);
+ picture = XRenderCreatePicture(xserver->display, pixmap, XRenderFindStandardFormat(xserver->display, format), attr_mask, attr);
+
+ if (res_pixmap) {
+ *res_pixmap = pixmap;
+ } else {
+ XFreePixmap(xserver->display, pixmap);
+ }
+
+ return picture;
+}
+
+
+/* convenience helper for creating a filled picture, supply res_pixmap to keep a reference to the pixmap drawable. */
+static Picture create_picture_fill(vwm_charts_t *charts, unsigned width, unsigned height, unsigned depth, unsigned long attrs_mask, XRenderPictureAttributes *attrs, XRenderColor *color, Pixmap *res_pixmap)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+ Picture picture;
+
+ picture = create_picture(charts, width, height, depth, attrs_mask, attrs, res_pixmap);
+ XRenderFillRectangle(xserver->display, PictOpSrc, picture, color, 0, 0, width, height);
+
+ return picture;
+}
+
+
+/* initialize charts system */
+vwm_charts_t * vwm_charts_create(vwm_xserver_t *xserver)
+{
+ vwm_charts_t *charts;
+ Pixmap bitmask;
+
+ charts = calloc(1, sizeof(vwm_charts_t));
+ if (!charts) {
+ VWM_PERROR("unable to allocate vwm_charts_t");
+ goto _err;
+ }
+
+ charts->xserver = xserver;
+ charts->prev_sampling_interval = charts->sampling_interval = 0.1f; /* default to 10Hz */
+
+ if (!vmon_init(&charts->vmon, VMON_FLAG_2PASS, CHART_VMON_SYS_WANTS, CHART_VMON_PROC_WANTS)) {
+ VWM_ERROR("unable to initialize libvmon");
+ goto _err_charts;
+ }
+
+ charts->vmon.proc_ctor_cb = vmon_ctor_cb;
+ charts->vmon.proc_dtor_cb = vmon_dtor_cb;
+ charts->vmon.sample_cb = sample_callback;
+ charts->vmon.sample_cb_arg = charts;
+ gettimeofday(&charts->this_sample, NULL);
+
+ /* get all the text and graphics stuff setup for charts */
+ charts->chart_font = XLoadQueryFont(xserver->display, CHART_FIXED_FONT);
+ if (!charts->chart_font) {
+ VWM_ERROR("unable to load chart font \"%s\"", CHART_FIXED_FONT);
+ goto _err_vmon;
+ }
+
+ /* create a GC for rendering the text using Xlib into the text chart stencils */
+ bitmask = create_pixmap(charts, 1, 1, CHART_MASK_DEPTH);
+ charts->text_gc = XCreateGC(xserver->display, bitmask, 0, NULL);
+ XSetForeground(xserver->display, charts->text_gc, WhitePixel(xserver->display, xserver->screen_num));
+ XFreePixmap(xserver->display, bitmask);
+
+ /* create some repeating source fill pictures for drawing through the text and graph stencils */
+ charts->text_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_visible_color, NULL);
+ charts->shadow_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_shadow_color, NULL);
+
+ charts->bg_fill = create_picture(charts, 1, CHART_ROW_HEIGHT, 32, CPRepeat, &pa_repeat, NULL);
+ XRenderFillRectangle(xserver->display, PictOpSrc, charts->bg_fill, &chart_bg_color, 0, 0, 1, CHART_ROW_HEIGHT);
+ XRenderFillRectangle(xserver->display, PictOpSrc, charts->bg_fill, &chart_div_color, 0, CHART_ROW_HEIGHT - 1, 1, 1);
+
+ charts->snowflakes_text_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_snowflakes_visible_color, NULL);
+ charts->grapha_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_grapha_color, NULL);
+ charts->graphb_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_graphb_color, NULL);
+
+ charts->finish_fill = create_picture(charts, 1, 2, 32, CPRepeat, &pa_repeat, NULL);
+ XRenderFillRectangle(xserver->display, PictOpSrc, charts->finish_fill, &chart_visible_color, 0, 0, 1, 1);
+ XRenderFillRectangle(xserver->display, PictOpSrc, charts->finish_fill, &chart_trans_color, 0, 1, 1, 1);
+
+ return charts;
+
+_err_vmon:
+ vmon_destroy(&charts->vmon);
+
+_err_charts:
+ free(charts);
+
+_err:
+ return NULL;
+}
+
+
+/* teardown charts system */
+void vwm_charts_destroy(vwm_charts_t *charts)
+{
+ /* TODO: free rest of stuff.. */
+ free(charts);
+}
+
+
+/* copies a row from src to dest */
+static void copy_row(vwm_charts_t *charts, vwm_chart_t *chart, int src_row, Picture src, int dest_row, Picture dest)
+{
+ XRenderComposite(charts->xserver->display, PictOpSrc, src, None, dest,
+ 0, src_row * CHART_ROW_HEIGHT, /* src */
+ 0, 0, /* mask */
+ 0, dest_row * CHART_ROW_HEIGHT, /* dest */
+ chart->width, CHART_ROW_HEIGHT); /* dimensions */
+}
+
+
+/* fills a row with the specified color */
+static void fill_row(vwm_charts_t *charts, vwm_chart_t *chart, int row, Picture pic, XRenderColor *color)
+{
+ XRenderFillRectangle(charts->xserver->display, PictOpSrc, pic, color,
+ 0, row * CHART_ROW_HEIGHT, /* dest */
+ chart->width, CHART_ROW_HEIGHT); /* dimensions */
+}
+
+
+/* copy what's below a given row up the specified amount within the same picture */
+static void shift_below_row_up(vwm_charts_t *charts, vwm_chart_t *chart, int row, Picture pic, int rows)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+
+ XRenderChangePicture(xserver->display, pic, CPRepeat, &pa_no_repeat);
+ XRenderComposite(xserver->display, PictOpSrc, pic, None, pic,
+ 0, (rows + row) * CHART_ROW_HEIGHT, /* src */
+ 0, 0, /* mask */
+ 0, row * CHART_ROW_HEIGHT, /* dest */
+ chart->width, (rows + chart->heirarchy_end) * CHART_ROW_HEIGHT - (rows + row) * CHART_ROW_HEIGHT); /* dimensions */
+ XRenderChangePicture(xserver->display, pic, CPRepeat, &pa_repeat);
+}
+
+
+/* moves what's below a given row up above it if specified, the row becoming discarded */
+static void snowflake_row(vwm_charts_t *charts, vwm_chart_t *chart, Picture pic, int copy, int row)
+{
+ VWM_TRACE("pid=%i chart=%p row=%i copy=%i heirarhcy_end=%i", chart->monitor->pid, chart, row, copy, chart->heirarchy_end);
+
+ if (copy)
+ copy_row(charts, chart, row, pic, 0, chart->tmp_picture);
+
+ shift_below_row_up(charts, chart, row, pic, 1);
+
+ if (copy) {
+ copy_row(charts, chart, 0, chart->tmp_picture, chart->heirarchy_end, pic);
+ } else {
+ fill_row(charts, chart, chart->heirarchy_end, pic, &chart_trans_color);
+ }
+}
+
+/* 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.
+ */
+
+
+static void shift_below_row_down(vwm_charts_t *charts, vwm_chart_t *chart, int row, Picture pic, int rows)
+{
+ XRenderComposite(charts->xserver->display, PictOpSrc, pic, None, pic,
+ 0, row * CHART_ROW_HEIGHT, /* src */
+ 0, 0, /* mask */
+ 0, (row + rows) * CHART_ROW_HEIGHT, /* dest */
+ chart->width, chart->height - (rows + row) * CHART_ROW_HEIGHT); /* dimensions */
+}
+
+
+/* shifts what's below a given row down a row, and clears the row, preparing it for populating */
+static void allocate_row(vwm_charts_t *charts, vwm_chart_t *chart, Picture pic, int row)
+{
+ VWM_TRACE("pid=%i chart=%p row=%i", chart->monitor->pid, chart, row);
+
+ shift_below_row_down(charts, chart, row, pic, 1);
+ fill_row(charts, chart, row, pic, &chart_trans_color);
+}
+
+
+/* shadow a row from the text layer in the shadow layer */
+static void shadow_row(vwm_charts_t *charts, vwm_chart_t *chart, int row)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+
+ /* 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(xserver->display, PictOpSrc, charts->shadow_fill, chart->text_picture, chart->shadow_picture,
+ 0, 0,
+ -1, row * CHART_ROW_HEIGHT,
+ 0, row * CHART_ROW_HEIGHT,
+ chart->visible_width, CHART_ROW_HEIGHT);
+
+ XRenderComposite(xserver->display, PictOpOver, charts->shadow_fill, chart->text_picture, chart->shadow_picture,
+ 0, 0,
+ 0, -1 + row * CHART_ROW_HEIGHT,
+ 0, row * CHART_ROW_HEIGHT,
+ chart->visible_width, CHART_ROW_HEIGHT);
+
+ XRenderComposite(xserver->display, PictOpOver, charts->shadow_fill, chart->text_picture, chart->shadow_picture,
+ 0, 0,
+ 1, row * CHART_ROW_HEIGHT,
+ 0, row * CHART_ROW_HEIGHT,
+ chart->visible_width, CHART_ROW_HEIGHT);
+
+ XRenderComposite(xserver->display, PictOpOver, charts->shadow_fill, chart->text_picture, chart->shadow_picture,
+ 0, 0,
+ 0, 1 + row * CHART_ROW_HEIGHT,
+ 0, row * CHART_ROW_HEIGHT,
+ chart->visible_width, CHART_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(CHART_ISTHREAD_ARGV) - 1;
+ items[0].chars = CHART_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(CHART_NOCOMM_ARGV) - 1;
+ items[nr].chars = CHART_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_charts_t *charts, vwm_chart_t *chart, int row, double a_fraction, double a_total, double b_fraction, double b_total)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+ int a_height, b_height;
+
+ /* compute the bar heights for this sample */
+ a_height = (a_fraction / a_total * (double)(CHART_ROW_HEIGHT - 1)); /* give up 1 pixel for the div */
+ b_height = (b_fraction / b_total * (double)(CHART_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(xserver->display, PictOpSrc, chart->grapha_picture, &chart_visible_color,
+ chart->phase, row * CHART_ROW_HEIGHT, /* dst x, y */
+ 1, a_height); /* dst w, h */
+ XRenderFillRectangle(xserver->display, PictOpSrc, chart->graphb_picture, &chart_visible_color,
+ chart->phase, row * CHART_ROW_HEIGHT + (CHART_ROW_HEIGHT - b_height) - 1, /* dst x, y */
+ 1, b_height); /* dst w, h */
+}
+
+
+/* helper for marking a finish line at the current phase for the specified row */
+static void mark_finish(vwm_charts_t *charts, vwm_chart_t *chart, int row)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+
+ XRenderComposite(xserver->display, PictOpSrc, charts->finish_fill, None, chart->grapha_picture,
+ 0, 0, /* src x, y */
+ 0, 0, /* mask x, y */
+ chart->phase, row * CHART_ROW_HEIGHT, /* dst x, y */
+ 1, CHART_ROW_HEIGHT - 1);
+ XRenderComposite(xserver->display, PictOpSrc, charts->finish_fill, None, chart->graphb_picture,
+ 0, 0, /* src x, y */
+ 0, 0, /* mask x, y */
+ chart->phase, row * CHART_ROW_HEIGHT, /* dst x, y */
+ 1, CHART_ROW_HEIGHT - 1);
+}
+
+
+/* helper for drawing a proc's argv @ specified x offset and row on the chart */
+static void print_argv(vwm_charts_t *charts, vwm_chart_t *chart, int x, int row, vmon_proc_t *proc)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+ XTextItem items[CHART_MAX_ARGC];
+ int nr_items;
+
+ argv2xtext(proc, items, NELEMS(items), &nr_items);
+ XDrawText(xserver->display, chart->text_pixmap, charts->text_gc,
+ x, (row + 1) * CHART_ROW_HEIGHT - 3, /* dst x, y */
+ items, nr_items);
+}
+
+
+/* draws proc in a row of the process heirarchy */
+static void draw_heirarchy_row(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_t *proc, int depth, int row, int heirarchy_changed)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+ vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT];
+ vmon_proc_t *child;
+ char str[256];
+ int str_len, str_width;
+
+/* 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 (!chart->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(xserver->display, PictOpSrc, chart->text_picture, &chart_trans_color,
+ 0, row * CHART_ROW_HEIGHT, /* dst x, y */
+ chart->width, CHART_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(charts->chart_font, str, str_len);
+
+ /* the process' comm label indented according to depth, followed with their respective argv's */
+ print_argv(charts, chart, depth * (CHART_ROW_HEIGHT / 2), row, proc);
+
+ /* 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(xserver->display, PictOpSrc, chart->text_picture, &chart_trans_color,
+ chart->visible_width - str_width, row * CHART_ROW_HEIGHT, /* dst x,y */
+ chart->width - (chart->visible_width - str_width), CHART_ROW_HEIGHT); /* dst w,h */
+
+ XDrawString(xserver->display, chart->text_pixmap, charts->text_gc,
+ chart->visible_width - str_width, (row + 1) * CHART_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 != chart->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) * CHART_ROW_HEIGHT;
+ int sub;
+
+ /* XXX: everything done in this code block only dirties _this_ process' row in the rendered chart output */
+
+ /* walk up the ancestors until reaching chart->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 != chart->monitor; ancestor = ancestor->parent) {
+ sub++;
+ bar_x = (depth - sub) * (CHART_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(xserver->display, chart->text_pixmap, charts->text_gc,
+ bar_x, bar_y - CHART_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) * (CHART_ROW_HEIGHT / 2) + 4;
+
+ /* if we're the last sibling, corner the tee by shortening the vbar */
+ if (proc == last_sibling) {
+ XDrawLine(xserver->display, chart->text_pixmap, charts->text_gc,
+ bar_x, bar_y - CHART_ROW_HEIGHT, /* dst x1, y1 */
+ bar_x, bar_y - 4); /* dst x2, y2 (vertical bar) */
+ } else {
+ XDrawLine(xserver->display, chart->text_pixmap, charts->text_gc,
+ bar_x, bar_y - CHART_ROW_HEIGHT, /* dst x1, y1 */
+ bar_x, bar_y); /* dst x2, y2 (vertical bar) */
+ }
+
+ XDrawLine(xserver->display, chart->text_pixmap, charts->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(charts, chart, row);
+}
+
+
+/* recursive draw function for "rest" of chart: the per-process rows (heirarchy, argv, state, wchan, pid...) */
+static void draw_chart_rest(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_t *proc, int *depth, int *row, int heirarchy_changed)
+{
+ vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT];
+ vwm_perproc_ctxt_t *proc_ctxt = proc->foo;
+ vmon_proc_t *child;
+ double utime_delta, stime_delta;
+
+ /* 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 chart.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 chart=%p depth=%i row=%i", chart, *depth, *row);
+ in_stale_entrypoint = in_stale = 1;
+ (*row) += count_rows(proc) - 1;
+ }
+
+ (*depth)++;
+ list_for_each_entry_prev(child, &proc->children, siblings) {
+ draw_chart_rest(charts, chart, child, depth, row, heirarchy_changed);
+ (*row)--;
+ }
+
+ if (!proc->is_thread) {
+ list_for_each_entry_prev(child, &proc->threads, threads) {
+ draw_chart_rest(charts, chart, 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);
+
+ mark_finish(charts, chart, (*row));
+
+ /* extract the row from the various layers */
+ snowflake_row(charts, chart, chart->grapha_picture, 1, (*row));
+ snowflake_row(charts, chart, chart->graphb_picture, 1, (*row));
+ snowflake_row(charts, chart, chart->text_picture, 0, (*row));
+ snowflake_row(charts, chart, chart->shadow_picture, 0, (*row));
+ chart->snowflakes_cnt++;
+
+ /* stamp the name (and whatever else we include) into chart.text_picture */
+ print_argv(charts, chart, 5, chart->heirarchy_end, proc);
+ shadow_row(charts, chart, chart->heirarchy_end);
+
+ chart->heirarchy_end--;
+
+ if (in_stale_entrypoint) {
+ VWM_TRACE("exited stale at chart=%p depth=%i row=%i", chart, *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(charts, chart, chart->grapha_picture, (*row));
+ allocate_row(charts, chart, chart->graphb_picture, (*row));
+ allocate_row(charts, chart, chart->text_picture, (*row));
+ allocate_row(charts, chart, chart->shadow_picture, (*row));
+
+ chart->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 != charts->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 = charts->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 = charts->total_delta;
+ } else {
+ stime_delta = proc_ctxt->stime_delta;
+ utime_delta = proc_ctxt->utime_delta;
+ }
+
+ draw_bars(charts, chart, *row, stime_delta, charts->total_delta, utime_delta, charts->total_delta);
+
+ draw_heirarchy_row(charts, chart, 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_chart_rest(charts, chart, child, depth, row, heirarchy_changed);
+ }
+ }
+
+ list_for_each_entry(child, &proc->children, siblings) {
+ draw_chart_rest(charts, chart, child, depth, row, heirarchy_changed);
+ }
+ (*depth)--;
+}
+
+
+/* recursive draw function entrypoint, draws the IOWait/Idle/HZ row, then enters draw_chart_rest() */
+static void draw_chart(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_t *proc, int *depth, int *row)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+ int heirarchy_changed = 0;
+ int str_len, str_width;
+ char str[256];
+
+/* CPU utilization graphs */
+ /* IOWait and Idle % @ row 0 */
+ draw_bars(charts, chart, *row, charts->iowait_delta, charts->total_delta, charts->idle_delta, charts->total_delta);
+
+ /* only draw the \/\/\ and HZ if necessary */
+ if (chart->redraw_needed || charts->prev_sampling_interval != charts->sampling_interval) {
+ snprintf(str, sizeof(str), "\\/\\/\\ %2iHz %n", (int)(charts->sampling_interval == INFINITY ? 0 : 1 / charts->sampling_interval), &str_len);
+ XRenderFillRectangle(xserver->display, PictOpSrc, chart->text_picture, &chart_trans_color,
+ 0, 0, /* dst x, y */
+ chart->visible_width, CHART_ROW_HEIGHT); /* dst w, h */
+ str_width = XTextWidth(charts->chart_font, str, str_len);
+ XDrawString(xserver->display, chart->text_pixmap, charts->text_gc,
+ chart->visible_width - str_width, CHART_ROW_HEIGHT - 3, /* dst x, y */
+ str, str_len);
+ shadow_row(charts, chart, 0);
+ }
+ (*row)++;
+
+ if (!chart->redraw_needed)
+ heirarchy_changed = proc_heirarchy_changed(proc);
+
+
+ draw_chart_rest(charts, chart, proc, depth, row, heirarchy_changed);
+
+ chart->redraw_needed = 0;
+
+ return;
+}
+
+
+/* consolidated version of chart text and graph rendering, makes snowflakes integration cleaner, this always gets called regardless of the charts mode */
+static void maintain_chart(vwm_charts_t *charts, vwm_chart_t *chart)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+ int row = 0, depth = 0;
+
+ if (!chart->monitor || !chart->monitor->stores[VMON_STORE_PROC_STAT])
+ return;
+
+ /* TODO:
+ * A 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 charts when the window resizes.
+ * However, simply resizing the charts is insufficient. Their contents need to be redrawn in the new dimensions, this is where it
+ * gets annoying. The current maintain/draw_chart makes assumptions about being run from the periodic vmon per-process callback.
+ * There needs to be a redraw mode added where draw_chart is just reconstructing the current state, which requires that we suppress
+ * the phase advance and in maintain_chart() and just enter draw_chart() to redraw everything for the same generation.
+ * So this probably requires some tweaking of draw_chart() as well as maintain_chart(). I want to be able tocall mainta_charts()
+ * 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.
+ */
+
+ chart->phase += (chart->width - 1); /* simply change this to .phase++ to scroll the other direction */
+ chart->phase %= chart->width;
+ XRenderFillRectangle(xserver->display, PictOpSrc, chart->grapha_picture, &chart_trans_color, chart->phase, 0, 1, chart->height);
+ XRenderFillRectangle(xserver->display, PictOpSrc, chart->graphb_picture, &chart_trans_color, chart->phase, 0, 1, chart->height);
+
+ /* recursively draw the monitored processes to the chart */
+ draw_chart(charts, chart, chart->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_charts_t *charts = sys_cb_arg;
+ vwm_chart_t *chart = proc_cb_arg;
+
+ VWM_TRACE("proc=%p chart=%p", proc, chart);
+
+ /* render the various always-updated charts, this is the component we do regardless of the charts mode and window visibility,
+ * essentially the incrementally rendered/historic components */
+ maintain_chart(charts, chart);
+
+ /* XXX TODO: we used to mark repaint as being needed if this chart's window was mapped, but
+ * since extricating charts from windows that's no longer convenient, and repaint is
+ * always performed after a sample. Make sure the repainting isn't costly when nothing
+ * charted is mapped (the case that code optimized)
+ */
+}
+
+
+/* return the composed height of the chart */
+static int vwm_chart_composed_height(vwm_charts_t *charts, vwm_chart_t *chart)
+{
+ int snowflakes = chart->snowflakes_cnt ? 1 + chart->snowflakes_cnt : 0; /* don't include the separator row if there are no snowflakes */
+
+ return MIN((chart->heirarchy_end + snowflakes) * CHART_ROW_HEIGHT, chart->visible_height);
+}
+
+
+/* reset snowflakes on the specified chart */
+void vwm_chart_reset_snowflakes(vwm_charts_t *charts, vwm_chart_t *chart)
+{
+ if (chart->snowflakes_cnt) {
+ chart->snowflakes_cnt = 0;
+ chart->redraw_needed = 1;
+ }
+}
+
+
+static void free_chart_pictures(vwm_charts_t *charts, vwm_chart_t *chart)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+
+ XRenderFreePicture(xserver->display, chart->grapha_picture);
+ XRenderFreePicture(xserver->display, chart->graphb_picture);
+ XRenderFreePicture(xserver->display, chart->tmp_picture);
+ XRenderFreePicture(xserver->display, chart->text_picture);
+ XFreePixmap(xserver->display, chart->text_pixmap);
+ XRenderFreePicture(xserver->display, chart->shadow_picture);
+ XRenderFreePicture(xserver->display, chart->picture);
+
+}
+
+
+static void copy_chart_pictures(vwm_charts_t *charts, vwm_chart_t *src, vwm_chart_t *dest)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+
+ if (!src->width)
+ return;
+
+ /* XXX: note the graph pictures are copied from their current phase in the x dimension */
+ XRenderComposite(xserver->display, PictOpSrc, src->grapha_picture, None, dest->grapha_picture,
+ src->phase, 0, /* src x, y */
+ 0, 0, /* mask x, y */
+ dest->phase, 0, /* dest x, y */
+ src->width, src->height);
+ XRenderComposite(xserver->display, PictOpSrc, src->graphb_picture, None, dest->graphb_picture,
+ src->phase, 0, /* src x, y */
+ 0, 0, /* mask x, y */
+ dest->phase, 0, /* dest x, y */
+ src->width, src->height);
+ XRenderComposite(xserver->display, PictOpSrc, src->text_picture, None, dest->text_picture,
+ 0, 0, /* src x, y */
+ 0, 0, /* mask x, y */
+ 0, 0, /* dest x, y */
+ src->width, src->height);
+ XRenderComposite(xserver->display, PictOpSrc, src->shadow_picture, None, dest->shadow_picture,
+ 0, 0, /* src x, y */
+ 0, 0, /* mask x, y */
+ 0, 0, /* dest x, y */
+ src->width, src->height);
+ XRenderComposite(xserver->display, PictOpSrc, src->picture, None, dest->picture,
+ 0, 0, /* src x, y */
+ 0, 0, /* mask x, y */
+ 0, 0, /* dest x, y */
+ src->width, src->height);
+}
+
+
+/* (re)size the specified chart's visible dimensions */
+int vwm_chart_set_visible_size(vwm_charts_t *charts, vwm_chart_t *chart, int width, int height)
+{
+ if (width != chart->visible_width || height != chart->visible_height)
+ chart->redraw_needed = 1;
+
+ /* TODO error handling: if a create failed but we had an chart, free whatever we created and leave it be, succeed.
+ * if none existed it's a hard error and we must propagate it. */
+
+ /* if larger than the charts currently are, enlarge them */
+ if (width > chart->width || height > chart->height) {
+ vwm_chart_t existing = *chart;
+
+ chart->width = MAX(chart->width, MAX(width, CHART_GRAPH_MIN_WIDTH));
+ chart->height = MAX(chart->height, MAX(height, CHART_GRAPH_MIN_HEIGHT));
+
+ chart->grapha_picture = create_picture_fill(charts, chart->width, chart->height, CHART_MASK_DEPTH, CPRepeat, &pa_repeat, &chart_trans_color, NULL);
+ chart->graphb_picture = create_picture_fill(charts, chart->width, chart->height, CHART_MASK_DEPTH, CPRepeat, &pa_repeat, &chart_trans_color, NULL);
+ chart->tmp_picture = create_picture(charts, chart->width, CHART_ROW_HEIGHT, CHART_MASK_DEPTH, 0, NULL, NULL);
+
+ /* keep the text_pixmap reference around for XDrawText usage */
+ chart->text_picture = create_picture_fill(charts, chart->width, chart->height, CHART_MASK_DEPTH, 0, NULL, &chart_trans_color, &chart->text_pixmap);
+
+ chart->shadow_picture = create_picture_fill(charts, chart->width, chart->height, CHART_MASK_DEPTH, 0, NULL, &chart_trans_color, NULL);
+ chart->picture = create_picture(charts, chart->width, chart->height, 32, 0, NULL, NULL);
+
+ copy_chart_pictures(charts, &existing, chart);
+ free_chart_pictures(charts, &existing);
+ }
+
+ chart->visible_width = width;
+ chart->visible_height = height;
+
+ return 1;
+}
+
+
+/* create an chart and start monitoring for the supplied pid */
+vwm_chart_t * vwm_chart_create(vwm_charts_t *charts, int pid, int width, int height)
+{
+ vwm_chart_t *chart;
+
+ chart = calloc(1, sizeof(vwm_chart_t));
+ if (!chart) {
+ VWM_PERROR("Unable to allocate vwm_chart_t");
+ goto _err;
+ }
+
+ /* 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 */
+ chart->monitor = vmon_proc_monitor(&charts->vmon, NULL, pid, VMON_WANT_PROC_INHERIT, (void (*)(vmon_t *, void *, vmon_proc_t *, void *))proc_sample_callback, chart);
+ if (!chart->monitor) {
+ VWM_ERROR("Unable to establish proc monitor");
+ goto _err_free;
+ }
+
+ /* 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 */
+ chart->heirarchy_end = 1 + count_rows(chart->monitor);
+ chart->gen_last_composed = -1;
+
+ if (!vwm_chart_set_visible_size(charts, chart, width, height)) {
+ VWM_ERROR("Unable to set initial chart size");
+ goto _err_unmonitor;
+ }
+
+ return chart;
+
+_err_unmonitor:
+ vmon_proc_unmonitor(&charts->vmon, chart->monitor, (void (*)(vmon_t *, void *, vmon_proc_t *, void *))proc_sample_callback, chart);
+
+_err_free:
+ free(chart);
+_err:
+ return NULL;
+}
+
+
+/* stop monitoring and destroy the supplied chart */
+void vwm_chart_destroy(vwm_charts_t *charts, vwm_chart_t *chart)
+{
+ vmon_proc_unmonitor(&charts->vmon, chart->monitor, (void (*)(vmon_t *, void *, vmon_proc_t *, void *))proc_sample_callback, chart);
+ free_chart_pictures(charts, chart);
+ free(chart);
+}
+
+
+/* this composes the maintained chart into the base chart 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_chart_compose(vwm_charts_t *charts, vwm_chart_t *chart, XserverRegion *res_damaged_region)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+ int height;
+
+ if (!chart->width || !chart->height)
+ return;
+
+ if (chart->gen_last_composed == chart->monitor->generation)
+ return; /* noop if no sampling occurred since last compose */
+
+ chart->gen_last_composed = chart->monitor->generation; /* remember this generation */
+
+ //VWM_TRACE("composing %p", chart);
+
+ height = vwm_chart_composed_height(charts, chart);
+
+ /* fill the chart picture with the background */
+ XRenderComposite(xserver->display, PictOpSrc, charts->bg_fill, None, chart->picture,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ chart->visible_width, height);
+
+ /* draw the graphs into the chart through the stencils being maintained by the sample callbacks */
+ XRenderComposite(xserver->display, PictOpOver, charts->grapha_fill, chart->grapha_picture, chart->picture,
+ 0, 0,
+ chart->phase, 0,
+ 0, 0,
+ chart->visible_width, height);
+ XRenderComposite(xserver->display, PictOpOver, charts->graphb_fill, chart->graphb_picture, chart->picture,
+ 0, 0,
+ chart->phase, 0,
+ 0, 0,
+ chart->visible_width, height);
+
+ /* draw the shadow into the chart picture using a translucent black source drawn through the shadow mask */
+ XRenderComposite(xserver->display, PictOpOver, charts->shadow_fill, chart->shadow_picture, chart->picture,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ chart->visible_width, height);
+
+ /* render chart text into the chart picture using a white source drawn through the chart text as a mask, on top of everything */
+ XRenderComposite(xserver->display, PictOpOver, charts->text_fill, chart->text_picture, chart->picture,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ chart->visible_width, (chart->heirarchy_end * CHART_ROW_HEIGHT));
+
+ XRenderComposite(xserver->display, PictOpOver, charts->snowflakes_text_fill, chart->text_picture, chart->picture,
+ 0, 0,
+ 0, chart->heirarchy_end * CHART_ROW_HEIGHT,
+ 0, chart->heirarchy_end * CHART_ROW_HEIGHT,
+ chart->visible_width, height - (chart->heirarchy_end * CHART_ROW_HEIGHT));
+
+ /* damage the window to ensure the updated chart is drawn (TODO: this can be done more selectively/efficiently) */
+ if (res_damaged_region) {
+ XRectangle damage = {};
+
+ damage.width = chart->visible_width;
+ damage.height = chart->visible_height;
+
+ *res_damaged_region = XFixesCreateRegion(xserver->display, &damage, 1);
+ }
+}
+
+
+/* render the chart into a picture at the specified coordinates and dimensions */
+void vwm_chart_render(vwm_charts_t *charts, vwm_chart_t *chart, int op, Picture dest, int x, int y, int width, int height)
+{
+ vwm_xserver_t *xserver = charts->xserver;
+
+ if (!chart->width || !chart->height)
+ return;
+
+ /* draw the monitoring chart atop dest, note we stay within the window borders here. */
+ XRenderComposite(xserver->display, op, chart->picture, None, dest,
+ 0, 0, 0, 0, /* src x,y, maxk x, y */
+ x, /* dst x */
+ y, /* dst y */
+ width, MIN(vwm_chart_composed_height(charts, chart), height) /* FIXME */); /* w, h */
+}
+
+
+/* increase the sample rate relative to current using the table of intervals */
+void vwm_charts_rate_increase(vwm_charts_t *charts)
+{
+ int i;
+
+ assert(charts);
+
+ for (i = 0; i < NELEMS(sampling_intervals); i++) {
+ if (sampling_intervals[i] < charts->sampling_interval) {
+ charts->sampling_interval = sampling_intervals[i];
+ break;
+ }
+ }
+}
+
+
+/* decrease the sample rate relative to current using the table of intervals */
+void vwm_charts_rate_decrease(vwm_charts_t *charts)
+{
+ int i;
+
+ assert(charts);
+
+ for (i = NELEMS(sampling_intervals) - 1; i >= 0; i--) {
+ if (sampling_intervals[i] > charts->sampling_interval) {
+ charts->sampling_interval = sampling_intervals[i];
+ break;
+ }
+ }
+}
+
+
+/* set an arbitrary sample rate rather than using one of the presets, 0 to pause */
+void vwm_charts_rate_set(vwm_charts_t *charts, unsigned hertz)
+{
+ assert(charts);
+
+ /* XXX: note floating point divide by 0 simply results in infinity */
+ charts->sampling_interval = 1.0f / (float)hertz;
+}
+
+
+/* convenience 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;
+}
+
+
+/* update the charts if necessary, return if updating occurred, and duration before another update needed in *desired_delay */
+int vwm_charts_update(vwm_charts_t *charts, int *desired_delay)
+{
+ float this_delta = 0.0f;
+ int ret = 0;
+
+ gettimeofday(&charts->maybe_sample, NULL);
+ if ((charts->sampling_interval == INFINITY && !charts->sampling_paused) || /* XXX this is kind of a kludge to get the 0 Hz indicator drawn before pausing */
+ (charts->sampling_interval != INFINITY && ((this_delta = delta(&charts->maybe_sample, &charts->this_sample)) >= charts->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 (charts->sampling_interval < INFINITY &&
+ charts->sampling_interval <= charts->prev_sampling_interval &&
+ this_delta >= (charts->sampling_interval * 1.5)) {
+
+ /* require > 1 contiguous drops before lowering the rate, tolerates spurious one-off stalls */
+ if (++charts->contiguous_drops > 2)
+ vwm_charts_rate_decrease(charts);
+ } else {
+ charts->contiguous_drops = 0;
+ }
+
+ /* age the sys-wide sample data into "last" variables, before the new sample overwrites them. */
+ charts->last_sample = charts->this_sample;
+ charts->this_sample = charts->maybe_sample;
+ if ((sys_stat = charts->vmon.stores[VMON_STORE_SYS_STAT])) {
+ charts->last_user_cpu = sys_stat->user;
+ charts->last_system_cpu = sys_stat->system;
+ charts->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;
+
+ charts->last_idle = sys_stat->idle;
+ charts->last_iowait = sys_stat->iowait;
+ }
+
+ ret = vmon_sample(&charts->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 */
+
+ charts->sampling_paused = (charts->sampling_interval == INFINITY);
+ charts->prev_sampling_interval = charts->sampling_interval;
+ }
+
+ /* TODO: make some effort to compute how long to sleep, but this is perfectly fine for now. */
+ *desired_delay = charts->sampling_interval == INFINITY ? -1 : charts->sampling_interval * 300.0;
+
+ return ret;
+}
© All Rights Reserved