diff options
Diffstat (limited to 'src/charts.c')
-rw-r--r-- | src/charts.c | 328 |
1 files changed, 181 insertions, 147 deletions
diff --git a/src/charts.c b/src/charts.c index 5d5cbc3..0126b59 100644 --- a/src/charts.c +++ b/src/charts.c @@ -203,6 +203,7 @@ static void vmon_dtor_cb(vmon_t *vmon, vmon_proc_t *proc) /* initialize charts system */ vwm_charts_t * vwm_charts_create(vcr_backend_t *vbe, unsigned flags) { + vmon_flags_t vmon_flags = VMON_FLAG_2PASS; vwm_charts_t *charts; charts = calloc(1, sizeof(vwm_charts_t)); @@ -216,12 +217,14 @@ vwm_charts_t * vwm_charts_create(vcr_backend_t *vbe, unsigned flags) if (flags & VWM_CHARTS_FLAG_DEFER_MAINTENANCE) charts->defer_maintenance = 1; - if (flags & VWM_CHARTS_NO_THREADS) + if (flags & VWM_CHARTS_NO_THREADS) { charts->no_threads = 1; + vmon_flags |= VMON_FLAG_NEGLECT_THREADS; + } charts->prev_sampling_interval_secs = charts->sampling_interval_secs = CHART_DEFAULT_INTERVAL_SECS; - if (!vmon_init(&charts->vmon, VMON_FLAG_2PASS, CHART_VMON_SYS_WANTS, CHART_VMON_PROC_WANTS)) { + if (!vmon_init(&charts->vmon, vmon_flags, CHART_VMON_SYS_WANTS, CHART_VMON_PROC_WANTS)) { VWM_ERROR("unable to initialize libvmon"); goto _err_charts; } @@ -334,18 +337,18 @@ static void proc_argv2strs(const vmon_proc_t *proc, vcr_str_t *strs, int max_str /* helper for counting number of existing descendants subtrees */ -static int count_rows(vmon_proc_t *proc) +static int count_rows(vmon_proc_t *proc, unsigned no_threads) { - int count = 1; /* XXX maybe suppress proc->is_new? */ + int count = no_threads ? !proc->is_thread : 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); + count += count_rows(child, no_threads); } list_for_each_entry(child, &proc->children, siblings) - count += count_rows(child); + count += count_rows(child, no_threads); return count; } @@ -413,18 +416,20 @@ static void print_argv(const vwm_charts_t *charts, const vwm_chart_t *chart, int /* determine if a given process has subsequent siblings in the hierarchy */ -static inline int proc_has_subsequent_siblings(vmon_t *vmon, vmon_proc_t *proc) +static inline int proc_has_subsequent_siblings(vwm_charts_t *charts, vmon_proc_t *proc) { - struct list_head *sib, *head = &vmon->processes; + struct list_head *sib, *head = &charts->vmon.processes; if (proc->is_thread) { - /* Supporting threads having children arrived late in vwm's existence, - * but it indeed is a thing. */ - assert(proc->parent && proc->parent->is_threaded); - head = &proc->parent->threads; - for (sib = proc->threads.next; sib != head; sib = sib->next) { - if (!(list_entry(sib, vmon_proc_t, threads)->is_stale)) - return 1; + if (!charts->no_threads) { + /* Supporting threads having children arrived late in vwm's existence, + * but it indeed is a thing. */ + assert(proc->parent && proc->parent->is_threaded); + head = &proc->parent->threads; + for (sib = proc->threads.next; sib != head; sib = sib->next) { + if (!(list_entry(sib, vmon_proc_t, threads)->is_stale)) + return 1; + } } return 0; @@ -454,9 +459,8 @@ static unsigned interval_as_hz(vwm_charts_t *charts) /* draw a process' row slice of a process tree */ static void draw_tree_row(vwm_charts_t *charts, vwm_chart_t *chart, int x, int depth, int row, const vmon_proc_t *proc, int *res_width) { - vmon_proc_t *child, *ancestor, *sibling, *last_sibling = NULL; int bar_x = 0, bar_y = (row + 1) * VCR_ROW_HEIGHT; - int sub; + vmon_proc_t *child, *sibling, *last_sibling = NULL; /* only if this process isn't the root process @ the window shall we consider all relational drawing conditions */ if (proc == chart->proc) @@ -464,18 +468,27 @@ static void draw_tree_row(vwm_charts_t *charts, vwm_chart_t *chart, int x, int d /* XXX: everything done in this code block only dirties _this_ process' row in the rendered chart output */ - /* walk up the ancestors until reaching chart->proc, 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->proc; ancestor = ancestor->parent, sub++) { - bar_x = ((depth - 1) - sub) * (VCR_ROW_HEIGHT / 2) + 4; + if (proc->parent) { + int sub = 1; - assert(depth > 0); + /* walk up the ancestors until reaching chart->proc, any ancestors we encounter which have more siblings we draw a vertical bar for */ + /* this draws the |'s in something like: | | | | comm */ + for (vmon_proc_t *ancestor = proc->parent; ancestor != chart->proc; ancestor = ancestor->parent) { + bar_x = ((depth - 1) - sub) * (VCR_ROW_HEIGHT / 2) + 4; - /* determine if the ancestor has remaining siblings which are not stale, if so, draw a connecting bar at its depth */ - if (proc_has_subsequent_siblings(&charts->vmon, ancestor)) - vcr_draw_ortho_line(chart->vcr, VCR_LAYER_TEXT, - x + bar_x, bar_y - VCR_ROW_HEIGHT, /* dst x1, y1 */ - x + bar_x, bar_y); /* dst x2, y2 (vertical line) */ + assert(depth > 0); + + /* determine if the ancestor has remaining siblings which are not stale, if so, draw a connecting bar at its depth */ + if (proc_has_subsequent_siblings(charts, ancestor)) + vcr_draw_ortho_line(chart->vcr, VCR_LAYER_TEXT, + x + bar_x, bar_y - VCR_ROW_HEIGHT, /* dst x1, y1 */ + x + bar_x, bar_y); /* dst x2, y2 (vertical line) */ + + sub += (!charts->no_threads || !ancestor->is_thread); + + /* it shouldn't be possible to run out of parents, we should always arrive at chart->proc */ + assert(ancestor->parent); + } } /* determine if _any_ of our siblings have children requiring us to draw a tee immediately before our comm string. @@ -697,8 +710,10 @@ static void draw_columns(vwm_charts_t *charts, vwm_chart_t *chart, vwm_column_t str_len = snpf(str, sizeof(str), "WChan"); else { - /* don't show wchan for processes with threads, since their main thread will show it. */ - if (!proc->is_thread && !list_empty(&proc->threads)) + /* don't show wchan for processes with threads, since their main thread will show it, + * unless we're in --no-threads mode. + */ + if (!proc->is_thread && !list_empty(&proc->threads) && !charts->no_threads) break; str_len = snpf(str, sizeof(str), "%.*s", @@ -713,8 +728,10 @@ static void draw_columns(vwm_charts_t *charts, vwm_chart_t *chart, vwm_column_t if (heading) str_len = snpf(str, sizeof(str), "State"); else { - /* don't show process state for processes with threads, since their main thread will show it. */ - if (!proc->is_thread && !list_empty(&proc->threads)) + /* don't show process state for processes with threads, since their main thread will show it. + * unless we're in --no-threads mode. + */ + if (!proc->is_thread && !list_empty(&proc->threads) && !charts->no_threads) break; str_len = snpf(str, sizeof(str), "%c", proc_stat->state); @@ -881,145 +898,160 @@ static void draw_chart_rest(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_ if (deferred_pass && proc->is_stale) return; - if (!deferred_pass) { - /* These incremental/structural aspects can't be repeated in the final defer_maintenance pass since it's - * a repeated pass within the same sample - we can't realize these effects twice. - */ - if (sample_duration_idx == 0) { /* some things need to only be done once per sample duration, some at the start, some at the end */ - static int in_stale = 0; - - if (proc->is_stale) { /* we "realize" stale processes only in the first draw within a sample duration */ - /* what to do when a process (subtree) has gone away */ - 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; - - /* this advances row to the last row of all descendants, the minus one is needed since we're - * already at proc's row, and count_rows() includes it in the count. Imagine there are no - * descendants, count_rows returns 1, we turn that into 0, and (*row) stays unchanged, which - * is correct - we snowflake ourself and that's that. - */ - (*row) += count_rows(proc) - 1; - } - - (*depth)++; - list_for_each_entry_prev(child, &proc->children, siblings) { - draw_chart_rest(charts, chart, child, depth, row, deferred_pass, sample_duration_idx); - (*row)--; - } + /* Don't render and don't allocate/snowflake thread rows in --no-threads mode */ + if (!proc->is_thread || !charts->no_threads) { + if (!deferred_pass) { + /* These incremental/structural aspects can't be repeated in the final defer_maintenance pass since it's + * a repeated pass within the same sample - we can't realize these effects twice. + */ + if (sample_duration_idx == 0) { /* some things need to only be done once per sample duration, some at the start, some at the end */ + static int in_stale = 0; + + if (proc->is_stale) { /* we "realize" stale processes only in the first draw within a sample duration */ + /* what to do when a process (subtree) has gone away */ + 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; + + /* this advances row to the last row of all descendants, the minus one is needed since we're + * already at proc's row, and count_rows() includes it in the count. Imagine there are no + * descendants, count_rows returns 1, we turn that into 0, and (*row) stays unchanged, which + * is correct - we snowflake ourself and that's that. + */ + (*row) += count_rows(proc, charts->no_threads) - 1; + } - if (!proc->is_thread) { - list_for_each_entry_prev(child, &proc->threads, threads) { + (*depth)++; + list_for_each_entry_prev(child, &proc->children, siblings) { draw_chart_rest(charts, chart, child, depth, row, deferred_pass, sample_duration_idx); (*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); + if (!proc->is_thread) { + /* In --no-threads mode we hide the spatial effects of the followed threads, + * which amounts to messing with the depth and row variables. This could be + * done better. + */ + if (charts->no_threads) + (*depth)--; + + list_for_each_entry_prev(child, &proc->threads, threads) { + draw_chart_rest(charts, chart, child, depth, row, deferred_pass, sample_duration_idx); + if (!charts->no_threads) + (*row)--; + } + + if (charts->no_threads) + (*depth)++; + } + (*depth)--; - mark_finish(charts, chart, (*row)); + 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); - /* extract the row from the various layers */ - snowflake_row(charts, chart, (*row)); - chart->snowflakes_cnt++; + mark_finish(charts, chart, (*row)); - /* stamp the name (and whatever else we include) into chart.text_picture */ - draw_columns(charts, chart, chart->snowflake_columns, 0 /* heading */, 0 /* depth */, chart->hierarchy_end, proc); - vcr_shadow_row(chart->vcr, VCR_LAYER_TEXT, chart->hierarchy_end); + /* extract the row from the various layers */ + snowflake_row(charts, chart, (*row)); + chart->snowflakes_cnt++; - chart->hierarchy_end--; + /* stamp the name (and whatever else we include) into chart.text_picture */ + draw_columns(charts, chart, chart->snowflake_columns, 0 /* heading */, 0 /* depth */, chart->hierarchy_end, proc); + vcr_shadow_row(chart->vcr, VCR_LAYER_TEXT, chart->hierarchy_end); - if (in_stale_entrypoint) { - VWM_TRACE("exited stale at chart=%p depth=%i row=%i", chart, *depth, *row); - in_stale = 0; - } + chart->hierarchy_end--; - return; - } else { - /* If we're not stale, assert we're not in_stale, because the count_rows() used above is indiscriminate. - * if there's !is_stale descendents then we'll get confused as-is. - */ - assert(!in_stale); - } + if (in_stale_entrypoint) { + VWM_TRACE("exited stale at chart=%p depth=%i row=%i", chart, *depth, *row); + in_stale = 0; + } + + return; + } else { + /* If we're not stale, assert we're not in_stale, because the count_rows() used above is indiscriminate. + * if there's !is_stale descendents then we'll get confused as-is. + */ + assert(!in_stale); + } - /* 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; + /* 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; + proc_ctxt->generation = charts->vmon.generation; + } } - } - if (proc->is_stale) - return; /* is_stale is already handled on the first sample_diration_idx */ - - /* we "realize" new processes on the last draw within a duration. - * FIXME TODO: this could be placed more accurately in time by referencing the process's - * PROC_STAT_START time and allocating the row at that point within a duration. - * but for now it's still an improvement over losing time to simply place it at the end of - * the duration. We don't have two samples to compute cpu utilizations for it anyways, so - * even if we were to place it accurately on the timeline, there wouldn't be data to put - * in the intervening space between the start line and the end anyways, which would be less - * accurate/potentially misleading - basically the start line would have to be repeated to - * fill in the space where we have no data so as to still indicate "hey, the process started - * back here, but this filled white region is where we couldn't collect anything about it - * since its start point. This raises an interesting issue in general surrounding start lines - * in general; many processes tend to already exist when vmon starts up, and we draw the start - * lines when we begin monitoring a given process... and that is misleading if the process was - * preexisting. In such caes, when the start time is way in the past, we should either suppress - * the start line, or be willing to place it out of phase - if the graph covers that moment. If - * we were to place it out of phase, we'd have another situation where we can't leave the space - * between then and the current sample empty, it would have to all be filled with start line. - */ - if (proc->is_new) { - if (sample_duration_idx != (charts->this_sample_duration - 1)) - return; /* suppress doing anything aboout new processes until the last draw within the duration */ + if (proc->is_stale) + return; /* is_stale is already handled on the first sample_diration_idx */ + + /* we "realize" new processes on the last draw within a duration. + * FIXME TODO: this could be placed more accurately in time by referencing the process's + * PROC_STAT_START time and allocating the row at that point within a duration. + * but for now it's still an improvement over losing time to simply place it at the end of + * the duration. We don't have two samples to compute cpu utilizations for it anyways, so + * even if we were to place it accurately on the timeline, there wouldn't be data to put + * in the intervening space between the start line and the end anyways, which would be less + * accurate/potentially misleading - basically the start line would have to be repeated to + * fill in the space where we have no data so as to still indicate "hey, the process started + * back here, but this filled white region is where we couldn't collect anything about it + * since its start point. This raises an interesting issue in general surrounding start lines + * in general; many processes tend to already exist when vmon starts up, and we draw the start + * lines when we begin monitoring a given process... and that is misleading if the process was + * preexisting. In such caes, when the start time is way in the past, we should either suppress + * the start line, or be willing to place it out of phase - if the graph covers that moment. If + * we were to place it out of phase, we'd have another situation where we can't leave the space + * between then and the current sample empty, it would have to all be filled with start line. + */ + if (proc->is_new) { + if (sample_duration_idx != (charts->this_sample_duration - 1)) + return; /* suppress doing anything aboout new processes until the last draw within the duration */ - /* what to do when a process has been introduced */ - VWM_TRACE("%i is new", proc->pid); + /* what to do when a process has been introduced */ + VWM_TRACE("%i is new", proc->pid); - allocate_row(charts, chart, (*row)); + allocate_row(charts, chart, (*row)); - chart->hierarchy_end++; + chart->hierarchy_end++; - /* 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; - } + /* 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, - (proc->is_thread || !proc->is_threaded) ? charts->vmon.num_cpus : 1.f /* mult */, - stime_delta, - charts->inv_total_delta, - utime_delta, - charts->inv_total_delta); - } + draw_bars(charts, chart, *row, + (proc->is_thread || !proc->is_threaded) ? charts->vmon.num_cpus : 1.f /* mult */, + stime_delta, + charts->inv_total_delta, + utime_delta, + charts->inv_total_delta); + } - /* unless a deferred pass, only try draw the overlay on the last draw within a duration */ - if (deferred_pass || sample_duration_idx == (charts->this_sample_duration - 1)) - draw_overlay_row(charts, chart, proc, *depth, *row, deferred_pass); + /* unless a deferred pass, only try draw the overlay on the last draw within a duration */ + if (deferred_pass || sample_duration_idx == (charts->this_sample_duration - 1)) + draw_overlay_row(charts, chart, proc, *depth, *row, deferred_pass); - (*row)++; + /* note these are suppressed for --no-threads */ + (*row)++; + (*depth)++; + } /* 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, deferred_pass, sample_duration_idx); @@ -1029,7 +1061,9 @@ static void draw_chart_rest(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_ list_for_each_entry(child, &proc->children, siblings) { draw_chart_rest(charts, chart, child, depth, row, deferred_pass, sample_duration_idx); } - (*depth)--; + + if (!proc->is_thread || !charts->no_threads) + (*depth)--; } @@ -1231,7 +1265,7 @@ vwm_chart_t * vwm_chart_create(vwm_charts_t *charts, int pid, int width, int hei /* 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 hierarchy */ - chart->hierarchy_end = CHART_NUM_FIXED_HEADER_ROWS + count_rows(chart->proc); + chart->hierarchy_end = CHART_NUM_FIXED_HEADER_ROWS + count_rows(chart->proc, charts->no_threads); chart->gen_last_composed = -1; chart->vcr = vcr_new(charts->vcr_backend, &chart->hierarchy_end, &chart->snowflakes_cnt, &charts->marker_distance); |