summaryrefslogtreecommitdiff
path: root/src/libvmon/vmon.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libvmon/vmon.c')
-rw-r--r--src/libvmon/vmon.c869
1 files changed, 552 insertions, 317 deletions
diff --git a/src/libvmon/vmon.c b/src/libvmon/vmon.c
index 83d78c4..b4c008c 100644
--- a/src/libvmon/vmon.c
+++ b/src/libvmon/vmon.c
@@ -5,10 +5,10 @@
* This has been written specifically for use in vwm, my window manager,
* but includes some facilities for other uses.
*
- * Copyright (c) 2012-2017 Vito Caputo - <vcaputo@pengaru.com>
+ * Copyright (c) 2012-2025 Vito Caputo - <vcaputo@pengaru.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
+ * it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
@@ -20,6 +20,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
@@ -51,6 +52,8 @@ typedef enum _sample_ret_t {
/* some private convenience functions */
static void try_free(void **ptr)
{
+ assert(ptr);
+
if ((*ptr)) {
free((*ptr));
(*ptr) = NULL;
@@ -60,6 +63,8 @@ static void try_free(void **ptr)
static void try_close(int *fd)
{
+ assert(fd);
+
if ((*fd) != -1) {
close((*fd));
(*fd) = -1;
@@ -69,6 +74,8 @@ static void try_close(int *fd)
static void try_closedir(DIR **dir)
{
+ assert(dir);
+
if ((*dir)) {
closedir((*dir));
(*dir) = NULL;
@@ -78,6 +85,8 @@ static void try_closedir(DIR **dir)
static ssize_t try_pread(int fd, void *buf, size_t count, off_t offset)
{
+ assert(buf);
+
if (fd != -1) {
return pread(fd, buf, count, offset);
} else {
@@ -89,10 +98,14 @@ static ssize_t try_pread(int fd, void *buf, size_t count, off_t offset)
/* this does a copy from src to dest
* sets the bit specified by changed_pos in the bitmap *changed if what was copied to src was different from what was already there */
-static void memcmpcpy(void *dest, const void *src, size_t n, char *changed, int changed_pos)
+static void memcmpcpy(void *dest, const void *src, size_t n, char *changed, unsigned changed_pos)
{
size_t i = 0;
+ assert(dest);
+ assert(src);
+ assert(changed);
+
/* if the changed bit is set on entry we don't execute the comparison-copy */
if (!BITTEST(changed, changed_pos)) {
/* a simple slow compare and copy loop, break out if we've found a change */
@@ -119,30 +132,40 @@ typedef enum _vmon_load_flags_t {
} vmon_load_flags_t;
/* we enlarge *alloc if necessary, but never shrink it. changed[changed_pos] bit is set if a difference is detected, supply LOAD_FLAGS_NOTRUNCATE if we don't want empty contents to truncate last-known data */
-static char * load_contents_fd(vmon_t *vmon, vmon_char_array_t *array, int fd, vmon_load_flags_t flags, char *changed, int changed_pos)
+static int load_contents_fd(vmon_t *vmon, vmon_char_array_t *array, int fd, vmon_load_flags_t flags, char *changed, unsigned changed_pos)
{
- int total = 0, len, alloc_len = array->alloc_len;
- char *alloc = array->array, *tmp;
+ size_t total = 0;
+ ssize_t len;
+ assert(vmon);
+ assert(array);
+ assert(changed);
+
+ if (fd < 0) /* no use attempting the pread() on -1 fds */
+ return 0;
+
+ /* FIXME: this is used to read proc files, you can't actually read proc files iteratively
+ * without race conditions; it needs to be all done as a single read.
+ */
while ((len = pread(fd, vmon->buf, sizeof(vmon->buf), total)) > 0) {
- if (total + len > alloc_len) {
- /* realloc to accomodate the new total */
- tmp = realloc(alloc, total + len);
+ size_t newsize = total + len;
+
+ if (newsize > array->alloc_len) {
+ char *tmp;
+
+ tmp = realloc(array->array, newsize);
if (!tmp)
- return NULL;
+ return -ENOMEM;
- alloc_len = total + len;
- alloc = tmp;
+ array->array = tmp;
+ array->alloc_len = newsize;
}
- memcmpcpy(&alloc[total], vmon->buf, len, changed, changed_pos);
+ memcmpcpy(&array->array[total], vmon->buf, len, changed, changed_pos);
total += len;
}
- array->array = alloc;
- array->alloc_len = alloc_len;
-
/* if we read something or didn't encounter an error, store the new total */
if (total || (len == 0 && !(flags & LOAD_FLAGS_NOTRUNCATE))) {
/* if the new length differs ensure the changed bit is set */
@@ -152,7 +175,7 @@ static char * load_contents_fd(vmon_t *vmon, vmon_char_array_t *array, int fd, v
array->len = total;
}
- return alloc;
+ return 0;
}
@@ -163,6 +186,10 @@ static int openf(vmon_t *vmon, int flags, DIR *dir, char *fmt, ...)
va_list va_arg;
char buf[4096];
+ assert(vmon);
+ assert(dir);
+ assert(fmt);
+
va_start(va_arg, fmt);
vsnprintf(buf, sizeof(buf), fmt, va_arg); /* XXX accepting the possibility of truncating the path */
va_end(va_arg);
@@ -180,6 +207,10 @@ static DIR * opendirf(vmon_t *vmon, DIR *dir, char *fmt, ...)
va_list va_arg;
char buf[4096];
+ assert(vmon);
+ assert(dir);
+ assert(fmt);
+
va_start(va_arg, fmt);
vsnprintf(buf, sizeof(buf), fmt, va_arg); /* XXX accepting the possibility of truncating the path */
va_end(va_arg);
@@ -193,164 +224,252 @@ static DIR * opendirf(vmon_t *vmon, DIR *dir, char *fmt, ...)
/* enlarge an array by the specified amount */
-static int grow_array(vmon_char_array_t *array, int amount)
+static int grow_array(vmon_char_array_t *array, size_t amount)
{
char *tmp;
+ assert(array);
+
tmp = realloc(array->array, array->alloc_len + amount);
if (!tmp)
- return 0;
+ return -ENOMEM;
array->alloc_len += amount;
array->array = tmp;
- return 1;
+ return 0;
}
/* load the contents of a symlink, dir is not optional */
#define READLINKF_GROWINIT 10
#define READLINKF_GROWBY 2
-static char * readlinkf(vmon_t *vmon, vmon_char_array_t *array, DIR *dir, char *fmt, ...)
+static int readlinkf(vmon_t *vmon, vmon_char_array_t *array, DIR *dir, char *fmt, ...)
{
- va_list va_arg;
- int len;
char buf[4096];
+ va_list va_arg;
+ ssize_t len;
+
+ assert(vmon);
+ assert(array);
+ assert(dir);
+ assert(fmt);
va_start(va_arg, fmt);
vsnprintf(buf, sizeof(buf), fmt, va_arg);
va_end(va_arg);
- if (!array->array && !grow_array(array, READLINKF_GROWINIT))
- goto _fail;
+ if (!array->array && grow_array(array, READLINKF_GROWINIT) < 0)
+ return -ENOMEM;
do {
len = readlinkat(dirfd(dir), buf, array->array, (array->alloc_len - 1));
- } while (len != -1 && len == (array->alloc_len - 1) && (len = grow_array(array, READLINKF_GROWBY)));
+ } while (len != -1 && len == (array->alloc_len - 1) && (len = grow_array(array, READLINKF_GROWBY)) >= 0);
- if (len <= 0)
- goto _fail;
+ if (len < 0)
+ return -errno;
array->len = len;
array->array[array->len] = '\0';
- return array->array;
-
-_fail:
- return NULL;
+ return 0;
}
/* here starts private per-process samplers and other things like following children implementation etc. */
-/* sample stat process stats */
-typedef enum _vmon_proc_stat_fsm_t {
-#define VMON_ENUM_PARSER_STATES
-#include "defs/proc_stat.def"
-} vmon_proc_stat_fsm_t;
-
-static sample_ret_t proc_sample_stat(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_stat_t **store)
+/* simple helper for installing callbacks on the callback lists, currently only used for the per-process sample callbacks */
+/* will not install the same callback function & arg combination more than once, and will not install NULL-functioned callbacks at all! */
+static int maybe_install_proc_callback(vmon_t *vmon, list_head_t *callbacks, void (*func)(vmon_t *, void *, vmon_proc_t *, void *), void *arg)
{
- int changes = 0;
- int i, len, total = 0;
- char *arg;
- int argn, prev_argc;
- vmon_proc_stat_fsm_t state = VMON_PARSER_STATE_PROC_STAT_PID;
-#define VMON_PREPARE_PARSER
-#include "defs/proc_stat.def"
+ assert(vmon);
+ assert(callbacks);
+ assert(!arg || func);
- if (!proc) { /* dtor */
- try_close(&(*store)->comm_fd);
- try_free((void **)&(*store)->comm);
- try_close(&(*store)->cmdline_fd);
- try_free((void **)&(*store)->cmdline);
- try_close(&(*store)->wchan_fd);
- try_close(&(*store)->stat_fd);
- try_free((void **)&(*store)->exe);
+ if (func) {
+ vmon_proc_callback_t *cb;
- return DTOR_FREE;
+ list_for_each_entry(cb, callbacks, callbacks) {
+ if (cb->func == func && cb->arg == arg)
+ break;
+ }
+
+ if (&cb->callbacks == callbacks) {
+ cb = calloc(1, sizeof(vmon_proc_callback_t));
+ if (!cb)
+ return 0;
+
+ cb->func = func;
+ cb->arg = arg;
+ list_add_tail(&cb->callbacks, callbacks);
+ }
}
-/* _retry: */
- if (!(*store)) { /* ctor */
+ return 1;
+}
- *store = calloc(1, sizeof(vmon_proc_stat_t));
- if (proc->is_thread) {
- (*store)->comm_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/task/%i/comm", proc->pid, proc->pid);
- (*store)->cmdline_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/task/%i/cmdline", proc->pid, proc->pid);
- (*store)->wchan_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/task/%i/wchan", proc->pid, proc->pid);
- (*store)->stat_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/task/%i/stat", proc->pid, proc->pid);
- } else {
- (*store)->comm_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/comm", proc->pid);
- (*store)->cmdline_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/cmdline", proc->pid);
- (*store)->wchan_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/wchan", proc->pid);
- (*store)->stat_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/stat", proc->pid);
- }
+/* helper for searching for the process in the process array, specify NULL to find a free slot */
+static int find_proc_in_array(vmon_t *vmon, vmon_proc_t *proc, int hint)
+{
+ int ret = -1;
- /* initially everything is considered changed */
- memset((*store)->changed, 0xff, sizeof((*store)->changed));
+ assert(vmon);
+
+ if (hint >= 0 && hint < vmon->array_allocated_nr && vmon->array[hint] == proc) {
+ /* the hint was accurate, bypass the search */
+ ret = hint;
} else {
- /* clear the entire changed bitmap */
- memset((*store)->changed, 0, sizeof((*store)->changed));
+ int i;
+
+ /* search for the entry in the array */
+ for (i = 0; i < vmon->array_allocated_nr; i++) {
+ if (vmon->array[i] == proc) {
+ ret = i;
+ break;
+ }
+ }
}
- /* XXX TODO: integrate load_contents_fd() calls into changes++ maintenance */
- /* /proc/$pid/comm */
- load_contents_fd(vmon, &(*store)->comm, (*store)->comm_fd, LOAD_FLAGS_NOTRUNCATE, (*store)->changed, VMON_PROC_STAT_COMM);
+ if (!proc && ret == -1) {
+ vmon_proc_t **tmp;
- /* /proc/$pid/cmdline */
- load_contents_fd(vmon, &(*store)->cmdline, (*store)->cmdline_fd, LOAD_FLAGS_NOTRUNCATE, (*store)->changed, VMON_PROC_STAT_CMDLINE);
- for (prev_argc = (*store)->argc, (*store)->argc = 0, i = 0; i < (*store)->cmdline.len; i++) {
- if (!(*store)->cmdline.array[i])
- (*store)->argc++;
+ /* enlarge the array */
+ tmp = realloc(vmon->array, (vmon->array_allocated_nr + VMON_ARRAY_GROWBY) * sizeof(vmon_proc_t *));
+ if (tmp) {
+ memset(&tmp[vmon->array_allocated_nr], 0, VMON_ARRAY_GROWBY * sizeof(vmon_proc_t *));
+ ret = vmon->array_hint_free = vmon->array_allocated_nr;
+ vmon->array_allocated_nr += VMON_ARRAY_GROWBY;
+ vmon->array = tmp;
+ } /* XXX TODO: handle realloc failure */
}
- /* if the cmdline has changed, allocate argv array and store ptrs to the fields within it */
- if (BITTEST((*store)->changed, VMON_PROC_STAT_CMDLINE)) {
- if (prev_argc != (*store)->argc) {
- try_free((void **)&(*store)->argv); /* XXX could realloc */
- (*store)->argv = calloc(1, (*store)->argc * sizeof(char *));
- }
+ return ret;
+}
- for (argn = 0, arg = (*store)->cmdline.array, i = 0; i < (*store)->cmdline.len; i++) {
- if (!(*store)->cmdline.array[i]) {
- (*store)->argv[argn++] = arg;
- arg = &(*store)->cmdline.array[i + 1];
+
+/* this is the private variant that allows providing a parent, which libvmon needs for constructing hierarchies, but callers shouldn't be doing themselves */
+static vmon_proc_t * proc_monitor(vmon_t *vmon, vmon_proc_t *parent, int pid, vmon_proc_wants_t wants, void (*sample_cb)(vmon_t *, void *, vmon_proc_t *, void *), void *sample_cb_arg)
+{
+ vmon_proc_t *proc;
+ int hash = (pid % VMON_HTAB_SIZE), i;
+ int is_thread = (wants & VMON_INTERNAL_PROC_IS_THREAD) ? 1 : 0;
+
+ assert(vmon);
+ assert(!sample_cb_arg || sample_cb);
+
+ wants &= ~VMON_INTERNAL_PROC_IS_THREAD;
+
+ if (pid < 0)
+ return NULL;
+
+ list_for_each_entry(proc, &vmon->htab[hash], bucket) {
+ /* search for the process to see if it's already monitored, we allow threads to exist with the same pid hence the additional is_thread comparison */
+ if (proc->pid == pid && proc->is_thread == is_thread) {
+ if (!maybe_install_proc_callback(vmon, &proc->sample_callbacks, sample_cb, sample_cb_arg))
+ return NULL;
+
+ proc->wants = wants; /* we can alter wants this way, though it clearly needs more consideration XXX */
+
+ if (parent) {
+ /* This is a predicament. If the top-level (externally-established) process monitor wins the race, the process has no parent and is on the vmon->processes list.
+ * Then the follow_children-established monitor comes along and wants to assign a parent, this isn't such a big deal, and we should be able to permit it, however,
+ * we're in the process of iterating the top-level processes list when we run the follow_children() sampler of a descendant which happens to also be the parent.
+ * We can't simply remove the process from the top-level processes list mid-iteration and stick it on the children list of the parent, the iterator could wind up
+ * following the new pointer into the parents children.
+ *
+ * What I'm doing for now is allowing the assignment of a parent when there is no parent, but we don't perform the vmon->processes to parent->children migration
+ * until the top-level sample_siblings() function sees the node with the parent. At that point, the node will migrate to the parent's children list. Since
+ * this particular migration happens immediately within the same sample, it
+ */
+ if (!proc->parent) {
+ /* if a parent was supplied, and there is no current parent, the process is top-level currently by external callers, but now appears to be a child of something as well,
+ * so in this scenario, we'll remove it from the top-level siblings list, and make it a child of the new parent. I don't think there needs to be concern about its being a thread here. */
+ /* Note we can't simply move the process from its current processes list and add it to the supplied parent's children list, as that would break the iterator above us at the top-level, so
+ * we must defer the migration until the processes iterator context can do it for us - but this is tricky because we're in the middle of traversing our hierarchy and this process may
+ * be in a critical is_new state which must be realized this sample at its location in the hierarchy for correctness, there will be no reappearance of that critical state in the correct
+ * tree position for users like vwm. */
+ /* the VMON_FLAG_2PASS flag has been introduced for users like vwm */
+ proc->parent = parent;
+ proc->refcnt++;
+ }
+#if 0
+ else if (parent != proc->parent) {
+ /* We're switching parents; this used to be considered unexpected, but then vmon happened and it monitors the whole tree from PID1 down.
+ * PID1 is special in that it inherits orphans, so we can already be monitoring a child of an exited parent, and here PID1's children
+ * following is looking up a newly inherited orphan, which reaches here finding proc with a non-NULL, but different parent.
+ * Note the introduction of PR_SET_CHILD_SUBREAPER has made this no longer limited to PID1 either.
+ */
+ /* now, we can't simply switch parents in a single step... since the is_stale=1 state must be seen by the front-end before we can
+ * unlink it from the structure in a subsequent sample. So instead, we should be queueing an adoption by the new parent, but
+ * for the time being we can just suppress the refcnt bump and let the adoptive parent reestablish the monitor anew after
+ * runs its course under its current parent.
+ */
+ }
+#endif
+ } else {
+ proc->refcnt++;
}
+
+ return proc;
}
}
- /* /proc/$pid/wchan */
- load_contents_fd(vmon, &(*store)->wchan, (*store)->wchan_fd, LOAD_FLAGS_NOTRUNCATE, (*store)->changed, VMON_PROC_STAT_WCHAN);
-
- /* /proc/$pid/exe */
- if ((*store)->cmdline.len) /* kernel threads have no cmdline, and always fail readlinkf() on exe, skip readlinking the exe for them using this heuristic */
- readlinkf(vmon, &(*store)->exe, vmon->proc_dir, "%i/exe", proc->pid);
+ proc = (vmon_proc_t *)calloc(1, sizeof(vmon_proc_t));
+ if (proc == NULL)
+ return NULL; /* TODO: report an error */
- /* XXX TODO: there's a race between discovering comm_len from /proc/$pid/comm and applying it in the parsing of /proc/$pid/stat, detect the race
- * scenario and retry the sample when detected by goto _retry (see commented _retry label above) */
+ proc->pid = pid;
+ proc->wants = wants;
+ proc->generation = vmon->generation;
+ proc->refcnt = 1;
+ proc->is_new = 1; /* newly created process */
+ proc->is_thread = is_thread;
+ proc->parent = parent;
+ INIT_LIST_HEAD(&proc->sample_callbacks);
+ INIT_LIST_HEAD(&proc->children);
+ INIT_LIST_HEAD(&proc->siblings);
+ INIT_LIST_HEAD(&proc->threads);
- /* read in stat and parse it assigning the stat members accordingly */
- while ((len = try_pread((*store)->stat_fd, vmon->buf, sizeof(vmon->buf), total)) > 0) {
- total += len;
+ if (!maybe_install_proc_callback(vmon, &proc->sample_callbacks, sample_cb, sample_cb_arg)) {
+ free(proc);
+ return NULL;
+ }
- for (i = 0; i < len; i++) {
- /* parse the fields from the file, stepping through... */
- _p.input = vmon->buf[i];
- switch (state) {
-#define VMON_PARSER_DELIM ' ' /* TODO XXX eliminate the need for this, I want the .def's to include all the data format knowledge */
-#define VMON_IMPLEMENT_PARSER
-#include "defs/proc_stat.def"
- default:
- /* we're finished parsing once we've fallen off the end of the symbols */
- goto _out; /* this saves us the EOF read syscall */
- }
+ if (parent) {
+ /* if a parent is specified, attach this process to the parent's children or threads with its siblings */
+ if (is_thread) {
+ list_add_tail(&proc->threads, &parent->threads);
+ parent->threads_changed = 1;
+ parent->is_threaded = 1;
+ parent->n_current_threads++;
+ } else {
+ list_add_tail(&proc->siblings, &parent->children);
+ parent->children_changed = 1;
}
+ } else {
+ /* if no parent is specified, this is a toplevel process, attach it to the vmon->processes list with its siblings */
+ /* XXX ignoring is_thread if no parent is specified, it shouldn't occur, unless I want to support external callers monitoring specific threads explicitly, maybe... */
+ list_add_tail(&proc->siblings, &vmon->processes);
+ vmon->processes_changed = 1;
}
-_out:
- return changes ? SAMPLE_CHANGED : SAMPLE_UNCHANGED;
+ /* add this process to the hash table */
+ list_add_tail(&proc->bucket, &vmon->htab[hash]);
+
+ /* if process table maintenance is enabled acquire a free slot for this process */
+ if ((vmon->flags & VMON_FLAG_PROC_ARRAY) && (i = find_proc_in_array(vmon, NULL, vmon->array_hint_free)) != -1) {
+ vmon->array[i] = proc;
+
+ /* cache where we get inserted into the array */
+ proc->array_hint_pos = i;
+ }
+
+ /* invoke ctor callback if set, note it's only called when a new vmon_proc_t has been instantiated */
+ if (vmon->proc_ctor_cb)
+ vmon->proc_ctor_cb(vmon, proc);
+
+ return proc;
}
@@ -362,15 +481,15 @@ static int proc_follow_children(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_follo
vmon_proc_t *tmp, *_tmp;
list_head_t *cur, *start;
+ assert(vmon);
+ assert(store);
+
if (!proc) { /* dtor */
try_close(&(*store)->children_fd);
return DTOR_FREE;
}
- if (proc->is_thread) /* don't follow children of threads */
- return SAMPLE_UNCHANGED;
-
if (!(*store)) { /* implicit ctor on first sample */
*store = calloc(1, sizeof(vmon_proc_follow_children_t));
@@ -420,14 +539,30 @@ static int proc_follow_children(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_follo
}
}
- if (found || (tmp = vmon_proc_monitor(vmon, proc, child_pid, proc->wants, NULL, NULL))) {
- /* position the process in the siblings list, and update the start */
- /* move to front breaks vwm, we rely on the stale processes maintaining their position! maybe make it an option to vmon_init() since it can be a useful optimization. */
- start = &tmp->siblings;
- } /* else { vmon_proc_monitor failed just move on } */
+ if (found || (tmp = proc_monitor(vmon, proc, child_pid, proc->wants, NULL, NULL))) {
+ /* There's an edge case where vmon_proc_monitor() finds child_pid existing as a child of something else,
+ * in that case we're effectively migrating it to a new parent. This occurs in the vmon use case where
+ * it's monitoring PID1-down, and PID1 of course inherits orphans. So some descendant proc is already
+ * being monitored with its children monitored too, but that proc has exited, orphaning its children.
+ * The kernel has since moved the orphaned children up to be children of PID1, and we could be performing
+ * children following for PID1 here, discovering those newly inherited orphans whose exited parent hasn't
+ * even been flagged as is_stale yet in libvmon, let alone been unreffed/removed from the htab.
+ *
+ * In such a scenario, we need to _not_ use its siblings node as a search start, because we'll be stepping
+ * into the other parent's children list, which would be Very Broken. What we instead do, is basically
+ * nothing, so it can be handled in a future sample, after the exited parent can go through its is_stale=1
+ * cycle and unlink itself from the orphaned descendants. There are more complicated ways to handle this
+ * which would technically be more accurate, but let's just do the simple and correct thing for now.
+ */
+ if (tmp->parent == proc)
+ start = &tmp->siblings;
+ } /* else { proc_monitor failed just move on } */
child_pid = 0;
break;
+
+ default:
+ assert(0);
}
}
}
@@ -462,6 +597,9 @@ static int proc_follow_threads(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_follow
vmon_proc_t *tmp, *_tmp;
int found;
+ assert(vmon);
+ assert(store);
+
if (!proc) { /* dtor */
try_closedir(&(*store)->task_dir);
@@ -471,7 +609,8 @@ static int proc_follow_threads(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_follow
if (proc->is_thread) /* bypass following the threads of threads */
return SAMPLE_UNCHANGED;
- if (!proc->stores || !proc->stores[VMON_STORE_PROC_STAT] || (((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->num_threads <= 1 && list_empty(&proc->threads)))
+ if (!proc->stores[VMON_STORE_PROC_STAT] ||
+ (((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->num_threads <= 1 && list_empty(&proc->threads)))
/* bypass following of threads if we either can't determine the number from the proc stat sample or if the sample says there's 1 or less (and an empty threads list, handling stale exited threads) */
/* XXX I'm not sure if this is always the right thing to do, there may be some situations where one could play games with clone() directly
* and escape the monitoring library with a lone thread having had the main thread exit, leaving the count at 1 while having a process
@@ -495,6 +634,24 @@ static int proc_follow_threads(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_follow
vmon_proc_unmonitor(vmon, tmp, NULL, NULL);
}
+ /* If proc is stale, assume all the threads are stale as well. In vwm/charts.c we assume all descendants of a stale node
+ * are implicitly stale, so let's ensure that's a consistent assumumption WRT libvmon's maintenance of the hierarchy.
+ * The readdir below seems like it would't find any threads of a stale process, but maybe there's some potential for a race there,
+ * particularly since we reuse an open reference on the task_dir.
+ * This reflects a similar implicit is_stale propagation in follow_children.
+ */
+ if (proc->is_stale) {
+ list_for_each_entry(tmp, &proc->threads, threads)
+ tmp->is_stale = 1;
+
+ proc->n_current_threads = 0;
+
+ /* FIXME: the changes count seems to be unused here, so this currently always returns SAMPLE_UNCHANGED, and
+ * it's unclear to me if that was intentional or just never finished.
+ */
+ return SAMPLE_UNCHANGED;
+ }
+
start = &proc->threads;
while ((dentry = readdir((*store)->task_dir))) {
int tid;
@@ -519,25 +676,148 @@ static int proc_follow_threads(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_follow
}
}
- if (found || (tmp = vmon_proc_monitor(vmon, proc, tid, (proc->wants | VMON_INTERNAL_PROC_IS_THREAD), NULL, NULL)))
+ if (found || (tmp = proc_monitor(vmon, proc, tid, (proc->wants | VMON_INTERNAL_PROC_IS_THREAD), NULL, NULL)))
start = &tmp->threads;
}
list_for_each_entry_safe(tmp, _tmp, &proc->threads, threads) {
/* set children not found to stale status so the caller can respond and on our next sample invocation we will unmonitor them */
- if (tmp->generation != vmon->generation)
+ if (tmp->generation != vmon->generation) {
tmp->is_stale = 1;
+ assert(proc->n_current_threads);
+ proc->n_current_threads--;
+ }
}
return changes ? SAMPLE_CHANGED : SAMPLE_UNCHANGED;
}
+/* sample stat process stats */
+typedef enum _vmon_proc_stat_fsm_t {
+#define VMON_ENUM_PARSER_STATES
+#include "defs/proc_stat.def"
+} vmon_proc_stat_fsm_t;
+
+static sample_ret_t proc_sample_stat(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_stat_t **store)
+{
+ int changes = 0;
+ int i, len, total = 0;
+ char *arg;
+ int argn, prev_argc;
+ vmon_proc_stat_fsm_t state = VMON_PARSER_STATE_PROC_STAT_PID;
+#define VMON_PREPARE_PARSER
+#include "defs/proc_stat.def"
+
+ assert(vmon);
+ assert(store);
+
+ if (!proc) { /* dtor */
+ try_close(&(*store)->comm_fd);
+ try_free((void **)&(*store)->comm.array);
+ try_close(&(*store)->cmdline_fd);
+ try_free((void **)&(*store)->cmdline.array);
+ try_free((void **)&(*store)->argv);
+ try_close(&(*store)->wchan_fd);
+ try_free((void **)&(*store)->wchan.array);
+ try_close(&(*store)->stat_fd);
+ try_free((void **)&(*store)->exe.array);
+
+ return DTOR_FREE;
+ }
+
+/* _retry: */
+ if (!(*store)) { /* ctor */
+
+ *store = calloc(1, sizeof(vmon_proc_stat_t));
+
+ if (proc->is_thread) {
+ (*store)->comm_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/task/%i/comm", proc->pid, proc->pid);
+ (*store)->cmdline_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/task/%i/cmdline", proc->pid, proc->pid);
+ (*store)->wchan_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/task/%i/wchan", proc->pid, proc->pid);
+ (*store)->stat_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/task/%i/stat", proc->pid, proc->pid);
+ } else {
+ (*store)->comm_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/comm", proc->pid);
+ (*store)->cmdline_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/cmdline", proc->pid);
+ (*store)->wchan_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/wchan", proc->pid);
+ (*store)->stat_fd = openf(vmon, O_RDONLY, vmon->proc_dir, "%i/stat", proc->pid);
+ }
+
+ /* initially everything is considered changed */
+ memset((*store)->changed, 0xff, sizeof((*store)->changed));
+ } else {
+ /* clear the entire changed bitmap */
+ memset((*store)->changed, 0, sizeof((*store)->changed));
+ }
+
+ /* XXX TODO: integrate load_contents_fd() calls into changes++ maintenance */
+ /* /proc/$pid/comm */
+ load_contents_fd(vmon, &(*store)->comm, (*store)->comm_fd, LOAD_FLAGS_NOTRUNCATE, (*store)->changed, VMON_PROC_STAT_COMM);
+
+ /* /proc/$pid/cmdline */
+ load_contents_fd(vmon, &(*store)->cmdline, (*store)->cmdline_fd, LOAD_FLAGS_NOTRUNCATE, (*store)->changed, VMON_PROC_STAT_CMDLINE);
+ for (prev_argc = (*store)->argc, (*store)->argc = 0, i = 0; i < (*store)->cmdline.len; i++) {
+ if (!(*store)->cmdline.array[i])
+ (*store)->argc++;
+ }
+
+ /* if the cmdline has changed, allocate argv array and store ptrs to the fields within it */
+ if (BITTEST((*store)->changed, VMON_PROC_STAT_CMDLINE)) {
+ if (prev_argc != (*store)->argc) {
+ try_free((void **)&(*store)->argv); /* XXX could realloc */
+ (*store)->argv = calloc(1, (*store)->argc * sizeof(char *));
+ }
+
+ for (argn = 0, arg = (*store)->cmdline.array, i = 0; i < (*store)->cmdline.len; i++) {
+ if (!(*store)->cmdline.array[i]) {
+ (*store)->argv[argn++] = arg;
+ arg = &(*store)->cmdline.array[i + 1];
+ }
+ }
+ }
+
+ /* /proc/$pid/wchan */
+ load_contents_fd(vmon, &(*store)->wchan, (*store)->wchan_fd, LOAD_FLAGS_NOTRUNCATE, (*store)->changed, VMON_PROC_STAT_WCHAN);
+
+ /* /proc/$pid/exe */
+ if ((*store)->cmdline.len) /* kernel threads have no cmdline, and always fail readlinkf() on exe, skip readlinking the exe for them using this heuristic */
+ readlinkf(vmon, &(*store)->exe, vmon->proc_dir, "%i/exe", proc->pid);
+
+ /* XXX TODO: there's a race between discovering comm_len from /proc/$pid/comm and applying it in the parsing of /proc/$pid/stat, detect the race
+ * scenario and retry the sample when detected by goto _retry (see commented _retry label above) */
+
+ /* read in stat and parse it assigning the stat members accordingly */
+ while ((len = try_pread((*store)->stat_fd, vmon->buf, sizeof(vmon->buf), total)) > 0) {
+ total += len;
+
+ for (i = 0; i < len; i++) {
+ /* parse the fields from the file, stepping through... */
+ _p.input = vmon->buf[i];
+ switch (state) {
+#define VMON_PARSER_DELIM ' ' /* TODO XXX eliminate the need for this, I want the .def's to include all the data format knowledge */
+#define VMON_IMPLEMENT_PARSER
+#include "defs/proc_stat.def"
+ default:
+ /* we're finished parsing once we've fallen off the end of the symbols */
+ goto _out; /* this saves us the EOF read syscall */
+ }
+ }
+ }
+
+_out:
+ return changes ? SAMPLE_CHANGED : SAMPLE_UNCHANGED;
+}
+
+
+
+
/* helper for maintaining reference counted global objects table */
static vmon_fobject_t * fobject_lookup_hinted(vmon_t *vmon, const char *path, vmon_fobject_t *hint)
{
- vmon_fobject_t *fobject = NULL, *tmp = NULL;
- uint64_t inum;
+ vmon_fobject_t *fobject = NULL, *tmp = NULL;
+ uint64_t inum;
+
+ assert(vmon);
/* TODO maintain the fobject hash tables, they should probably be isolated/scoped based on the string before the : */
/* in reality there needs to be a list somewhere of the valid prefixes we wish to support, and that list should associate
@@ -582,6 +862,9 @@ static vmon_fobject_t * fobject_lookup_hinted(vmon_t *vmon, const char *path, vm
/* add a reference to an fobject, fd_ref is optional (should be present if the reference is due to an open fd, which may not be the case) */
static void fobject_ref(vmon_t *vmon, vmon_fobject_t *fobject, vmon_proc_fd_t *fd_ref)
{
+ assert(vmon);
+ assert(fobject);
+
fobject->refcnt++;
if (fd_ref)
@@ -592,6 +875,9 @@ static void fobject_ref(vmon_t *vmon, vmon_fobject_t *fobject, vmon_proc_fd_t *f
/* fobject is required and is the object being unrefereced, fd_ref is optional but represents the per-process fd reference being used to access this fobject via */
static int fobject_unref(vmon_t *vmon, vmon_fobject_t *fobject, vmon_proc_fd_t *fd_ref)
{
+ assert(vmon);
+ assert(fobject);
+
if (fd_ref)
list_del(&fd_ref->ref_fds);
@@ -616,10 +902,13 @@ static int fobject_unref(vmon_t *vmon, vmon_fobject_t *fobject, vmon_proc_fd_t *
/* helper for deleting an fd from the per-process fds list list */
static void del_fd(vmon_t *vmon, vmon_proc_fd_t *fd)
{
+ assert(vmon);
+ assert(fd);
+
list_del(&fd->fds);
if (fd->object)
fobject_unref(vmon, fd->object, fd); /* note we supply both the fobject ptr and proc_fd ptr (fd) */
- try_free((void **)&fd->object_path);
+ try_free((void **)&fd->object_path.array);
free(fd);
}
@@ -634,6 +923,9 @@ static sample_ret_t proc_sample_files(vmon_t *vmon, vmon_proc_t *proc, vmon_proc
vmon_proc_fd_t *fd, *_fd;
vmon_fobject_t *cur_object = NULL;
+ assert(vmon);
+ assert(store);
+
if (!proc) {
/* dtor implementation */
if (--((*store)->refcnt)) /* suppress dtor while references exist, the store is shared */
@@ -755,6 +1047,9 @@ static sample_ret_t proc_sample_vm(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_vm
#define VMON_PREPARE_PARSER
#include "defs/proc_vm.def"
+ assert(vmon);
+ assert(store);
+
if (!proc) { /* dtor */
try_close(&(*store)->statm_fd);
@@ -812,6 +1107,9 @@ static sample_ret_t proc_sample_io(vmon_t *vmon, vmon_proc_t *proc, vmon_proc_io
#define VMON_PREPARE_PARSER
#include "defs/proc_io.def"
+ assert(vmon);
+ assert(store);
+
if (!proc) { /* dtor */
try_close(&(*store)->io_fd);
@@ -873,6 +1171,8 @@ static sample_ret_t sys_sample_stat(vmon_t *vmon, vmon_sys_stat_t **store)
#define VMON_PREPARE_PARSER
#include "defs/sys_stat.def"
+ assert(store);
+
if (!vmon) { /* dtor */
try_close(&(*store)->stat_fd);
return DTOR_FREE;
@@ -932,6 +1232,8 @@ static sample_ret_t sys_sample_vm(vmon_t *vmon, vmon_sys_vm_t **store)
#define VMON_PREPARE_PARSER
#include "defs/sys_vm.def"
+ assert(store);
+
if (!vmon) { /* dtor */
try_close(&(*store)->meminfo_fd);
return DTOR_FREE;
@@ -975,6 +1277,8 @@ int vmon_init(vmon_t *vmon, vmon_flags_t flags, vmon_sys_wants_t sys_wants, vmon
{
int i;
+ assert(vmon);
+
if ((flags & VMON_FLAG_PROC_ALL) && (proc_wants & VMON_WANT_PROC_FOLLOW_CHILDREN))
return 0;
@@ -1001,6 +1305,9 @@ int vmon_init(vmon_t *vmon, vmon_flags_t flags, vmon_sys_wants_t sys_wants, vmon
vmon->sys_wants = sys_wants;
vmon->proc_wants = proc_wants;
vmon->ticks_per_sec = sysconf(_SC_CLK_TCK);
+ vmon->num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
+ if (vmon->num_cpus <= 0)
+ vmon->num_cpus = 1; /* default to 1 cpu */
/* here we populate the sys and proc function tables */
#define vmon_want(_sym, _name, _func) \
@@ -1029,169 +1336,12 @@ void vmon_destroy(vmon_t *vmon)
}
-/* helper for searching for the process in the process array, specify NULL to find a free slot */
-static int find_proc_in_array(vmon_t *vmon, vmon_proc_t *proc, int hint)
-{
- int ret = -1;
-
- if (hint >= 0 && hint < vmon->array_allocated_nr && vmon->array[hint] == proc) {
- /* the hint was accurate, bypass the search */
- ret = hint;
- } else {
- int i;
-
- /* search for the entry in the array */
- for (i = 0; i < vmon->array_allocated_nr; i++) {
- if (vmon->array[i] == proc) {
- ret = i;
- break;
- }
- }
- }
-
- if (!proc && ret == -1) {
- vmon_proc_t **tmp;
-
- /* enlarge the array */
- tmp = realloc(vmon->array, (vmon->array_allocated_nr + VMON_ARRAY_GROWBY) * sizeof(vmon_proc_t *));
- if (tmp) {
- memset(&tmp[vmon->array_allocated_nr], 0, VMON_ARRAY_GROWBY * sizeof(vmon_proc_t *));
- ret = vmon->array_hint_free = vmon->array_allocated_nr;
- vmon->array_allocated_nr += VMON_ARRAY_GROWBY;
- vmon->array = tmp;
- } /* XXX TODO: handle realloc failure */
- }
-
- return ret;
-}
-
-
-/* simple helper for installing callbacks on the callback lists, currently only used for the per-process sample callbacks */
-/* will not install the same callback function & arg combination more than once, and will not install NULL-functioned callbacks at all! */
-static int maybe_install_proc_callback(vmon_t *vmon, list_head_t *callbacks, void (*func)(vmon_t *, void *, vmon_proc_t *, void *), void *arg)
-{
- if (func) {
- vmon_proc_callback_t *cb;
-
- list_for_each_entry(cb, callbacks, callbacks) {
- if (cb->func == func && cb->arg == arg)
- break;
- }
-
- if (&cb->callbacks == callbacks) {
- cb = calloc(1, sizeof(vmon_proc_callback_t));
- if (!cb)
- return 0;
-
- cb->func = func;
- cb->arg = arg;
- list_add_tail(&cb->callbacks, callbacks);
- }
- }
-
- return 1;
-}
-
-
/* monitor a process under a given vmon instance, the public interface.
* XXX note it's impossible to say "none" for wants per-process, just "inherit", if vmon_init() was told a proc_wants of "inherit" then it's like having "none"
* proc_wants for all proceses, perhaps improve this if there's a pressure to support this use case */
-vmon_proc_t * vmon_proc_monitor(vmon_t *vmon, vmon_proc_t *parent, int pid, vmon_proc_wants_t wants, void (*sample_cb)(vmon_t *, void *, vmon_proc_t *, void *), void *sample_cb_arg)
+vmon_proc_t * vmon_proc_monitor(vmon_t *vmon, int pid, vmon_proc_wants_t wants, void (*sample_cb)(vmon_t *, void *, vmon_proc_t *, void *), void *sample_cb_arg)
{
- vmon_proc_t *proc;
- int hash = (pid % VMON_HTAB_SIZE), i;
- int is_thread = (wants & VMON_INTERNAL_PROC_IS_THREAD) ? 1 : 0;
-
- wants &= ~VMON_INTERNAL_PROC_IS_THREAD;
-
- if (pid < 0)
- return NULL;
-
- list_for_each_entry(proc, &vmon->htab[hash], bucket) {
- /* search for the process to see if it's already monitored, we allow threads to exist with the same pid hence the additional is_thread comparison */
- if (proc->pid == pid && proc->is_thread == is_thread) {
- if (!maybe_install_proc_callback(vmon, &proc->sample_callbacks, sample_cb, sample_cb_arg))
- return NULL;
-
- proc->wants = wants; /* we can alter wants this way, though it clearly needs more consideration XXX */
- proc->refcnt++;
- /* This is a predicament. If the top-level (externally-established) process monitor wins the race, the process has no parent and is on the vmon->processes list.
- * Then the follow_children-established monitor comes along and wants to assign a parent, this isn't such a big deal, and we should be able to permit it, however,
- * we're in the process of iterating the top-level processes list when we run the follow_children() sampler of a descendant which happens to also be the parent.
- * We can't simply remove the process from the top-level processes list mid-iteration and stick it on the children list of the parent, the iterator could wind up
- * following the new pointer into the parents children.
- *
- * What I'm doing for now is allowing the assignment of a parent when there is no parent, but we don't perform the vmon->processes to parent->children migration
- * until the top-level sample_siblings() function sees the node with the parent. At that point, the node will migrate to the parent's children list.
- */
- if (parent && !proc->parent) {
- /* if a parent was supplied, and there is no current parent, the process is a top-level currently by external callers, but now appears to be a child of something as well,
- * so in this scenario, we'll remove it from the top-level siblings list, and make it a child of the new parent. I don't think there needs to be concern about its being a thread here. */
- /* Note we can't simply move the process from its current processes list and add it to the supplied parent's children list, as that would break the iterator above us at the top-level, so
- * we must defer the migration until the processes iterator context can do it for us - but this is tricky because we're in the middle of traversing our heirarchy and this process may
- * be in a critical is_new state which must be realized this sample at its location in the heirarchy for correctness, there will be no reappearance of that critical state in the correct
- * tree position for users like vwm. */
- /* the VMON_FLAG_2PASS flag has been introduced for users like vwm */
- proc->parent = parent;
- } /* else if (parent && proc->parent) { XXX TODO: it shouldn't be possible to have conflicting parents, but may want to log an error if it happens. */
-
- return proc;
- }
- }
-
- proc = (vmon_proc_t *)calloc(1, sizeof(vmon_proc_t));
- if (proc == NULL)
- return NULL; /* TODO: report an error */
-
- proc->pid = pid;
- proc->wants = wants;
- proc->generation = vmon->generation;
- proc->refcnt = 1;
- proc->is_new = 1; /* newly created process */
- proc->is_thread = is_thread;
- proc->parent = parent;
- INIT_LIST_HEAD(&proc->sample_callbacks);
- INIT_LIST_HEAD(&proc->children);
- INIT_LIST_HEAD(&proc->siblings);
- INIT_LIST_HEAD(&proc->threads);
-
- if (!maybe_install_proc_callback(vmon, &proc->sample_callbacks, sample_cb, sample_cb_arg)) {
- free(proc);
- return NULL;
- }
-
- if (parent) {
- /* if a parent is specified, attach this process to the parent's children or threads with its siblings */
- if (is_thread) {
- list_add_tail(&proc->threads, &parent->threads);
- parent->threads_changed = 1;
- } else {
- list_add_tail(&proc->siblings, &parent->children);
- parent->children_changed = 1;
- }
- } else {
- /* if no parent is specified, this is a toplevel process, attach it to the vmon->processes list with its siblings */
- /* XXX ignoring is_thread if no parent is specified, it shouldn't occur, unless I want to support external callers monitoring specific threads explicitly, maybe... */
- list_add_tail(&proc->siblings, &vmon->processes);
- vmon->processes_changed = 1;
- }
-
- /* add this process to the hash table */
- list_add_tail(&proc->bucket, &vmon->htab[hash]);
-
- /* if process table maintenance is enabled acquire a free slot for this process */
- if ((vmon->flags & VMON_FLAG_PROC_ARRAY) && (i = find_proc_in_array(vmon, NULL, vmon->array_hint_free)) != -1) {
- vmon->array[i] = proc;
-
- /* cache where we get inserted into the array */
- proc->array_hint_pos = i;
- }
-
- /* invoke ctor callback if set, note it's only called when a new vmon_proc_t has been instantiated */
- if (vmon->proc_ctor_cb)
- vmon->proc_ctor_cb(vmon, proc);
-
- return proc;
+ return proc_monitor(vmon, NULL, pid, wants, sample_cb, sample_cb_arg);
}
@@ -1201,6 +1351,10 @@ void vmon_proc_unmonitor(vmon_t *vmon, vmon_proc_t *proc, void (*sample_cb)(vmon
vmon_proc_t *child, *_child;
int i;
+ assert(vmon);
+ assert(proc);
+ assert(!sample_cb_arg || sample_cb);
+
if (sample_cb) { /* uninstall callback */
vmon_proc_callback_t *cb, *_cb;
@@ -1225,7 +1379,7 @@ void vmon_proc_unmonitor(vmon_t *vmon, vmon_proc_t *proc, void (*sample_cb)(vmon
vmon_proc_unmonitor(vmon, child, NULL, NULL);
}
- /* unmonitor all threads being monitored, suppressed if this process is a thread itself, as threads don't have children */
+ /* unmonitor all threads being monitored, suppressed if this process is a thread itself */
if (!proc->is_thread) {
list_for_each_entry_safe(child, _child, &proc->threads, threads)
vmon_proc_unmonitor(vmon, child, NULL, NULL);
@@ -1258,8 +1412,18 @@ void vmon_proc_unmonitor(vmon_t *vmon, vmon_proc_t *proc, void (*sample_cb)(vmon
for (i = 0; i < sizeof(vmon->proc_funcs) / sizeof(vmon->proc_funcs[VMON_STORE_PROC_STAT]); i++) {
if (proc->stores[i] != NULL) { /* any non-NULL stores must have a function installed and must have been sampled, invoke the dtor branch */
- if (vmon->proc_funcs[i](vmon, NULL, &proc->stores[i]) == DTOR_FREE)
+ sample_ret_t r;
+
+ r = vmon->proc_funcs[i](vmon, NULL, &proc->stores[i]);
+ switch (r) {
+ case DTOR_FREE:
try_free((void **)&proc->stores[i]);
+ break;
+ case DTOR_NOFREE:
+ break;
+ default:
+ assert(0);
+ }
}
}
@@ -1276,6 +1440,9 @@ static void sample(vmon_t *vmon, vmon_proc_t *proc)
{
int i, wants, cur;
+ assert(vmon);
+ assert(proc);
+
proc->children_changed = proc->threads_changed = 0;
/* load this process monitors wants, or inherit the default */
@@ -1284,8 +1451,14 @@ static void sample(vmon_t *vmon, vmon_proc_t *proc)
proc->activity = 0;
for (i = 0, cur = 1; wants; cur <<= 1, i++) {
if (wants & cur) {
- if (vmon->proc_funcs[i](vmon, proc, &proc->stores[i]) == SAMPLE_CHANGED)
- proc->activity |= cur;
+ /* XXX: this is a bit awkward, but in constrained environments you might want to only follow threads while
+ * suppressing their sampling of the other WANT_PROC* data. To achieve that you specify VMON_FLAG_NEGLECT_THREADS
+ * in combination with VMON_WANT_PROC_FOLLOW_THREADS. The following line is what makes that actually work.
+ * This also adds another ordering dependency in proc_wants.def
+ */
+ if (!proc->is_thread || !(vmon->flags & VMON_FLAG_NEGLECT_THREADS) || cur <= VMON_WANT_PROC_FOLLOW_THREADS)
+ if (vmon->proc_funcs[i](vmon, proc, &proc->stores[i]) == SAMPLE_CHANGED)
+ proc->activity |= cur;
wants &= ~cur;
}
@@ -1293,21 +1466,53 @@ static void sample(vmon_t *vmon, vmon_proc_t *proc)
}
+static int sample_siblings_unipass(vmon_t *vmon, list_head_t *siblings);
+static int sample_siblings_pass1(vmon_t *vmon, list_head_t *siblings);
+static int sample_siblings_pass2(vmon_t *vmon, list_head_t *siblings);
+
/* internal sampling helper, perform sampling for all sibling processes in the provided siblings list */
-static int sample_threads(vmon_t *vmon, list_head_t *threads)
+static int sample_threads_unipass(vmon_t *vmon, list_head_t *threads)
{
vmon_proc_t *proc;
+ assert(vmon);
+ assert(threads);
+
list_for_each_entry(proc, threads, threads) {
sample(vmon, proc);
+ sample_siblings_unipass(vmon, &proc->children); /* invoke samplers for this thread's children (which strangely is a thing) */
+ }
-#if 0
- /* callbacks can't be installed currently on threads */
- vmon_proc_callback_t *cb;
+ return 1;
+}
- list_for_each_entry(cb, &proc->sample_callbacks, callbacks)
- cb->func(vmon, vmon->sample_cb_arg, proc, cb->arg);
-#endif
+
+/* internal sampling helper, perform sampling for all sibling processes in the provided siblings list */
+static int sample_threads_pass1(vmon_t *vmon, list_head_t *threads)
+{
+ vmon_proc_t *proc;
+
+ assert(vmon);
+ assert(threads);
+
+ list_for_each_entry(proc, threads, threads) {
+ sample(vmon, proc);
+ sample_siblings_pass1(vmon, &proc->children); /* invoke samplers for this thread's children (which strangely is a thing) */
+ }
+
+ return 1;
+}
+
+
+static int sample_threads_pass2(vmon_t *vmon, list_head_t *threads)
+{
+ vmon_proc_t *proc;
+
+ assert(vmon);
+ assert(threads);
+
+ list_for_each_entry(proc, threads, threads) {
+ sample_siblings_pass2(vmon, &proc->children); /* invoke samplers for this thread's children (which strangely is a thing) */
}
return 1;
@@ -1315,10 +1520,13 @@ static int sample_threads(vmon_t *vmon, list_head_t *threads)
/* internal single-pass sampling helper, recursively perform sampling and callbacks for all sibling processes in the provided siblings list */
-static int sample_siblings(vmon_t *vmon, list_head_t *siblings)
+static int sample_siblings_unipass(vmon_t *vmon, list_head_t *siblings)
{
vmon_proc_t *proc, *_proc;
+ assert(vmon);
+ assert(siblings);
+
list_for_each_entry_safe(proc, _proc, siblings, siblings) {
vmon_proc_callback_t *cb;
int entered_new = 0;
@@ -1327,9 +1535,9 @@ static int sample_siblings(vmon_t *vmon, list_head_t *siblings)
if (proc->is_new)
entered_new = 1;
- sample(vmon, proc); /* invoke samplers for this node */
- sample_threads(vmon, &proc->threads); /* invoke samplers for this node's threads */
- sample_siblings(vmon, &proc->children); /* invoke samplers for this node's children, and their callbacks, by recursing into this function */
+ sample(vmon, proc); /* invoke samplers for this node */
+ sample_threads_unipass(vmon, &proc->threads); /* invoke samplers for this node's threads */
+ sample_siblings_unipass(vmon, &proc->children); /* invoke samplers for this node's children, and their callbacks, by recursing into this function */
/* XXX TODO: error returns */
/* if this is the top-level processes list, and proc has found a parent through the above sampling, migrate it to the parent's children list */
@@ -1340,7 +1548,7 @@ static int sample_siblings(vmon_t *vmon, list_head_t *siblings)
}
/* XXX note that sample_callbacks are called only after all the descendants have had their sampling performed (and their potential callbacks invoked)
- * this enables the installation of a callback at a specific node in the process heirarchy which can also perform duties on behalf of the children
+ * this enables the installation of a callback at a specific node in the process hierarchy which can also perform duties on behalf of the children
* being monitored, handy when automatically following children, an immediately relevant use case (vwm)
*/
list_for_each_entry(cb, &proc->sample_callbacks, callbacks)
@@ -1361,16 +1569,19 @@ static int sample_siblings(vmon_t *vmon, list_head_t *siblings)
}
-/* 2pass version of the internal heirarchical sampling helper */
+/* 2pass version of the internal hierarchical sampling helper */
static int sample_siblings_pass1(vmon_t *vmon, list_head_t *siblings)
{
vmon_proc_t *proc, *_proc;
+ assert(vmon);
+ assert(siblings);
+
/* invoke samplers */
list_for_each_entry_safe(proc, _proc, siblings, siblings) {
- sample(vmon, proc); /* invoke samplers for this node */
- sample_threads(vmon, &proc->threads); /* invoke samplers for this node's threads */
- sample_siblings_pass1(vmon, &proc->children); /* invoke samplers for this node's children, by recursing into this function */
+ sample(vmon, proc); /* invoke samplers for this node */
+ sample_threads_pass1(vmon, &proc->threads); /* invoke samplers for this node's threads */
+ sample_siblings_pass1(vmon, &proc->children); /* invoke samplers for this node's children, by recursing into this function */
/* XXX TODO: error returns */
/* if this is the top-level processes list, and proc has found a parent through the above sampling, migrate it to the parent's children list */
@@ -1401,10 +1612,14 @@ static int sample_siblings_pass2(vmon_t *vmon, list_head_t *siblings)
{
vmon_proc_t *proc;
+ assert(vmon);
+ assert(siblings);
+
/* invoke callbacks */
list_for_each_entry(proc, siblings, siblings) {
vmon_proc_callback_t *cb;
+ sample_threads_pass2(vmon, &proc->threads); /* recurse into any children of the threads, invoking callbacks as encountered from the leaves up */
sample_siblings_pass2(vmon, &proc->children); /* recurse into children, we invoke callbacks as encountered on nodes from the leaves up */
list_for_each_entry(cb, &proc->sample_callbacks, callbacks)
@@ -1427,6 +1642,8 @@ int vmon_sample(vmon_t *vmon)
{
int i, wants, cur, ret = 1;
+ assert(vmon);
+
vmon->generation++;
/* first manage the "all processes monitored" use case, this doesn't do any sampling, it just maintains the top-level list of processes being monitored */
@@ -1460,7 +1677,7 @@ int vmon_sample(vmon_t *vmon)
if (!found) {
/* monitor the process */
- vmon_proc_t *proc = vmon_proc_monitor(vmon, NULL, pid, vmon->proc_wants, NULL, NULL);
+ vmon_proc_t *proc = proc_monitor(vmon, NULL, pid, vmon->proc_wants, NULL, NULL);
if (!proc)
continue; /* TODO error */
@@ -1505,12 +1722,12 @@ int vmon_sample(vmon_t *vmon)
/* then the per-process samplers */
if ((vmon->flags & VMON_FLAG_PROC_ARRAY)) {
int j;
- /* TODO: determine if this really makes sense, if we always maintain a heirarchy even in array mode, then we
- * should probably always sample in the heirarchical order, or maybe make it caller-specified.
- * There is a benefit to invoking the callbacks in heirarchical order, the callbacks can make assumptions about the children
+ /* TODO: determine if this really makes sense, if we always maintain a hierarchy even in array mode, then we
+ * should probably always sample in the hierarchical order, or maybe make it caller-specified.
+ * There is a benefit to invoking the callbacks in hierarchical order, the callbacks can make assumptions about the children
* having the callbacks invoked prior to the current node, if done in depth-first order....
- * XXX this is a problem, figure out what to do with this, for now we don't even maintain the heirarchy in VMON_FLAG_PROC_ALL
- * mode, only FOLLOW_CHILDREN mode, and it's likely PROC_ARRAY will generally be used together with PROC_ALL, so no heirarchy
+ * XXX this is a problem, figure out what to do with this, for now we don't even maintain the hierarchy in VMON_FLAG_PROC_ALL
+ * mode, only FOLLOW_CHILDREN mode, and it's likely PROC_ARRAY will generally be used together with PROC_ALL, so no hierarchy
* is available to traverse even if we wanted to.
*/
@@ -1534,7 +1751,7 @@ int vmon_sample(vmon_t *vmon)
}
}
} else if ((vmon->flags & VMON_FLAG_2PASS)) {
- /* recursive heirarchical depth-first processes tree sampling, at each node threads come before children, done in two passes:
+ /* recursive hierarchical depth-first processes tree sampling, at each node threads come before children, done in two passes:
* Pass 1. samplers
* Pass 2. callbacks
* XXX this is the path vwm utilizes, everything else is for other uses, like implementing top-like programs.
@@ -1542,11 +1759,29 @@ int vmon_sample(vmon_t *vmon)
ret = sample_siblings_pass1(vmon, &vmon->processes); /* XXX TODO: errors */
ret = sample_siblings_pass2(vmon, &vmon->processes);
} else {
- /* recursive heirarchical depth-first processes tree sampling, at each node threads come before children, done in a single pass:
+ /* recursive hierarchical depth-first processes tree sampling, at each node threads come before children, done in a single pass:
* Pass 1. samplers; callbacks (for every node)
*/
- ret = sample_siblings(vmon, &vmon->processes);
+ ret = sample_siblings_unipass(vmon, &vmon->processes);
}
return ret;
}
+
+
+void vmon_dump_procs(vmon_t *vmon, FILE *out)
+{
+ assert(vmon);
+ assert(out);
+
+ fprintf(out, "generation=%i\n", vmon->generation);
+ for (int i = 0; i < VMON_HTAB_SIZE; i++) {
+ vmon_proc_t *proc;
+
+ list_for_each_entry(proc, &vmon->htab[i], bucket) {
+ fprintf(out, "[%i] proc=%p parent=%p gen=%i pid=%i rc=%i is_threaded=%i is_thread=%i is_new=%u is_stale=%u\n",
+ i, proc, proc->parent, proc->generation, proc->pid, proc->refcnt, (unsigned)proc->is_threaded, (unsigned)proc->is_thread, (unsigned)proc->is_new, (unsigned)proc->is_stale);
+
+ }
+ }
+}
© All Rights Reserved