872c8faaf2
Since the invention of read-only memory, write-only memory has been considered deprecated. Where appropriate, either make use of the value, or avoid writing it, to make it clear that it is not used.
1327 lines
51 KiB
C
1327 lines
51 KiB
C
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
|
|
|
/***
|
|
This file is part of systemd.
|
|
|
|
Copyright (C) 2009-2013 Intel Corporation
|
|
|
|
Authors:
|
|
Auke Kok <auke-jan.h.kok@intel.com>
|
|
|
|
systemd is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
systemd 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
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
***/
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
#include <unistd.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "util.h"
|
|
#include "macro.h"
|
|
#include "store.h"
|
|
#include "svg.h"
|
|
#include "bootchart.h"
|
|
#include "list.h"
|
|
|
|
#define time_to_graph(t) ((t) * arg_scale_x)
|
|
#define ps_to_graph(n) ((n) * arg_scale_y)
|
|
#define kb_to_graph(m) ((m) * arg_scale_y * 0.0001)
|
|
#define to_color(n) (192.0 - ((n) * 192.0))
|
|
|
|
static char str[8092];
|
|
|
|
#define svg(a...) do { snprintf(str, 8092, ## a); fputs(str, of); fflush(of); } while (0)
|
|
|
|
static const char * const colorwheel[12] = {
|
|
"rgb(255,32,32)", // red
|
|
"rgb(32,192,192)", // cyan
|
|
"rgb(255,128,32)", // orange
|
|
"rgb(128,32,192)", // blue-violet
|
|
"rgb(255,255,32)", // yellow
|
|
"rgb(192,32,128)", // red-violet
|
|
"rgb(32,255,32)", // green
|
|
"rgb(255,64,32)", // red-orange
|
|
"rgb(32,32,255)", // blue
|
|
"rgb(255,192,32)", // yellow-orange
|
|
"rgb(192,32,192)", // violet
|
|
"rgb(32,192,32)" // yellow-green
|
|
};
|
|
|
|
static double idletime = -1.0;
|
|
static int pfiltered = 0;
|
|
static int pcount = 0;
|
|
static int kcount = 0;
|
|
static float psize = 0;
|
|
static float ksize = 0;
|
|
static float esize = 0;
|
|
static struct list_sample_data *sampledata;
|
|
static struct list_sample_data *prev_sampledata;
|
|
extern struct list_sample_data *head;
|
|
|
|
static void svg_header(void) {
|
|
float w;
|
|
float h;
|
|
struct list_sample_data *sampledata_last;
|
|
|
|
sampledata = head;
|
|
LIST_FIND_TAIL(struct list_sample_data, link, sampledata, head);
|
|
sampledata_last = head;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
sampledata_last = sampledata;
|
|
}
|
|
|
|
/* min width is about 1600px due to the label */
|
|
w = 150.0 + 10.0 + time_to_graph(sampledata_last->sampletime - graph_start);
|
|
w = ((w < 1600.0) ? 1600.0 : w);
|
|
|
|
/* height is variable based on pss, psize, ksize */
|
|
h = 400.0 + (arg_scale_y * 30.0) /* base graphs and title */
|
|
+ (arg_pss ? (100.0 * arg_scale_y) + (arg_scale_y * 7.0) : 0.0) /* pss estimate */
|
|
+ psize + ksize + esize;
|
|
|
|
svg("<?xml version=\"1.0\" standalone=\"no\"?>\n");
|
|
svg("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" ");
|
|
svg("\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
|
|
|
|
//svg("<g transform=\"translate(10,%d)\">\n", 1000 + 150 + (pcount * 20));
|
|
svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" ",
|
|
w, h);
|
|
svg("xmlns=\"http://www.w3.org/2000/svg\">\n\n");
|
|
|
|
/* write some basic info as a comment, including some help */
|
|
svg("<!-- This file is a bootchart SVG file. It is best rendered in a browser -->\n");
|
|
svg("<!-- such as Chrome, Chromium, or Firefox. Other applications that -->\n");
|
|
svg("<!-- render these files properly but more slowly are ImageMagick, gimp, -->\n");
|
|
svg("<!-- inkscape, etc. To display the files on your system, just point -->\n");
|
|
svg("<!-- your browser to file:///run/log/ and click. This bootchart was -->\n\n");
|
|
|
|
svg("<!-- generated by bootchart version %s, running with options: -->\n", VERSION);
|
|
svg("<!-- hz=\"%f\" n=\"%d\" -->\n", arg_hz, arg_samples_len);
|
|
svg("<!-- x=\"%f\" y=\"%f\" -->\n", arg_scale_x, arg_scale_y);
|
|
svg("<!-- rel=\"%d\" f=\"%d\" -->\n", arg_relative, arg_filter);
|
|
svg("<!-- p=\"%d\" e=\"%d\" -->\n", arg_pss, arg_entropy);
|
|
svg("<!-- o=\"%s\" i=\"%s\" -->\n\n", arg_output_path, arg_init_path);
|
|
|
|
/* style sheet */
|
|
svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n");
|
|
|
|
svg(" rect { stroke-width: 1; }\n");
|
|
svg(" rect.cpu { fill: rgb(64,64,240); stroke-width: 0; fill-opacity: 0.7; }\n");
|
|
svg(" rect.wait { fill: rgb(240,240,0); stroke-width: 0; fill-opacity: 0.7; }\n");
|
|
svg(" rect.bi { fill: rgb(240,128,128); stroke-width: 0; fill-opacity: 0.7; }\n");
|
|
svg(" rect.bo { fill: rgb(192,64,64); stroke-width: 0; fill-opacity: 0.7; }\n");
|
|
svg(" rect.ps { fill: rgb(192,192,192); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
|
|
svg(" rect.krnl { fill: rgb(240,240,0); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
|
|
svg(" rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n");
|
|
svg(" rect.clrw { stroke-width: 0; fill-opacity: 0.7;}\n");
|
|
svg(" line { stroke: rgb(64,64,64); stroke-width: 1; }\n");
|
|
svg("// line.sec1 { }\n");
|
|
svg(" line.sec5 { stroke-width: 2; }\n");
|
|
svg(" line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n");
|
|
svg(" line.dot { stroke-dasharray: 2 4; }\n");
|
|
svg(" line.idle { stroke: rgb(64,64,64); stroke-dasharray: 10 6; stroke-opacity: 0.7; }\n");
|
|
|
|
svg(" .run { font-size: 8; font-style: italic; }\n");
|
|
svg(" text { font-family: Verdana, Helvetica; font-size: 10; }\n");
|
|
svg(" text.sec { font-size: 8; }\n");
|
|
svg(" text.t1 { font-size: 24; }\n");
|
|
svg(" text.t2 { font-size: 12; }\n");
|
|
svg(" text.idle { font-size: 18; }\n");
|
|
|
|
svg(" ]]>\n </style>\n</defs>\n\n");
|
|
}
|
|
|
|
static void svg_title(const char *build) {
|
|
char cmdline[256] = "";
|
|
char filename[PATH_MAX];
|
|
char buf[256];
|
|
char rootbdev[16] = "Unknown";
|
|
char model[256] = "Unknown";
|
|
char date[256] = "Unknown";
|
|
char cpu[256] = "Unknown";
|
|
char *c;
|
|
FILE *f;
|
|
time_t t;
|
|
int fd;
|
|
struct utsname uts;
|
|
|
|
/* grab /proc/cmdline */
|
|
fd = openat(procfd, "cmdline", O_RDONLY);
|
|
f = fdopen(fd, "r");
|
|
if (f) {
|
|
if (!fgets(cmdline, 255, f))
|
|
sprintf(cmdline, "Unknown");
|
|
fclose(f);
|
|
}
|
|
|
|
/* extract root fs so we can find disk model name in sysfs */
|
|
/* FIXME: this works only in the simple case */
|
|
c = strstr(cmdline, "root=/dev/");
|
|
if (c) {
|
|
strncpy(rootbdev, &c[10], 3);
|
|
rootbdev[3] = '\0';
|
|
sprintf(filename, "block/%s/device/model", rootbdev);
|
|
fd = openat(sysfd, filename, O_RDONLY);
|
|
f = fdopen(fd, "r");
|
|
if (f) {
|
|
if (!fgets(model, 255, f))
|
|
fprintf(stderr, "Error reading disk model for %s\n", rootbdev);
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
/* various utsname parameters */
|
|
if (uname(&uts))
|
|
fprintf(stderr, "Error getting uname info\n");
|
|
|
|
/* date */
|
|
t = time(NULL);
|
|
strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&t));
|
|
|
|
/* CPU type */
|
|
fd = openat(procfd, "cpuinfo", O_RDONLY);
|
|
f = fdopen(fd, "r");
|
|
if (f) {
|
|
while (fgets(buf, 255, f)) {
|
|
if (strstr(buf, "model name")) {
|
|
strncpy(cpu, &buf[13], 255);
|
|
break;
|
|
}
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
svg("<text class=\"t1\" x=\"0\" y=\"30\">Bootchart for %s - %s</text>\n",
|
|
uts.nodename, date);
|
|
svg("<text class=\"t2\" x=\"20\" y=\"50\">System: %s %s %s %s</text>\n",
|
|
uts.sysname, uts.release, uts.version, uts.machine);
|
|
svg("<text class=\"t2\" x=\"20\" y=\"65\">CPU: %s</text>\n",
|
|
cpu);
|
|
svg("<text class=\"t2\" x=\"20\" y=\"80\">Disk: %s</text>\n",
|
|
model);
|
|
svg("<text class=\"t2\" x=\"20\" y=\"95\">Boot options: %s</text>\n",
|
|
cmdline);
|
|
svg("<text class=\"t2\" x=\"20\" y=\"110\">Build: %s</text>\n",
|
|
build);
|
|
svg("<text class=\"t2\" x=\"20\" y=\"125\">Log start time: %.03fs</text>\n", log_start);
|
|
svg("<text class=\"t2\" x=\"20\" y=\"140\">Idle time: ");
|
|
|
|
if (idletime >= 0.0)
|
|
svg("%.03fs", idletime);
|
|
else
|
|
svg("Not detected");
|
|
svg("</text>\n");
|
|
svg("<text class=\"sec\" x=\"20\" y=\"155\">Graph data: %.03f samples/sec, recorded %i total, dropped %i samples, %i processes, %i filtered</text>\n",
|
|
arg_hz, arg_samples_len, overrun, pscount, pfiltered);
|
|
}
|
|
|
|
static void svg_graph_box(int height) {
|
|
double d = 0.0;
|
|
int i = 0;
|
|
double finalsample = 0.0;
|
|
struct list_sample_data *sampledata_last;
|
|
|
|
sampledata_last = head;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
sampledata_last = sampledata;
|
|
}
|
|
|
|
finalsample = sampledata_last->sampletime;
|
|
|
|
/* outside box, fill */
|
|
svg("<rect class=\"box\" x=\"%.03f\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(0.0),
|
|
time_to_graph(finalsample - graph_start),
|
|
ps_to_graph(height));
|
|
|
|
for (d = graph_start; d <= finalsample;
|
|
d += (arg_scale_x < 2.0 ? 60.0 : arg_scale_x < 10.0 ? 1.0 : 0.1)) {
|
|
/* lines for each second */
|
|
if (i % 50 == 0)
|
|
svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
|
|
time_to_graph(d - graph_start),
|
|
time_to_graph(d - graph_start),
|
|
ps_to_graph(height));
|
|
else if (i % 10 == 0)
|
|
svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
|
|
time_to_graph(d - graph_start),
|
|
time_to_graph(d - graph_start),
|
|
ps_to_graph(height));
|
|
else
|
|
svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
|
|
time_to_graph(d - graph_start),
|
|
time_to_graph(d - graph_start),
|
|
ps_to_graph(height));
|
|
|
|
/* time label */
|
|
if (i % 10 == 0)
|
|
svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
|
|
time_to_graph(d - graph_start),
|
|
-5.0,
|
|
d - graph_start);
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/* xml comments must not contain "--" */
|
|
static char* xml_comment_encode(const char* name) {
|
|
char *enc_name, *p;
|
|
|
|
enc_name = strdup(name);
|
|
if (!enc_name)
|
|
return NULL;
|
|
|
|
for (p = enc_name; *p; p++)
|
|
if (p[0] == '-' && p[1] == '-')
|
|
p[1] = '_';
|
|
|
|
return enc_name;
|
|
}
|
|
|
|
static void svg_pss_graph(void) {
|
|
struct ps_struct *ps;
|
|
int i;
|
|
struct list_sample_data *sampledata_last;
|
|
|
|
sampledata_last = head;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
sampledata_last = sampledata;
|
|
}
|
|
|
|
|
|
svg("\n\n<!-- Pss memory size graph -->\n");
|
|
|
|
svg("\n <text class=\"t2\" x=\"5\" y=\"-15\">Memory allocation - Pss</text>\n");
|
|
|
|
/* vsize 1000 == 1000mb */
|
|
svg_graph_box(100);
|
|
/* draw some hlines for usable memory sizes */
|
|
for (i = 100000; i < 1000000; i += 100000) {
|
|
svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"%.0f\" x2=\"%.03f\" y2=\"%.0f\"/>\n",
|
|
time_to_graph(.0),
|
|
kb_to_graph(i),
|
|
time_to_graph(sampledata_last->sampletime - graph_start),
|
|
kb_to_graph(i));
|
|
svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.0f\">%dM</text>\n",
|
|
time_to_graph(sampledata_last->sampletime - graph_start) + 5,
|
|
kb_to_graph(i), (1000000 - i) / 1000);
|
|
}
|
|
svg("\n");
|
|
|
|
/* now plot the graph itself */
|
|
i = 1;
|
|
prev_sampledata = head;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
int bottom;
|
|
int top;
|
|
struct ps_sched_struct *cross_place;
|
|
|
|
bottom = 0;
|
|
top = 0;
|
|
|
|
/* put all the small pss blocks into the bottom */
|
|
ps = ps_first;
|
|
while (ps->next_ps) {
|
|
ps = ps->next_ps;
|
|
if (!ps)
|
|
continue;
|
|
ps->sample = ps->first;
|
|
while (ps->sample->next) {
|
|
ps->sample = ps->sample->next;
|
|
if (ps->sample->sampledata == sampledata)
|
|
break;
|
|
}
|
|
if (ps->sample->sampledata == sampledata) {
|
|
if (ps->sample->pss <= (100 * arg_scale_y))
|
|
top += ps->sample->pss;
|
|
break;
|
|
}
|
|
}
|
|
while (ps->sample->cross) {
|
|
cross_place = ps->sample->cross;
|
|
ps = ps->sample->cross->ps_new;
|
|
ps->sample = cross_place;
|
|
if (ps->sample->pss <= (100 * arg_scale_y))
|
|
top += ps->sample->pss;
|
|
}
|
|
|
|
svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
"rgb(64,64,64)",
|
|
time_to_graph(prev_sampledata->sampletime - graph_start),
|
|
kb_to_graph(1000000.0 - top),
|
|
time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
|
|
kb_to_graph(top - bottom));
|
|
bottom = top;
|
|
|
|
/* now plot the ones that are of significant size */
|
|
ps = ps_first;
|
|
while (ps->next_ps) {
|
|
ps = ps->next_ps;
|
|
if (!ps)
|
|
continue;
|
|
ps->sample = ps->first;
|
|
while (ps->sample->next) {
|
|
ps->sample = ps->sample->next;
|
|
if (ps->sample->sampledata == sampledata)
|
|
break;
|
|
}
|
|
/* don't draw anything smaller than 2mb */
|
|
if (ps->sample->sampledata == sampledata) {
|
|
if (ps->sample->pss > (100 * arg_scale_y)) {
|
|
top = bottom + ps->sample->pss;
|
|
svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
colorwheel[ps->pid % 12],
|
|
time_to_graph(prev_sampledata->sampletime - graph_start),
|
|
kb_to_graph(1000000.0 - top),
|
|
time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
|
|
kb_to_graph(top - bottom));
|
|
bottom = top;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
while ((cross_place = ps->sample->cross)) {
|
|
ps = ps->sample->cross->ps_new;
|
|
ps->sample = cross_place;
|
|
if (ps->sample->pss > (100 * arg_scale_y)) {
|
|
top = bottom + ps->sample->pss;
|
|
svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
colorwheel[ps->pid % 12],
|
|
time_to_graph(prev_sampledata->sampletime - graph_start),
|
|
kb_to_graph(1000000.0 - top),
|
|
time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
|
|
kb_to_graph(top - bottom));
|
|
bottom = top;
|
|
}
|
|
}
|
|
prev_sampledata = sampledata;
|
|
i++;
|
|
}
|
|
|
|
/* overlay all the text labels */
|
|
i = 1;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
int bottom;
|
|
int top = 0;
|
|
struct ps_sched_struct *prev_sample;
|
|
struct ps_sched_struct *cross_place;
|
|
|
|
/* put all the small pss blocks into the bottom */
|
|
ps = ps_first->next_ps;
|
|
while (ps->next_ps) {
|
|
ps = ps->next_ps;
|
|
if (!ps)
|
|
continue;
|
|
ps->sample = ps->first;
|
|
while (ps->sample->next) {
|
|
ps->sample = ps->sample->next;
|
|
if (ps->sample->sampledata == sampledata)
|
|
break;
|
|
}
|
|
if (ps->sample->sampledata == sampledata) {
|
|
if (ps->sample->pss <= (100 * arg_scale_y))
|
|
top += ps->sample->pss;
|
|
break;
|
|
}
|
|
}
|
|
while ((cross_place = ps->sample->cross)) {
|
|
ps = ps->sample->cross->ps_new;
|
|
ps->sample = cross_place;
|
|
if (ps->sample->pss <= (100 * arg_scale_y))
|
|
top += ps->sample->pss;
|
|
}
|
|
bottom = top;
|
|
|
|
/* now plot the ones that are of significant size */
|
|
ps = ps_first;
|
|
while (ps->next_ps) {
|
|
prev_sample = ps->sample;
|
|
ps = ps->next_ps;
|
|
if (!ps)
|
|
continue;
|
|
ps->sample = ps->first;
|
|
while (ps->sample->next) {
|
|
prev_sample = ps->sample;
|
|
ps->sample = ps->sample->next;
|
|
if (ps->sample->sampledata == sampledata)
|
|
break;
|
|
}
|
|
/* don't draw anything smaller than 2mb */
|
|
if (ps->sample->sampledata == sampledata) {
|
|
if (ps->sample->pss > (100 * arg_scale_y)) {
|
|
top = bottom + ps->sample->pss;
|
|
/* draw a label with the process / PID */
|
|
if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y)))
|
|
svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n",
|
|
time_to_graph(sampledata->sampletime - graph_start),
|
|
kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)),
|
|
ps->name,
|
|
ps->pid);
|
|
bottom = top;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
while ((cross_place = ps->sample->cross)) {
|
|
ps = ps->sample->cross->ps_new;
|
|
ps->sample = cross_place;
|
|
prev_sample = ps->sample->prev;
|
|
if (ps->sample->pss > (100 * arg_scale_y)) {
|
|
top = bottom + ps->sample->pss;
|
|
/* draw a label with the process / PID */
|
|
if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y)))
|
|
svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n",
|
|
time_to_graph(sampledata->sampletime - graph_start),
|
|
kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)),
|
|
ps->name,
|
|
ps->pid);
|
|
bottom = top;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
/* debug output - full data dump */
|
|
svg("\n\n<!-- PSS map - csv format -->\n");
|
|
ps = ps_first;
|
|
while (ps->next_ps) {
|
|
_cleanup_free_ char *enc_name = NULL;
|
|
ps = ps->next_ps;
|
|
if (!ps)
|
|
continue;
|
|
|
|
enc_name = xml_comment_encode(ps->name);
|
|
if(!enc_name)
|
|
continue;
|
|
|
|
svg("<!-- %s [%d] pss=", enc_name, ps->pid);
|
|
|
|
ps->sample = ps->first;
|
|
while (ps->sample->next) {
|
|
ps->sample = ps->sample->next;
|
|
svg("%d," , ps->sample->pss);
|
|
}
|
|
svg(" -->\n");
|
|
}
|
|
|
|
}
|
|
|
|
static void svg_io_bi_bar(void) {
|
|
double max = 0.0;
|
|
double range;
|
|
int max_here = 0;
|
|
int i;
|
|
int k;
|
|
struct list_sample_data *start_sampledata;
|
|
struct list_sample_data *stop_sampledata;
|
|
|
|
svg("<!-- IO utilization graph - In -->\n");
|
|
|
|
svg("<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - read</text>\n");
|
|
|
|
/*
|
|
* calculate rounding range
|
|
*
|
|
* We need to round IO data since IO block data is not updated on
|
|
* each poll. Applying a smoothing function loses some burst data,
|
|
* so keep the smoothing range short.
|
|
*/
|
|
range = 0.25 / (1.0 / arg_hz);
|
|
if (range < 2.0)
|
|
range = 2.0; /* no smoothing */
|
|
|
|
/* surrounding box */
|
|
svg_graph_box(5);
|
|
|
|
/* find the max IO first */
|
|
i = 1;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
int start;
|
|
int stop;
|
|
int diff;
|
|
double tot;
|
|
|
|
start = MAX(i - ((range / 2) - 1), 0);
|
|
stop = MIN(i + (range / 2), samples - 1);
|
|
diff = (stop - start);
|
|
|
|
start_sampledata = sampledata;
|
|
stop_sampledata = sampledata;
|
|
|
|
for (k=0;(k<((range/2)-1))&&(start_sampledata->link_next);k++)
|
|
start_sampledata = start_sampledata->link_next;
|
|
for (k=0;(k<(range/2))&&(stop_sampledata->link_prev);k++)
|
|
stop_sampledata = stop_sampledata->link_prev;
|
|
|
|
tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi)
|
|
/ diff;
|
|
|
|
if (tot > max) {
|
|
max = tot;
|
|
max_here = i;
|
|
}
|
|
|
|
tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo)
|
|
/ diff;
|
|
|
|
if (tot > max)
|
|
max = tot;
|
|
|
|
i++;
|
|
}
|
|
|
|
/* plot bi */
|
|
i = 1;
|
|
prev_sampledata = head;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
int start;
|
|
int stop;
|
|
int diff;
|
|
double tot;
|
|
double pbi = 0;
|
|
|
|
start = MAX(i - ((range / 2) - 1), 0);
|
|
stop = MIN(i + (range / 2), samples);
|
|
diff = (stop - start);
|
|
|
|
start_sampledata = sampledata;
|
|
stop_sampledata = sampledata;
|
|
|
|
for (k=0;(k<((range/2)-1))&&(start_sampledata->link_next);k++)
|
|
start_sampledata = start_sampledata->link_next;
|
|
for (k=0;(k<(range/2))&&(stop_sampledata->link_prev);k++)
|
|
stop_sampledata = stop_sampledata->link_prev;
|
|
|
|
tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi)
|
|
/ diff;
|
|
|
|
if (max > 0)
|
|
pbi = tot / max;
|
|
|
|
if (pbi > 0.001)
|
|
svg("<rect class=\"bi\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(prev_sampledata->sampletime - graph_start),
|
|
(arg_scale_y * 5) - (pbi * (arg_scale_y * 5)),
|
|
time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
|
|
pbi * (arg_scale_y * 5));
|
|
|
|
/* labels around highest value */
|
|
if (i == max_here) {
|
|
svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
|
|
time_to_graph(sampledata->sampletime - graph_start) + 5,
|
|
((arg_scale_y * 5) - (pbi * (arg_scale_y * 5))) + 15,
|
|
max / 1024.0 / (interval / 1000000000.0));
|
|
}
|
|
i++;
|
|
prev_sampledata = sampledata;
|
|
}
|
|
}
|
|
|
|
static void svg_io_bo_bar(void) {
|
|
double max = 0.0;
|
|
double range;
|
|
int max_here = 0;
|
|
int i;
|
|
int k;
|
|
struct list_sample_data *start_sampledata;
|
|
struct list_sample_data *stop_sampledata;
|
|
|
|
svg("<!-- IO utilization graph - out -->\n");
|
|
|
|
svg("<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - write</text>\n");
|
|
|
|
/*
|
|
* calculate rounding range
|
|
*
|
|
* We need to round IO data since IO block data is not updated on
|
|
* each poll. Applying a smoothing function loses some burst data,
|
|
* so keep the smoothing range short.
|
|
*/
|
|
range = 0.25 / (1.0 / arg_hz);
|
|
if (range < 2.0)
|
|
range = 2.0; /* no smoothing */
|
|
|
|
/* surrounding box */
|
|
svg_graph_box(5);
|
|
|
|
/* find the max IO first */
|
|
i = 0;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
int start;
|
|
int stop;
|
|
int diff;
|
|
double tot;
|
|
|
|
start = MAX(i - ((range / 2) - 1), 0);
|
|
stop = MIN(i + (range / 2), samples - 1);
|
|
diff = (stop - start);
|
|
|
|
start_sampledata = sampledata;
|
|
stop_sampledata = sampledata;
|
|
|
|
for (k=0;(k<((range/2)-1))&&(start_sampledata->link_next);k++)
|
|
start_sampledata = start_sampledata->link_next;
|
|
for (k=0;(k<(range/2))&&(stop_sampledata->link_prev);k++)
|
|
stop_sampledata = stop_sampledata->link_prev;
|
|
|
|
tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi)
|
|
/ diff;
|
|
if (tot > max)
|
|
max = tot;
|
|
tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo)
|
|
/ diff;
|
|
if (tot > max) {
|
|
max = tot;
|
|
max_here = i;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
/* plot bo */
|
|
prev_sampledata = head;
|
|
i=1;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
int start;
|
|
int stop;
|
|
int diff;
|
|
double tot;
|
|
double pbo;
|
|
|
|
tot = 0;
|
|
pbo = 0;
|
|
|
|
start = MAX(i - ((range / 2) - 1), 0);
|
|
stop = MIN(i + (range / 2), samples);
|
|
diff = (stop - start);
|
|
|
|
start_sampledata = sampledata;
|
|
stop_sampledata = sampledata;
|
|
|
|
for (k=0;(k<((range/2)-1))&&(start_sampledata->link_next);k++)
|
|
start_sampledata = start_sampledata->link_next;
|
|
for (k=0;(k<(range/2))&&(stop_sampledata->link_prev);k++)
|
|
stop_sampledata = stop_sampledata->link_prev;
|
|
|
|
tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo)
|
|
/ diff;
|
|
|
|
if (max > 0)
|
|
pbo = tot / max;
|
|
|
|
if (pbo > 0.001)
|
|
svg("<rect class=\"bo\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(prev_sampledata->sampletime - graph_start),
|
|
(arg_scale_y * 5) - (pbo * (arg_scale_y * 5)),
|
|
time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
|
|
pbo * (arg_scale_y * 5));
|
|
|
|
/* labels around highest bo value */
|
|
if (i == max_here) {
|
|
svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
|
|
time_to_graph(sampledata->sampletime - graph_start) + 5,
|
|
((arg_scale_y * 5) - (pbo * (arg_scale_y * 5))),
|
|
max / 1024.0 / (interval / 1000000000.0));
|
|
}
|
|
i++;
|
|
prev_sampledata = sampledata;
|
|
}
|
|
}
|
|
|
|
static void svg_cpu_bar(void) {
|
|
|
|
svg("<!-- CPU utilization graph -->\n");
|
|
|
|
svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU utilization</text>\n");
|
|
/* surrounding box */
|
|
svg_graph_box(5);
|
|
|
|
/* bars for each sample, proportional to the CPU util. */
|
|
prev_sampledata = head;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
int c;
|
|
double trt;
|
|
double ptrt;
|
|
|
|
ptrt = trt = 0.0;
|
|
|
|
for (c = 0; c < cpus; c++)
|
|
trt += sampledata->runtime[c] - prev_sampledata->runtime[c];
|
|
|
|
trt = trt / 1000000000.0;
|
|
|
|
trt = trt / (double)cpus;
|
|
|
|
if (trt > 0.0)
|
|
ptrt = trt / (sampledata->sampletime - prev_sampledata->sampletime);
|
|
|
|
if (ptrt > 1.0)
|
|
ptrt = 1.0;
|
|
|
|
if (ptrt > 0.001) {
|
|
svg("<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(prev_sampledata->sampletime - graph_start),
|
|
(arg_scale_y * 5) - (ptrt * (arg_scale_y * 5)),
|
|
time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
|
|
ptrt * (arg_scale_y * 5));
|
|
}
|
|
prev_sampledata = sampledata;
|
|
}
|
|
}
|
|
|
|
static void svg_wait_bar(void) {
|
|
|
|
svg("<!-- Wait time aggregation box -->\n");
|
|
|
|
svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU wait</text>\n");
|
|
|
|
/* surrounding box */
|
|
svg_graph_box(5);
|
|
|
|
/* bars for each sample, proportional to the CPU util. */
|
|
prev_sampledata = head;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
int c;
|
|
double twt;
|
|
double ptwt;
|
|
|
|
ptwt = twt = 0.0;
|
|
|
|
for (c = 0; c < cpus; c++)
|
|
twt += sampledata->waittime[c] - prev_sampledata->waittime[c];
|
|
|
|
twt = twt / 1000000000.0;
|
|
|
|
twt = twt / (double)cpus;
|
|
|
|
if (twt > 0.0)
|
|
ptwt = twt / (sampledata->sampletime - prev_sampledata->sampletime);
|
|
|
|
if (ptwt > 1.0)
|
|
ptwt = 1.0;
|
|
|
|
if (ptwt > 0.001) {
|
|
svg("<rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(prev_sampledata->sampletime - graph_start),
|
|
((arg_scale_y * 5) - (ptwt * (arg_scale_y * 5))),
|
|
time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
|
|
ptwt * (arg_scale_y * 5));
|
|
}
|
|
prev_sampledata = sampledata;
|
|
}
|
|
}
|
|
|
|
|
|
static void svg_entropy_bar(void) {
|
|
|
|
svg("<!-- entropy pool graph -->\n");
|
|
|
|
svg("<text class=\"t2\" x=\"5\" y=\"-15\">Entropy pool size</text>\n");
|
|
/* surrounding box */
|
|
svg_graph_box(5);
|
|
|
|
/* bars for each sample, scale 0-4096 */
|
|
prev_sampledata = head;
|
|
LIST_FOREACH_BEFORE(link, sampledata, head) {
|
|
/* svg("<!-- entropy %.03f %i -->\n", sampletime[i], entropy_avail[i]); */
|
|
svg("<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(prev_sampledata->sampletime - graph_start),
|
|
((arg_scale_y * 5) - ((sampledata->entropy_avail / 4096.) * (arg_scale_y * 5))),
|
|
time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
|
|
(sampledata->entropy_avail / 4096.) * (arg_scale_y * 5));
|
|
prev_sampledata = sampledata;
|
|
}
|
|
}
|
|
|
|
static struct ps_struct *get_next_ps(struct ps_struct *ps) {
|
|
/*
|
|
* walk the list of processes and return the next one to be
|
|
* painted
|
|
*/
|
|
if (ps == ps_first)
|
|
return ps->next_ps;
|
|
|
|
/* go deep */
|
|
if (ps->children)
|
|
return ps->children;
|
|
|
|
/* find siblings */
|
|
if (ps->next)
|
|
return ps->next;
|
|
|
|
/* go back for parent siblings */
|
|
while (1) {
|
|
if (ps->parent)
|
|
if (ps->parent->next)
|
|
return ps->parent->next;
|
|
ps = ps->parent;
|
|
if (!ps)
|
|
return ps;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int ps_filter(struct ps_struct *ps) {
|
|
if (!arg_filter)
|
|
return 0;
|
|
|
|
/* can't draw data when there is only 1 sample (need start + stop) */
|
|
if (ps->first == ps->last)
|
|
return -1;
|
|
|
|
/* don't filter kthreadd */
|
|
if (ps->pid == 2)
|
|
return 0;
|
|
|
|
/* drop stuff that doesn't use any real CPU time */
|
|
if (ps->total <= 0.001)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void svg_do_initcall(int count_only) {
|
|
_cleanup_pclose_ FILE *f = NULL;
|
|
double t;
|
|
char func[256];
|
|
int ret;
|
|
int usecs;
|
|
|
|
/* can't plot initcall when disabled or in relative mode */
|
|
if (!initcall || arg_relative) {
|
|
kcount = 0;
|
|
return;
|
|
}
|
|
|
|
if (!count_only) {
|
|
svg("<!-- initcall -->\n");
|
|
|
|
svg("<text class=\"t2\" x=\"5\" y=\"-15\">Kernel init threads</text>\n");
|
|
/* surrounding box */
|
|
svg_graph_box(kcount);
|
|
}
|
|
|
|
kcount = 0;
|
|
|
|
/*
|
|
* Initcall graphing - parses dmesg buffer and displays kernel threads
|
|
* This somewhat uses the same methods and scaling to show processes
|
|
* but looks a lot simpler. It's overlaid entirely onto the PS graph
|
|
* when appropriate.
|
|
*/
|
|
|
|
f = popen("dmesg", "r");
|
|
if (!f)
|
|
return;
|
|
|
|
while (!feof(f)) {
|
|
int c;
|
|
int z = 0;
|
|
char l[256];
|
|
|
|
if (fgets(l, sizeof(l) - 1, f) == NULL)
|
|
continue;
|
|
|
|
c = sscanf(l, "[%lf] initcall %s %*s %d %*s %d %*s",
|
|
&t, func, &ret, &usecs);
|
|
if (c != 4) {
|
|
/* also parse initcalls done by module loading */
|
|
c = sscanf(l, "[%lf] initcall %s %*s %*s %d %*s %d %*s",
|
|
&t, func, &ret, &usecs);
|
|
if (c != 4)
|
|
continue;
|
|
}
|
|
|
|
/* chop the +0xXX/0xXX stuff */
|
|
while(func[z] != '+')
|
|
z++;
|
|
func[z] = 0;
|
|
|
|
if (count_only) {
|
|
/* filter out irrelevant stuff */
|
|
if (usecs >= 1000)
|
|
kcount++;
|
|
continue;
|
|
}
|
|
|
|
svg("<!-- thread=\"%s\" time=\"%.3f\" elapsed=\"%d\" result=\"%d\" -->\n",
|
|
func, t, usecs, ret);
|
|
|
|
if (usecs < 1000)
|
|
continue;
|
|
|
|
/* rect */
|
|
svg(" <rect class=\"krnl\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(t - (usecs / 1000000.0)),
|
|
ps_to_graph(kcount),
|
|
time_to_graph(usecs / 1000000.0),
|
|
ps_to_graph(1));
|
|
|
|
/* label */
|
|
svg(" <text x=\"%.03f\" y=\"%.03f\">%s <tspan class=\"run\">%.03fs</tspan></text>\n",
|
|
time_to_graph(t - (usecs / 1000000.0)) + 5,
|
|
ps_to_graph(kcount) + 15,
|
|
func,
|
|
usecs / 1000000.0);
|
|
|
|
kcount++;
|
|
}
|
|
}
|
|
|
|
static void svg_ps_bars(void) {
|
|
struct ps_struct *ps;
|
|
int i = 0;
|
|
int j = 0;
|
|
int pid;
|
|
double w = 0.0;
|
|
|
|
svg("<!-- Process graph -->\n");
|
|
|
|
svg("<text class=\"t2\" x=\"5\" y=\"-15\">Processes</text>\n");
|
|
|
|
/* surrounding box */
|
|
svg_graph_box(pcount);
|
|
|
|
/* pass 2 - ps boxes */
|
|
ps = ps_first;
|
|
while ((ps = get_next_ps(ps))) {
|
|
_cleanup_free_ char *enc_name = NULL;
|
|
double endtime;
|
|
double starttime;
|
|
int t;
|
|
|
|
enc_name = xml_comment_encode(ps->name);
|
|
if(!enc_name)
|
|
continue;
|
|
|
|
/* leave some trace of what we actually filtered etc. */
|
|
svg("<!-- %s [%i] ppid=%i runtime=%.03fs -->\n", enc_name, ps->pid,
|
|
ps->ppid, ps->total);
|
|
|
|
starttime = ps->first->sampledata->sampletime;
|
|
|
|
if (!ps_filter(ps)) {
|
|
/* remember where _to_ our children need to draw a line */
|
|
ps->pos_x = time_to_graph(starttime - graph_start);
|
|
ps->pos_y = ps_to_graph(j+1); /* bottom left corner */
|
|
} else if (ps->parent){
|
|
/* hook children to our parent coords instead */
|
|
ps->pos_x = ps->parent->pos_x;
|
|
ps->pos_y = ps->parent->pos_y;
|
|
|
|
/* if this is the last child, we might still need to draw a connecting line */
|
|
if ((!ps->next) && (ps->parent))
|
|
svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
|
|
ps->parent->pos_x,
|
|
ps_to_graph(j-1) + 10.0, /* whee, use the last value here */
|
|
ps->parent->pos_x,
|
|
ps->parent->pos_y);
|
|
continue;
|
|
}
|
|
|
|
endtime = ps->last->sampledata->sampletime;
|
|
svg(" <rect class=\"ps\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(starttime - graph_start),
|
|
ps_to_graph(j),
|
|
time_to_graph(ps->last->sampledata->sampletime - starttime),
|
|
ps_to_graph(1));
|
|
|
|
/* paint cpu load over these */
|
|
ps->sample = ps->first;
|
|
t = 1;
|
|
while (ps->sample->next) {
|
|
double rt, prt;
|
|
double wt, wrt;
|
|
struct ps_sched_struct *prev;
|
|
|
|
prev = ps->sample;
|
|
ps->sample = ps->sample->next;
|
|
|
|
/* calculate over interval */
|
|
rt = ps->sample->runtime - prev->runtime;
|
|
wt = ps->sample->waittime - prev->waittime;
|
|
|
|
prt = (rt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime);
|
|
wrt = (wt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime);
|
|
|
|
/* this can happen if timekeeping isn't accurate enough */
|
|
if (prt > 1.0)
|
|
prt = 1.0;
|
|
if (wrt > 1.0)
|
|
wrt = 1.0;
|
|
|
|
if ((prt < 0.1) && (wrt < 0.1)) /* =~ 26 (color threshold) */
|
|
continue;
|
|
|
|
svg(" <rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(prev->sampledata->sampletime - graph_start),
|
|
ps_to_graph(j),
|
|
time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime),
|
|
ps_to_graph(wrt));
|
|
|
|
/* draw cpu over wait - TODO figure out how/why run + wait > interval */
|
|
svg(" <rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
|
|
time_to_graph(prev->sampledata->sampletime - graph_start),
|
|
ps_to_graph(j + (1.0 - prt)),
|
|
time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime),
|
|
ps_to_graph(prt));
|
|
t++;
|
|
}
|
|
|
|
/* determine where to display the process name */
|
|
if ((endtime - starttime) < 1.5)
|
|
/* too small to fit label inside the box */
|
|
w = endtime;
|
|
else
|
|
w = starttime;
|
|
|
|
/* text label of process name */
|
|
svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]<tspan class=\"run\">%.03fs</tspan></text>\n",
|
|
time_to_graph(w - graph_start) + 5.0,
|
|
ps_to_graph(j) + 14.0,
|
|
ps->name,
|
|
ps->pid,
|
|
(ps->last->runtime - ps->first->runtime) / 1000000000.0);
|
|
/* paint lines to the parent process */
|
|
if (ps->parent) {
|
|
/* horizontal part */
|
|
svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
|
|
time_to_graph(starttime - graph_start),
|
|
ps_to_graph(j) + 10.0,
|
|
ps->parent->pos_x,
|
|
ps_to_graph(j) + 10.0);
|
|
|
|
/* one vertical line connecting all the horizontal ones up */
|
|
if (!ps->next)
|
|
svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
|
|
ps->parent->pos_x,
|
|
ps_to_graph(j) + 10.0,
|
|
ps->parent->pos_x,
|
|
ps->parent->pos_y);
|
|
}
|
|
|
|
j++; /* count boxes */
|
|
|
|
svg("\n");
|
|
}
|
|
|
|
/* last pass - determine when idle */
|
|
pid = getpid();
|
|
/* make sure we start counting from the point where we actually have
|
|
* data: assume that bootchart's first sample is when data started
|
|
*/
|
|
|
|
ps = ps_first;
|
|
while (ps->next_ps) {
|
|
ps = ps->next_ps;
|
|
if (ps->pid == pid)
|
|
break;
|
|
}
|
|
|
|
/* need to know last node first */
|
|
ps->sample = ps->first;
|
|
i = ps->sample->next->sampledata->counter;
|
|
|
|
while (ps->sample->next && i<(samples-(arg_hz/2))) {
|
|
double crt;
|
|
double brt;
|
|
int c;
|
|
int ii;
|
|
struct ps_sched_struct *sample_hz;
|
|
|
|
ps->sample = ps->sample->next;
|
|
sample_hz = ps->sample;
|
|
for (ii=0;((ii<(int)arg_hz/2)&&(ps->sample->next));ii++)
|
|
sample_hz = sample_hz->next;
|
|
|
|
/* subtract bootchart cpu utilization from total */
|
|
crt = 0.0;
|
|
for (c = 0; c < cpus; c++)
|
|
crt += sample_hz->sampledata->runtime[c] - ps->sample->sampledata->runtime[c];
|
|
brt = sample_hz->runtime - ps->sample->runtime;
|
|
/*
|
|
* our definition of "idle":
|
|
*
|
|
* if for (hz / 2) we've used less CPU than (interval / 2) ...
|
|
* defaults to 4.0%, which experimentally, is where atom idles
|
|
*/
|
|
if ((crt - brt) < (interval / 2.0)) {
|
|
idletime = ps->sample->sampledata->sampletime - graph_start;
|
|
svg("\n<!-- idle detected at %.03f seconds -->\n",
|
|
idletime);
|
|
svg("<line class=\"idle\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
|
|
time_to_graph(idletime),
|
|
-arg_scale_y,
|
|
time_to_graph(idletime),
|
|
ps_to_graph(pcount) + arg_scale_y);
|
|
svg("<text class=\"idle\" x=\"%.03f\" y=\"%.03f\">%.01fs</text>\n",
|
|
time_to_graph(idletime) + 5.0,
|
|
ps_to_graph(pcount) + arg_scale_y,
|
|
idletime);
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static void svg_top_ten_cpu(void) {
|
|
struct ps_struct *top[10];
|
|
struct ps_struct emptyps = {};
|
|
struct ps_struct *ps;
|
|
int n, m;
|
|
|
|
for (n = 0; n < (int) ELEMENTSOF(top); n++)
|
|
top[n] = &emptyps;
|
|
|
|
/* walk all ps's and setup ptrs */
|
|
ps = ps_first;
|
|
while ((ps = get_next_ps(ps))) {
|
|
for (n = 0; n < 10; n++) {
|
|
if (ps->total <= top[n]->total)
|
|
continue;
|
|
/* cascade insert */
|
|
for (m = 9; m > n; m--)
|
|
top[m] = top[m-1];
|
|
top[n] = ps;
|
|
break;
|
|
}
|
|
}
|
|
|
|
svg("<text class=\"t2\" x=\"20\" y=\"0\">Top CPU consumers:</text>\n");
|
|
for (n = 0; n < 10; n++)
|
|
svg("<text class=\"t3\" x=\"20\" y=\"%d\">%3.03fs - <![CDATA[%s]]> [%d]</text>\n",
|
|
20 + (n * 13),
|
|
top[n]->total,
|
|
top[n]->name,
|
|
top[n]->pid);
|
|
}
|
|
|
|
static void svg_top_ten_pss(void) {
|
|
struct ps_struct *top[10];
|
|
struct ps_struct emptyps = {};
|
|
struct ps_struct *ps;
|
|
int n, m;
|
|
|
|
for (n = 0; n < (int) ELEMENTSOF(top); n++)
|
|
top[n] = &emptyps;
|
|
|
|
/* walk all ps's and setup ptrs */
|
|
ps = ps_first;
|
|
while ((ps = get_next_ps(ps))) {
|
|
for (n = 0; n < 10; n++) {
|
|
if (ps->pss_max <= top[n]->pss_max)
|
|
continue;
|
|
/* cascade insert */
|
|
for (m = 9; m > n; m--)
|
|
top[m] = top[m-1];
|
|
top[n] = ps;
|
|
break;
|
|
}
|
|
}
|
|
|
|
svg("<text class=\"t2\" x=\"20\" y=\"0\">Top PSS consumers:</text>\n");
|
|
for (n = 0; n < 10; n++)
|
|
svg("<text class=\"t3\" x=\"20\" y=\"%d\">%dK - <![CDATA[%s]]> [%d]</text>\n",
|
|
20 + (n * 13),
|
|
top[n]->pss_max,
|
|
top[n]->name,
|
|
top[n]->pid);
|
|
}
|
|
|
|
void svg_do(const char *build) {
|
|
struct ps_struct *ps;
|
|
|
|
memset(&str, 0, sizeof(str));
|
|
|
|
ps = ps_first;
|
|
|
|
/* count initcall thread count first */
|
|
svg_do_initcall(1);
|
|
ksize = (kcount ? ps_to_graph(kcount) + (arg_scale_y * 2) : 0);
|
|
|
|
/* then count processes */
|
|
while ((ps = get_next_ps(ps))) {
|
|
if (!ps_filter(ps))
|
|
pcount++;
|
|
else
|
|
pfiltered++;
|
|
}
|
|
psize = ps_to_graph(pcount) + (arg_scale_y * 2);
|
|
|
|
esize = (arg_entropy ? arg_scale_y * 7 : 0);
|
|
|
|
/* after this, we can draw the header with proper sizing */
|
|
svg_header();
|
|
|
|
svg("<g transform=\"translate(10,400)\">\n");
|
|
svg_io_bi_bar();
|
|
svg("</g>\n\n");
|
|
|
|
svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 7.0));
|
|
svg_io_bo_bar();
|
|
svg("</g>\n\n");
|
|
|
|
svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 14.0));
|
|
svg_cpu_bar();
|
|
svg("</g>\n\n");
|
|
|
|
svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 21.0));
|
|
svg_wait_bar();
|
|
svg("</g>\n\n");
|
|
|
|
if (kcount) {
|
|
svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 28.0));
|
|
svg_do_initcall(0);
|
|
svg("</g>\n\n");
|
|
}
|
|
|
|
svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 28.0) + ksize);
|
|
svg_ps_bars();
|
|
svg("</g>\n\n");
|
|
|
|
svg("<g transform=\"translate(10, 0)\">\n");
|
|
svg_title(build);
|
|
svg("</g>\n\n");
|
|
|
|
svg("<g transform=\"translate(10,200)\">\n");
|
|
svg_top_ten_cpu();
|
|
svg("</g>\n\n");
|
|
|
|
if (arg_entropy) {
|
|
svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 28.0) + ksize + psize);
|
|
svg_entropy_bar();
|
|
svg("</g>\n\n");
|
|
}
|
|
|
|
if (arg_pss) {
|
|
svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 28.0) + ksize + psize + esize);
|
|
svg_pss_graph();
|
|
svg("</g>\n\n");
|
|
|
|
svg("<g transform=\"translate(410,200)\">\n");
|
|
svg_top_ten_pss();
|
|
svg("</g>\n\n");
|
|
}
|
|
|
|
/* svg footer */
|
|
svg("\n</svg>\n");
|
|
}
|