1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2009-2013 Intel Corporation
9 Auke Kok <auke-jan.h.kok@intel.com>
11 systemd is free software; you can redistribute it and/or modify it
12 under the terms of the GNU Lesser General Public License as published by
13 the Free Software Foundation; either version 2.1 of the License, or
14 (at your option) any later version.
16 systemd is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public License
22 along with systemd; If not, see <http://www.gnu.org/licenses/>.
30 #include <sys/utsname.h>
37 #include "bootchart.h"
41 #define time_to_graph(t) ((t) * arg_scale_x)
42 #define ps_to_graph(n) ((n) * arg_scale_y)
43 #define kb_to_graph(m) ((m) * arg_scale_y * 0.0001)
44 #define to_color(n) (192.0 - ((n) * 192.0))
46 static char str[8092];
48 #define svg(a...) do { snprintf(str, 8092, ## a); fputs(str, of); fflush(of); } while (0)
50 static const char * const colorwheel[12] = {
51 "rgb(255,32,32)", // red
52 "rgb(32,192,192)", // cyan
53 "rgb(255,128,32)", // orange
54 "rgb(128,32,192)", // blue-violet
55 "rgb(255,255,32)", // yellow
56 "rgb(192,32,128)", // red-violet
57 "rgb(32,255,32)", // green
58 "rgb(255,64,32)", // red-orange
59 "rgb(32,32,255)", // blue
60 "rgb(255,192,32)", // yellow-orange
61 "rgb(192,32,192)", // violet
62 "rgb(32,192,32)" // yellow-green
65 static double idletime = -1.0;
66 static int pfiltered = 0;
67 static int pcount = 0;
68 static int kcount = 0;
69 static double psize = 0;
70 static double ksize = 0;
71 static double esize = 0;
72 static struct list_sample_data *sampledata;
73 static struct list_sample_data *prev_sampledata;
74 extern struct list_sample_data *head;
76 static void svg_header(void) {
79 struct list_sample_data *sampledata_last;
84 LIST_FIND_TAIL(link, sampledata, head);
85 sampledata_last = head;
86 LIST_FOREACH_BEFORE(link, sampledata, head) {
87 sampledata_last = sampledata;
90 /* min width is about 1600px due to the label */
91 w = 150.0 + 10.0 + time_to_graph(sampledata_last->sampletime - graph_start);
92 w = ((w < 1600.0) ? 1600.0 : w);
94 /* height is variable based on pss, psize, ksize */
95 h = 400.0 + (arg_scale_y * 30.0) /* base graphs and title */
96 + (arg_pss ? (100.0 * arg_scale_y) + (arg_scale_y * 7.0) : 0.0) /* pss estimate */
97 + psize + ksize + esize;
99 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n");
100 svg("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" ");
101 svg("\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
103 //svg("<g transform=\"translate(10,%d)\">\n", 1000 + 150 + (pcount * 20));
104 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" ",
106 svg("xmlns=\"http://www.w3.org/2000/svg\">\n\n");
108 /* write some basic info as a comment, including some help */
109 svg("<!-- This file is a bootchart SVG file. It is best rendered in a browser -->\n");
110 svg("<!-- such as Chrome, Chromium, or Firefox. Other applications that -->\n");
111 svg("<!-- render these files properly but more slowly are ImageMagick, gimp, -->\n");
112 svg("<!-- inkscape, etc. To display the files on your system, just point -->\n");
113 svg("<!-- your browser to file:///run/log/ and click. This bootchart was -->\n\n");
115 svg("<!-- generated by bootchart version %s, running with options: -->\n", VERSION);
116 svg("<!-- hz=\"%f\" n=\"%d\" -->\n", arg_hz, arg_samples_len);
117 svg("<!-- x=\"%f\" y=\"%f\" -->\n", arg_scale_x, arg_scale_y);
118 svg("<!-- rel=\"%d\" f=\"%d\" -->\n", arg_relative, arg_filter);
119 svg("<!-- p=\"%d\" e=\"%d\" -->\n", arg_pss, arg_entropy);
120 svg("<!-- o=\"%s\" i=\"%s\" -->\n\n", arg_output_path, arg_init_path);
123 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n");
125 svg(" rect { stroke-width: 1; }\n");
126 svg(" rect.bg { fill: rgb(255,255,255); }\n");
127 svg(" rect.cpu { fill: rgb(64,64,240); stroke-width: 0; fill-opacity: 0.7; }\n");
128 svg(" rect.wait { fill: rgb(240,240,0); stroke-width: 0; fill-opacity: 0.7; }\n");
129 svg(" rect.bi { fill: rgb(240,128,128); stroke-width: 0; fill-opacity: 0.7; }\n");
130 svg(" rect.bo { fill: rgb(192,64,64); stroke-width: 0; fill-opacity: 0.7; }\n");
131 svg(" rect.ps { fill: rgb(192,192,192); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
132 svg(" rect.krnl { fill: rgb(240,240,0); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
133 svg(" rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n");
134 svg(" rect.clrw { stroke-width: 0; fill-opacity: 0.7;}\n");
135 svg(" line { stroke: rgb(64,64,64); stroke-width: 1; }\n");
136 svg("// line.sec1 { }\n");
137 svg(" line.sec5 { stroke-width: 2; }\n");
138 svg(" line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n");
139 svg(" line.dot { stroke-dasharray: 2 4; }\n");
140 svg(" line.idle { stroke: rgb(64,64,64); stroke-dasharray: 10 6; stroke-opacity: 0.7; }\n");
142 svg(" .run { font-size: 8; font-style: italic; }\n");
143 svg(" text { font-family: Verdana, Helvetica; font-size: 10; }\n");
144 svg(" text.sec { font-size: 8; }\n");
145 svg(" text.t1 { font-size: 24; }\n");
146 svg(" text.t2 { font-size: 12; }\n");
147 svg(" text.idle { font-size: 18; }\n");
149 svg(" ]]>\n </style>\n</defs>\n\n");
152 static void svg_title(const char *build) {
153 char cmdline[256] = "";
154 char filename[PATH_MAX];
156 char rootbdev[16] = "Unknown";
157 char model[256] = "Unknown";
158 char date[256] = "Unknown";
159 char cpu[256] = "Unknown";
166 /* grab /proc/cmdline */
167 fd = openat(procfd, "cmdline", O_RDONLY);
170 if (!fgets(cmdline, 255, f))
171 sprintf(cmdline, "Unknown");
175 /* extract root fs so we can find disk model name in sysfs */
176 /* FIXME: this works only in the simple case */
177 c = strstr(cmdline, "root=/dev/");
179 strncpy(rootbdev, &c[10], 3);
181 sprintf(filename, "block/%s/device/model", rootbdev);
182 fd = openat(sysfd, filename, O_RDONLY);
185 if (!fgets(model, 255, f))
186 fprintf(stderr, "Error reading disk model for %s\n", rootbdev);
191 /* various utsname parameters */
193 fprintf(stderr, "Error getting uname info\n");
197 r = strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&t));
201 fd = openat(procfd, "cpuinfo", O_RDONLY);
204 while (fgets(buf, 255, f)) {
205 if (strstr(buf, "model name")) {
206 strncpy(cpu, &buf[13], 255);
213 svg("<text class=\"t1\" x=\"0\" y=\"30\">Bootchart for %s - %s</text>\n",
215 svg("<text class=\"t2\" x=\"20\" y=\"50\">System: %s %s %s %s</text>\n",
216 uts.sysname, uts.release, uts.version, uts.machine);
217 svg("<text class=\"t2\" x=\"20\" y=\"65\">CPU: %s</text>\n",
219 svg("<text class=\"t2\" x=\"20\" y=\"80\">Disk: %s</text>\n",
221 svg("<text class=\"t2\" x=\"20\" y=\"95\">Boot options: %s</text>\n",
223 svg("<text class=\"t2\" x=\"20\" y=\"110\">Build: %s</text>\n",
225 svg("<text class=\"t2\" x=\"20\" y=\"125\">Log start time: %.03fs</text>\n", log_start);
226 svg("<text class=\"t2\" x=\"20\" y=\"140\">Idle time: ");
229 svg("%.03fs", idletime);
233 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",
234 arg_hz, arg_samples_len, overrun, pscount, pfiltered);
237 static void svg_graph_box(int height) {
240 double finalsample = 0.0;
241 struct list_sample_data *sampledata_last;
243 sampledata_last = head;
244 LIST_FOREACH_BEFORE(link, sampledata, head) {
245 sampledata_last = sampledata;
248 finalsample = sampledata_last->sampletime;
250 /* outside box, fill */
251 svg("<rect class=\"box\" x=\"%.03f\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
253 time_to_graph(finalsample - graph_start),
254 ps_to_graph(height));
256 for (d = graph_start; d <= finalsample;
257 d += (arg_scale_x < 2.0 ? 60.0 : arg_scale_x < 10.0 ? 1.0 : 0.1)) {
258 /* lines for each second */
260 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
261 time_to_graph(d - graph_start),
262 time_to_graph(d - graph_start),
263 ps_to_graph(height));
264 else if (i % 10 == 0)
265 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
266 time_to_graph(d - graph_start),
267 time_to_graph(d - graph_start),
268 ps_to_graph(height));
270 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
271 time_to_graph(d - graph_start),
272 time_to_graph(d - graph_start),
273 ps_to_graph(height));
277 svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
278 time_to_graph(d - graph_start),
286 /* xml comments must not contain "--" */
287 static char* xml_comment_encode(const char* name) {
290 enc_name = strdup(name);
294 for (p = enc_name; *p; p++)
295 if (p[0] == '-' && p[1] == '-')
301 static void svg_pss_graph(void) {
302 struct ps_struct *ps;
304 struct list_sample_data *sampledata_last;
306 sampledata_last = head;
307 LIST_FOREACH_BEFORE(link, sampledata, head) {
308 sampledata_last = sampledata;
312 svg("\n\n<!-- Pss memory size graph -->\n");
314 svg("\n <text class=\"t2\" x=\"5\" y=\"-15\">Memory allocation - Pss</text>\n");
316 /* vsize 1000 == 1000mb */
318 /* draw some hlines for usable memory sizes */
319 for (i = 100000; i < 1000000; i += 100000) {
320 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"%.0f\" x2=\"%.03f\" y2=\"%.0f\"/>\n",
323 time_to_graph(sampledata_last->sampletime - graph_start),
325 svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.0f\">%dM</text>\n",
326 time_to_graph(sampledata_last->sampletime - graph_start) + 5,
327 kb_to_graph(i), (1000000 - i) / 1000);
331 /* now plot the graph itself */
333 prev_sampledata = head;
334 LIST_FOREACH_BEFORE(link, sampledata, head) {
337 struct ps_sched_struct *cross_place;
342 /* put all the small pss blocks into the bottom */
344 while (ps->next_ps) {
348 ps->sample = ps->first;
349 while (ps->sample->next) {
350 ps->sample = ps->sample->next;
351 if (ps->sample->sampledata == sampledata)
354 if (ps->sample->sampledata == sampledata) {
355 if (ps->sample->pss <= (100 * arg_scale_y))
356 top += ps->sample->pss;
360 while (ps->sample->cross) {
361 cross_place = ps->sample->cross;
362 ps = ps->sample->cross->ps_new;
363 ps->sample = cross_place;
364 if (ps->sample->pss <= (100 * arg_scale_y))
365 top += ps->sample->pss;
368 svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
370 time_to_graph(prev_sampledata->sampletime - graph_start),
371 kb_to_graph(1000000.0 - top),
372 time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
373 kb_to_graph(top - bottom));
376 /* now plot the ones that are of significant size */
378 while (ps->next_ps) {
382 ps->sample = ps->first;
383 while (ps->sample->next) {
384 ps->sample = ps->sample->next;
385 if (ps->sample->sampledata == sampledata)
388 /* don't draw anything smaller than 2mb */
389 if (ps->sample->sampledata == sampledata) {
390 if (ps->sample->pss > (100 * arg_scale_y)) {
391 top = bottom + ps->sample->pss;
392 svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
393 colorwheel[ps->pid % 12],
394 time_to_graph(prev_sampledata->sampletime - graph_start),
395 kb_to_graph(1000000.0 - top),
396 time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
397 kb_to_graph(top - bottom));
403 while ((cross_place = ps->sample->cross)) {
404 ps = ps->sample->cross->ps_new;
405 ps->sample = cross_place;
406 if (ps->sample->pss > (100 * arg_scale_y)) {
407 top = bottom + ps->sample->pss;
408 svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
409 colorwheel[ps->pid % 12],
410 time_to_graph(prev_sampledata->sampletime - graph_start),
411 kb_to_graph(1000000.0 - top),
412 time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
413 kb_to_graph(top - bottom));
417 prev_sampledata = sampledata;
421 /* overlay all the text labels */
423 LIST_FOREACH_BEFORE(link, sampledata, head) {
426 struct ps_sched_struct *prev_sample;
427 struct ps_sched_struct *cross_place;
429 /* put all the small pss blocks into the bottom */
430 ps = ps_first->next_ps;
431 while (ps->next_ps) {
435 ps->sample = ps->first;
436 while (ps->sample->next) {
437 ps->sample = ps->sample->next;
438 if (ps->sample->sampledata == sampledata)
441 if (ps->sample->sampledata == sampledata) {
442 if (ps->sample->pss <= (100 * arg_scale_y))
443 top += ps->sample->pss;
447 while ((cross_place = ps->sample->cross)) {
448 ps = ps->sample->cross->ps_new;
449 ps->sample = cross_place;
450 if (ps->sample->pss <= (100 * arg_scale_y))
451 top += ps->sample->pss;
455 /* now plot the ones that are of significant size */
457 while (ps->next_ps) {
458 prev_sample = ps->sample;
462 ps->sample = ps->first;
463 while (ps->sample->next) {
464 prev_sample = ps->sample;
465 ps->sample = ps->sample->next;
466 if (ps->sample->sampledata == sampledata)
469 /* don't draw anything smaller than 2mb */
470 if (ps->sample->sampledata == sampledata) {
471 if (ps->sample->pss > (100 * arg_scale_y)) {
472 top = bottom + ps->sample->pss;
473 /* draw a label with the process / PID */
474 if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y)))
475 svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n",
476 time_to_graph(sampledata->sampletime - graph_start),
477 kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)),
485 while ((cross_place = ps->sample->cross)) {
486 ps = ps->sample->cross->ps_new;
487 ps->sample = cross_place;
488 prev_sample = ps->sample->prev;
489 if (ps->sample->pss > (100 * arg_scale_y)) {
490 top = bottom + ps->sample->pss;
491 /* draw a label with the process / PID */
492 if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y)))
493 svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n",
494 time_to_graph(sampledata->sampletime - graph_start),
495 kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)),
504 /* debug output - full data dump */
505 svg("\n\n<!-- PSS map - csv format -->\n");
507 while (ps->next_ps) {
508 _cleanup_free_ char *enc_name = NULL;
513 enc_name = xml_comment_encode(ps->name);
517 svg("<!-- %s [%d] pss=", enc_name, ps->pid);
519 ps->sample = ps->first;
520 while (ps->sample->next) {
521 ps->sample = ps->sample->next;
522 svg("%d," , ps->sample->pss);
529 static void svg_io_bi_bar(void) {
535 struct list_sample_data *start_sampledata;
536 struct list_sample_data *stop_sampledata;
538 svg("<!-- IO utilization graph - In -->\n");
540 svg("<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - read</text>\n");
543 * calculate rounding range
545 * We need to round IO data since IO block data is not updated on
546 * each poll. Applying a smoothing function loses some burst data,
547 * so keep the smoothing range short.
549 range = 0.25 / (1.0 / arg_hz);
551 range = 2.0; /* no smoothing */
553 /* surrounding box */
556 /* find the max IO first */
558 LIST_FOREACH_BEFORE(link, sampledata, head) {
564 start = MAX(i - ((range / 2) - 1), 0);
565 stop = MIN(i + (range / 2), samples - 1);
566 diff = (stop - start);
568 start_sampledata = sampledata;
569 stop_sampledata = sampledata;
571 for (k=0;(k<((range/2)-1))&&(start_sampledata->link_next);k++)
572 start_sampledata = start_sampledata->link_next;
573 for (k=0;(k<(range/2))&&(stop_sampledata->link_prev);k++)
574 stop_sampledata = stop_sampledata->link_prev;
576 tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi)
584 tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo)
595 prev_sampledata = head;
596 LIST_FOREACH_BEFORE(link, sampledata, head) {
603 start = MAX(i - ((range / 2) - 1), 0);
604 stop = MIN(i + (range / 2), samples);
605 diff = (stop - start);
607 start_sampledata = sampledata;
608 stop_sampledata = sampledata;
610 for (k=0;(k<((range/2)-1))&&(start_sampledata->link_next);k++)
611 start_sampledata = start_sampledata->link_next;
612 for (k=0;(k<(range/2))&&(stop_sampledata->link_prev);k++)
613 stop_sampledata = stop_sampledata->link_prev;
615 tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi)
622 svg("<rect class=\"bi\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
623 time_to_graph(prev_sampledata->sampletime - graph_start),
624 (arg_scale_y * 5) - (pbi * (arg_scale_y * 5)),
625 time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
626 pbi * (arg_scale_y * 5));
628 /* labels around highest value */
630 svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
631 time_to_graph(sampledata->sampletime - graph_start) + 5,
632 ((arg_scale_y * 5) - (pbi * (arg_scale_y * 5))) + 15,
633 max / 1024.0 / (interval / 1000000000.0));
636 prev_sampledata = sampledata;
640 static void svg_io_bo_bar(void) {
646 struct list_sample_data *start_sampledata;
647 struct list_sample_data *stop_sampledata;
649 svg("<!-- IO utilization graph - out -->\n");
651 svg("<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - write</text>\n");
654 * calculate rounding range
656 * We need to round IO data since IO block data is not updated on
657 * each poll. Applying a smoothing function loses some burst data,
658 * so keep the smoothing range short.
660 range = 0.25 / (1.0 / arg_hz);
662 range = 2.0; /* no smoothing */
664 /* surrounding box */
667 /* find the max IO first */
669 LIST_FOREACH_BEFORE(link, sampledata, head) {
675 start = MAX(i - ((range / 2) - 1), 0);
676 stop = MIN(i + (range / 2), samples - 1);
677 diff = (stop - start);
679 start_sampledata = sampledata;
680 stop_sampledata = sampledata;
682 for (k=0;(k<((range/2)-1))&&(start_sampledata->link_next);k++)
683 start_sampledata = start_sampledata->link_next;
684 for (k=0;(k<(range/2))&&(stop_sampledata->link_prev);k++)
685 stop_sampledata = stop_sampledata->link_prev;
687 tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi)
691 tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo)
701 prev_sampledata = head;
703 LIST_FOREACH_BEFORE(link, sampledata, head) {
712 start = MAX(i - ((range / 2) - 1), 0);
713 stop = MIN(i + (range / 2), samples);
714 diff = (stop - start);
716 start_sampledata = sampledata;
717 stop_sampledata = sampledata;
719 for (k=0;(k<((range/2)-1))&&(start_sampledata->link_next);k++)
720 start_sampledata = start_sampledata->link_next;
721 for (k=0;(k<(range/2))&&(stop_sampledata->link_prev);k++)
722 stop_sampledata = stop_sampledata->link_prev;
724 tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo)
731 svg("<rect class=\"bo\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
732 time_to_graph(prev_sampledata->sampletime - graph_start),
733 (arg_scale_y * 5) - (pbo * (arg_scale_y * 5)),
734 time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
735 pbo * (arg_scale_y * 5));
737 /* labels around highest bo value */
739 svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
740 time_to_graph(sampledata->sampletime - graph_start) + 5,
741 ((arg_scale_y * 5) - (pbo * (arg_scale_y * 5))),
742 max / 1024.0 / (interval / 1000000000.0));
745 prev_sampledata = sampledata;
749 static void svg_cpu_bar(int cpu_num) {
751 svg("<!-- CPU utilization graph -->\n");
754 svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] utilization</text>\n");
756 svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] utilization</text>\n", cpu_num);
757 /* surrounding box */
760 /* bars for each sample, proportional to the CPU util. */
761 prev_sampledata = head;
762 LIST_FOREACH_BEFORE(link, sampledata, head) {
770 for (c = 0; c < cpus; c++)
771 trt += sampledata->runtime[c] - prev_sampledata->runtime[c];
773 trt = sampledata->runtime[cpu_num] - prev_sampledata->runtime[cpu_num];
775 trt = trt / 1000000000.0;
778 trt = trt / (double)cpus;
781 ptrt = trt / (sampledata->sampletime - prev_sampledata->sampletime);
787 svg("<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
788 time_to_graph(prev_sampledata->sampletime - graph_start),
789 (arg_scale_y * 5) - (ptrt * (arg_scale_y * 5)),
790 time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
791 ptrt * (arg_scale_y * 5));
793 prev_sampledata = sampledata;
797 static void svg_wait_bar(int cpu_num) {
799 svg("<!-- Wait time aggregation box -->\n");
802 svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] wait</text>\n");
804 svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] wait</text>\n", cpu_num);
806 /* surrounding box */
809 /* bars for each sample, proportional to the CPU util. */
810 prev_sampledata = head;
811 LIST_FOREACH_BEFORE(link, sampledata, head) {
819 for (c = 0; c < cpus; c++)
820 twt += sampledata->waittime[c] - prev_sampledata->waittime[c];
822 twt = sampledata->waittime[cpu_num] - prev_sampledata->waittime[cpu_num];
824 twt = twt / 1000000000.0;
827 twt = twt / (double)cpus;
830 ptwt = twt / (sampledata->sampletime - prev_sampledata->sampletime);
836 svg("<rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
837 time_to_graph(prev_sampledata->sampletime - graph_start),
838 ((arg_scale_y * 5) - (ptwt * (arg_scale_y * 5))),
839 time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
840 ptwt * (arg_scale_y * 5));
842 prev_sampledata = sampledata;
846 static void svg_entropy_bar(void) {
848 svg("<!-- entropy pool graph -->\n");
850 svg("<text class=\"t2\" x=\"5\" y=\"-15\">Entropy pool size</text>\n");
851 /* surrounding box */
854 /* bars for each sample, scale 0-4096 */
855 prev_sampledata = head;
856 LIST_FOREACH_BEFORE(link, sampledata, head) {
857 /* svg("<!-- entropy %.03f %i -->\n", sampletime[i], entropy_avail[i]); */
858 svg("<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
859 time_to_graph(prev_sampledata->sampletime - graph_start),
860 ((arg_scale_y * 5) - ((sampledata->entropy_avail / 4096.) * (arg_scale_y * 5))),
861 time_to_graph(sampledata->sampletime - prev_sampledata->sampletime),
862 (sampledata->entropy_avail / 4096.) * (arg_scale_y * 5));
863 prev_sampledata = sampledata;
867 static struct ps_struct *get_next_ps(struct ps_struct *ps) {
869 * walk the list of processes and return the next one to be
883 /* go back for parent siblings */
886 if (ps->parent->next)
887 return ps->parent->next;
896 static bool ps_filter(struct ps_struct *ps) {
900 /* can't draw data when there is only 1 sample (need start + stop) */
901 if (ps->first == ps->last)
904 /* don't filter kthreadd */
908 /* drop stuff that doesn't use any real CPU time */
909 if (ps->total <= 0.001)
915 static void svg_do_initcall(int count_only) {
916 _cleanup_pclose_ FILE *f = NULL;
922 /* can't plot initcall when disabled or in relative mode */
923 if (!initcall || arg_relative) {
929 svg("<!-- initcall -->\n");
931 svg("<text class=\"t2\" x=\"5\" y=\"-15\">Kernel init threads</text>\n");
932 /* surrounding box */
933 svg_graph_box(kcount);
939 * Initcall graphing - parses dmesg buffer and displays kernel threads
940 * This somewhat uses the same methods and scaling to show processes
941 * but looks a lot simpler. It's overlaid entirely onto the PS graph
945 f = popen("dmesg", "r");
954 if (fgets(l, sizeof(l) - 1, f) == NULL)
957 c = sscanf(l, "[%lf] initcall %s %*s %d %*s %d %*s",
958 &t, func, &ret, &usecs);
960 /* also parse initcalls done by module loading */
961 c = sscanf(l, "[%lf] initcall %s %*s %*s %d %*s %d %*s",
962 &t, func, &ret, &usecs);
967 /* chop the +0xXX/0xXX stuff */
968 while(func[z] != '+')
973 /* filter out irrelevant stuff */
979 svg("<!-- thread=\"%s\" time=\"%.3f\" elapsed=\"%d\" result=\"%d\" -->\n",
980 func, t, usecs, ret);
986 svg(" <rect class=\"krnl\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
987 time_to_graph(t - (usecs / 1000000.0)),
989 time_to_graph(usecs / 1000000.0),
993 svg(" <text x=\"%.03f\" y=\"%.03f\">%s <tspan class=\"run\">%.03fs</tspan></text>\n",
994 time_to_graph(t - (usecs / 1000000.0)) + 5,
995 ps_to_graph(kcount) + 15,
1003 static void svg_ps_bars(void) {
1004 struct ps_struct *ps;
1010 svg("<!-- Process graph -->\n");
1012 svg("<text class=\"t2\" x=\"5\" y=\"-15\">Processes</text>\n");
1014 /* surrounding box */
1015 svg_graph_box(pcount);
1017 /* pass 2 - ps boxes */
1019 while ((ps = get_next_ps(ps))) {
1020 _cleanup_free_ char *enc_name = NULL, *escaped = NULL;
1025 if (!utf8_is_printable(ps->name, strlen(ps->name)))
1026 escaped = utf8_escape_non_printable(ps->name);
1028 enc_name = xml_comment_encode(escaped ? escaped : ps->name);
1032 /* leave some trace of what we actually filtered etc. */
1033 svg("<!-- %s [%i] ppid=%i runtime=%.03fs -->\n", enc_name, ps->pid,
1034 ps->ppid, ps->total);
1036 starttime = ps->first->sampledata->sampletime;
1038 if (!ps_filter(ps)) {
1039 /* remember where _to_ our children need to draw a line */
1040 ps->pos_x = time_to_graph(starttime - graph_start);
1041 ps->pos_y = ps_to_graph(j+1); /* bottom left corner */
1042 } else if (ps->parent){
1043 /* hook children to our parent coords instead */
1044 ps->pos_x = ps->parent->pos_x;
1045 ps->pos_y = ps->parent->pos_y;
1047 /* if this is the last child, we might still need to draw a connecting line */
1048 if ((!ps->next) && (ps->parent))
1049 svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1051 ps_to_graph(j-1) + 10.0, /* whee, use the last value here */
1057 endtime = ps->last->sampledata->sampletime;
1058 svg(" <rect class=\"ps\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1059 time_to_graph(starttime - graph_start),
1061 time_to_graph(ps->last->sampledata->sampletime - starttime),
1064 /* paint cpu load over these */
1065 ps->sample = ps->first;
1067 while (ps->sample->next) {
1070 struct ps_sched_struct *prev;
1073 ps->sample = ps->sample->next;
1075 /* calculate over interval */
1076 rt = ps->sample->runtime - prev->runtime;
1077 wt = ps->sample->waittime - prev->waittime;
1079 prt = (rt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime);
1080 wrt = (wt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime);
1082 /* this can happen if timekeeping isn't accurate enough */
1088 if ((prt < 0.1) && (wrt < 0.1)) /* =~ 26 (color threshold) */
1091 svg(" <rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1092 time_to_graph(prev->sampledata->sampletime - graph_start),
1094 time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime),
1097 /* draw cpu over wait - TODO figure out how/why run + wait > interval */
1098 svg(" <rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1099 time_to_graph(prev->sampledata->sampletime - graph_start),
1100 ps_to_graph(j + (1.0 - prt)),
1101 time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime),
1106 /* determine where to display the process name */
1107 if ((endtime - starttime) < 1.5)
1108 /* too small to fit label inside the box */
1113 /* text label of process name */
1114 svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]<tspan class=\"run\">%.03fs</tspan> %s</text>\n",
1115 time_to_graph(w - graph_start) + 5.0,
1116 ps_to_graph(j) + 14.0,
1117 escaped ? escaped : ps->name,
1119 (ps->last->runtime - ps->first->runtime) / 1000000000.0,
1120 arg_show_cgroup ? ps->cgroup : "");
1121 /* paint lines to the parent process */
1123 /* horizontal part */
1124 svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1125 time_to_graph(starttime - graph_start),
1126 ps_to_graph(j) + 10.0,
1128 ps_to_graph(j) + 10.0);
1130 /* one vertical line connecting all the horizontal ones up */
1132 svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1134 ps_to_graph(j) + 10.0,
1139 j++; /* count boxes */
1144 /* last pass - determine when idle */
1146 /* make sure we start counting from the point where we actually have
1147 * data: assume that bootchart's first sample is when data started
1151 while (ps->next_ps) {
1157 /* need to know last node first */
1158 ps->sample = ps->first;
1159 i = ps->sample->next->sampledata->counter;
1161 while (ps->sample->next && i<(samples-(arg_hz/2))) {
1166 struct ps_sched_struct *sample_hz;
1168 ps->sample = ps->sample->next;
1169 sample_hz = ps->sample;
1170 for (ii=0;((ii<(int)arg_hz/2)&&(sample_hz->next));ii++)
1171 sample_hz = sample_hz->next;
1173 /* subtract bootchart cpu utilization from total */
1175 for (c = 0; c < cpus; c++)
1176 crt += sample_hz->sampledata->runtime[c] - ps->sample->sampledata->runtime[c];
1177 brt = sample_hz->runtime - ps->sample->runtime;
1179 * our definition of "idle":
1181 * if for (hz / 2) we've used less CPU than (interval / 2) ...
1182 * defaults to 4.0%, which experimentally, is where atom idles
1184 if ((crt - brt) < (interval / 2.0)) {
1185 idletime = ps->sample->sampledata->sampletime - graph_start;
1186 svg("\n<!-- idle detected at %.03f seconds -->\n",
1188 svg("<line class=\"idle\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1189 time_to_graph(idletime),
1191 time_to_graph(idletime),
1192 ps_to_graph(pcount) + arg_scale_y);
1193 svg("<text class=\"idle\" x=\"%.03f\" y=\"%.03f\">%.01fs</text>\n",
1194 time_to_graph(idletime) + 5.0,
1195 ps_to_graph(pcount) + arg_scale_y,
1203 static void svg_top_ten_cpu(void) {
1204 struct ps_struct *top[10];
1205 struct ps_struct emptyps = {};
1206 struct ps_struct *ps;
1209 for (n = 0; n < (int) ELEMENTSOF(top); n++)
1212 /* walk all ps's and setup ptrs */
1214 while ((ps = get_next_ps(ps))) {
1215 for (n = 0; n < 10; n++) {
1216 if (ps->total <= top[n]->total)
1218 /* cascade insert */
1219 for (m = 9; m > n; m--)
1226 svg("<text class=\"t2\" x=\"20\" y=\"0\">Top CPU consumers:</text>\n");
1227 for (n = 0; n < 10; n++)
1228 svg("<text class=\"t3\" x=\"20\" y=\"%d\">%3.03fs - <![CDATA[%s]]> [%d]</text>\n",
1235 static void svg_top_ten_pss(void) {
1236 struct ps_struct *top[10];
1237 struct ps_struct emptyps = {};
1238 struct ps_struct *ps;
1241 for (n = 0; n < (int) ELEMENTSOF(top); n++)
1244 /* walk all ps's and setup ptrs */
1246 while ((ps = get_next_ps(ps))) {
1247 for (n = 0; n < 10; n++) {
1248 if (ps->pss_max <= top[n]->pss_max)
1250 /* cascade insert */
1251 for (m = 9; m > n; m--)
1258 svg("<text class=\"t2\" x=\"20\" y=\"0\">Top PSS consumers:</text>\n");
1259 for (n = 0; n < 10; n++)
1260 svg("<text class=\"t3\" x=\"20\" y=\"%d\">%dK - <![CDATA[%s]]> [%d]</text>\n",
1267 void svg_do(const char *build) {
1268 struct ps_struct *ps;
1272 memzero(&str, sizeof(str));
1276 /* count initcall thread count first */
1278 ksize = (kcount ? ps_to_graph(kcount) + (arg_scale_y * 2) : 0);
1280 /* then count processes */
1281 while ((ps = get_next_ps(ps))) {
1287 psize = ps_to_graph(pcount) + (arg_scale_y * 2);
1289 esize = (arg_entropy ? arg_scale_y * 7 : 0);
1291 /* after this, we can draw the header with proper sizing */
1293 svg("<rect class=\"bg\" width=\"100%%\" height=\"100%%\" />\n\n");
1295 svg("<g transform=\"translate(10,400)\">\n");
1299 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset));
1303 for (c = -1; c < (arg_percpu ? cpus : 0); c++) {
1305 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset));
1310 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset));
1317 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset));
1323 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset) + ksize);
1327 svg("<g transform=\"translate(10, 0)\">\n");
1331 svg("<g transform=\"translate(10,200)\">\n");
1336 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset) + ksize + psize);
1342 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset) + ksize + psize + esize);
1346 svg("<g transform=\"translate(410,200)\">\n");