]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/svg.c
bootchart: add parameter "-C" to expand process names to the full cmdline
[thirdparty/systemd.git] / src / bootchart / svg.c
1 /***
2 bootchart.c - This file is part of systemd-bootchart
3
4 Copyright (C) 2009-2013 Intel Coproration
5
6 Authors:
7 Auke Kok <auke-jan.h.kok@intel.com>
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <stdio.h>
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28 #include <limits.h>
29 #include <unistd.h>
30 #include <sys/utsname.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33
34 #include "bootchart.h"
35 #include "util.h"
36 #include "macro.h"
37
38
39 #define time_to_graph(t) ((t) * scale_x)
40 #define ps_to_graph(n) ((n) * scale_y)
41 #define kb_to_graph(m) ((m) * scale_y * 0.0001)
42 #define to_color(n) (192.0 - ((n) * 192.0))
43
44 #define max(x, y) (((x) > (y)) ? (x) : (y))
45 #define min(x, y) (((x) < (y)) ? (x) : (y))
46
47 static char str[8092];
48
49 #define svg(a...) do { snprintf(str, 8092, ## a); fputs(str, of); fflush(of); } while (0)
50
51 static const char *colorwheel[12] = {
52 "rgb(255,32,32)", // red
53 "rgb(32,192,192)", // cyan
54 "rgb(255,128,32)", // orange
55 "rgb(128,32,192)", // blue-violet
56 "rgb(255,255,32)", // yellow
57 "rgb(192,32,128)", // red-violet
58 "rgb(32,255,32)", // green
59 "rgb(255,64,32)", // red-orange
60 "rgb(32,32,255)", // blue
61 "rgb(255,192,32)", // yellow-orange
62 "rgb(192,32,192)", // violet
63 "rgb(32,192,32)" // yellow-green
64 };
65
66 static double idletime = -1.0;
67 static int pfiltered = 0;
68 static int pcount = 0;
69 static int kcount = 0;
70 static float psize = 0;
71 static float ksize = 0;
72 static float esize = 0;
73
74
75 static void svg_header(void)
76 {
77 float w;
78 float h;
79
80 /* min width is about 1600px due to the label */
81 w = 150.0 + 10.0 + time_to_graph(sampletime[samples-1] - graph_start);
82 w = ((w < 1600.0) ? 1600.0 : w);
83
84 /* height is variable based on pss, psize, ksize */
85 h = 400.0 + (scale_y * 30.0) /* base graphs and title */
86 + (pss ? (100.0 * scale_y) + (scale_y * 7.0) : 0.0) /* pss estimate */
87 + psize + ksize + esize;
88
89 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n");
90 svg("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" ");
91 svg("\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
92
93 //svg("<g transform=\"translate(10,%d)\">\n", 1000 + 150 + (pcount * 20));
94 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" ",
95 w, h);
96 svg("xmlns=\"http://www.w3.org/2000/svg\">\n\n");
97
98 /* write some basic info as a comment, including some help */
99 svg("<!-- This file is a bootchart SVG file. It is best rendered in a browser -->\n");
100 svg("<!-- such as Chrome, Chromium, or Firefox. Other applications that -->\n");
101 svg("<!-- render these files properly but more slowly are ImageMagick, gimp, -->\n");
102 svg("<!-- inkscape, etc. To display the files on your system, just point -->\n");
103 svg("<!-- your browser to file:///run/log/ and click. This bootchart was -->\n\n");
104
105 svg("<!-- generated by bootchart version %s, running with options: -->\n", VERSION);
106 svg("<!-- hz=\"%f\" n=\"%d\" -->\n", hz, len);
107 svg("<!-- x=\"%f\" y=\"%f\" -->\n", scale_x, scale_y);
108 svg("<!-- rel=\"%d\" f=\"%d\" -->\n", relative, filter);
109 svg("<!-- p=\"%d\" e=\"%d\" -->\n", pss, entropy);
110 svg("<!-- o=\"%s\" i=\"%s\" -->\n\n", output_path, init_path);
111
112 /* style sheet */
113 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n");
114
115 svg(" rect { stroke-width: 1; }\n");
116 svg(" rect.cpu { fill: rgb(64,64,240); stroke-width: 0; fill-opacity: 0.7; }\n");
117 svg(" rect.wait { fill: rgb(240,240,0); stroke-width: 0; fill-opacity: 0.7; }\n");
118 svg(" rect.bi { fill: rgb(240,128,128); stroke-width: 0; fill-opacity: 0.7; }\n");
119 svg(" rect.bo { fill: rgb(192,64,64); stroke-width: 0; fill-opacity: 0.7; }\n");
120 svg(" rect.ps { fill: rgb(192,192,192); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
121 svg(" rect.krnl { fill: rgb(240,240,0); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
122 svg(" rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n");
123 svg(" rect.clrw { stroke-width: 0; fill-opacity: 0.7;}\n");
124 svg(" line { stroke: rgb(64,64,64); stroke-width: 1; }\n");
125 svg("// line.sec1 { }\n");
126 svg(" line.sec5 { stroke-width: 2; }\n");
127 svg(" line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n");
128 svg(" line.dot { stroke-dasharray: 2 4; }\n");
129 svg(" line.idle { stroke: rgb(64,64,64); stroke-dasharray: 10 6; stroke-opacity: 0.7; }\n");
130
131 svg(" .run { font-size: 8; font-style: italic; }\n");
132 svg(" text { font-family: Verdana, Helvetica; font-size: 10; }\n");
133 svg(" text.sec { font-size: 8; }\n");
134 svg(" text.t1 { font-size: 24; }\n");
135 svg(" text.t2 { font-size: 12; }\n");
136 svg(" text.idle { font-size: 18; }\n");
137
138 svg(" ]]>\n </style>\n</defs>\n\n");
139
140 }
141
142
143 static void svg_title(const char *build)
144 {
145 char cmdline[256] = "";
146 char filename[PATH_MAX];
147 char buf[256];
148 char rootbdev[16] = "Unknown";
149 char model[256] = "Unknown";
150 char date[256] = "Unknown";
151 char cpu[256] = "Unknown";
152 char *c;
153 FILE *f;
154 time_t t;
155 int fd;
156 struct utsname uts;
157
158 /* grab /proc/cmdline */
159 fd = openat(procfd, "cmdline", O_RDONLY);
160 f = fdopen(fd, "r");
161 if (f) {
162 if (!fgets(cmdline, 255, f))
163 sprintf(cmdline, "Unknown");
164 fclose(f);
165 }
166
167 /* extract root fs so we can find disk model name in sysfs */
168 /* FIXME: this works only in the simple case */
169 c = strstr(cmdline, "root=/dev/");
170 if (c) {
171 strncpy(rootbdev, &c[10], 3);
172 rootbdev[3] = '\0';
173 sprintf(filename, "block/%s/device/model", rootbdev);
174 fd = openat(sysfd, filename, O_RDONLY);
175 f = fdopen(fd, "r");
176 if (f) {
177 if (!fgets(model, 255, f))
178 fprintf(stderr, "Error reading disk model for %s\n", rootbdev);
179 fclose(f);
180 }
181 }
182
183 /* various utsname parameters */
184 if (uname(&uts))
185 fprintf(stderr, "Error getting uname info\n");
186
187 /* date */
188 t = time(NULL);
189 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&t));
190
191 /* CPU type */
192 fd = openat(procfd, "cpuinfo", O_RDONLY);
193 f = fdopen(fd, "r");
194 if (f) {
195 while (fgets(buf, 255, f)) {
196 if (strstr(buf, "model name")) {
197 strncpy(cpu, &buf[13], 255);
198 break;
199 }
200 }
201 fclose(f);
202 }
203
204 svg("<text class=\"t1\" x=\"0\" y=\"30\">Bootchart for %s - %s</text>\n",
205 uts.nodename, date);
206 svg("<text class=\"t2\" x=\"20\" y=\"50\">System: %s %s %s %s</text>\n",
207 uts.sysname, uts.release, uts.version, uts.machine);
208 svg("<text class=\"t2\" x=\"20\" y=\"65\">CPU: %s</text>\n",
209 cpu);
210 svg("<text class=\"t2\" x=\"20\" y=\"80\">Disk: %s</text>\n",
211 model);
212 svg("<text class=\"t2\" x=\"20\" y=\"95\">Boot options: %s</text>\n",
213 cmdline);
214 svg("<text class=\"t2\" x=\"20\" y=\"110\">Build: %s</text>\n",
215 build);
216 svg("<text class=\"t2\" x=\"20\" y=\"125\">Log start time: %.03fs</text>\n", log_start);
217 svg("<text class=\"t2\" x=\"20\" y=\"140\">Idle time: ");
218
219 if (idletime >= 0.0)
220 svg("%.03fs", idletime);
221 else
222 svg("Not detected");
223 svg("</text>\n");
224 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",
225 hz, len, overrun, pscount, pfiltered);
226 }
227
228
229 static void svg_graph_box(int height)
230 {
231 double d = 0.0;
232 int i = 0;
233
234 /* outside box, fill */
235 svg("<rect class=\"box\" x=\"%.03f\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
236 time_to_graph(0.0),
237 time_to_graph(sampletime[samples-1] - graph_start),
238 ps_to_graph(height));
239
240 for (d = graph_start; d <= sampletime[samples-1];
241 d += (scale_x < 2.0 ? 60.0 : scale_x < 10.0 ? 1.0 : 0.1)) {
242 /* lines for each second */
243 if (i % 50 == 0)
244 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
245 time_to_graph(d - graph_start),
246 time_to_graph(d - graph_start),
247 ps_to_graph(height));
248 else if (i % 10 == 0)
249 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
250 time_to_graph(d - graph_start),
251 time_to_graph(d - graph_start),
252 ps_to_graph(height));
253 else
254 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
255 time_to_graph(d - graph_start),
256 time_to_graph(d - graph_start),
257 ps_to_graph(height));
258
259 /* time label */
260 if (i % 10 == 0)
261 svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
262 time_to_graph(d - graph_start),
263 -5.0,
264 d - graph_start);
265
266 i++;
267 }
268 }
269
270 /* xml comments must not contain "--" */
271 static char* xml_comment_encode(const char* name) {
272 char *enc_name, *p;
273
274 enc_name = strdup(name);
275 if (!enc_name)
276 return NULL;
277
278 for (p = enc_name; *p; p++)
279 if (p[0] == '-' && p[1] == '-')
280 p[1] = '_';
281
282 return enc_name;
283 }
284
285 static void svg_pss_graph(void)
286 {
287 struct ps_struct *ps;
288 int i;
289
290 svg("\n\n<!-- Pss memory size graph -->\n");
291
292 svg("\n <text class=\"t2\" x=\"5\" y=\"-15\">Memory allocation - Pss</text>\n");
293
294 /* vsize 1000 == 1000mb */
295 svg_graph_box(100);
296 /* draw some hlines for usable memory sizes */
297 for (i = 100000; i < 1000000; i += 100000) {
298 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"%.0f\" x2=\"%.03f\" y2=\"%.0f\"/>\n",
299 time_to_graph(.0),
300 kb_to_graph(i),
301 time_to_graph(sampletime[samples-1] - graph_start),
302 kb_to_graph(i));
303 svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.0f\">%dM</text>\n",
304 time_to_graph(sampletime[samples-1] - graph_start) + 5,
305 kb_to_graph(i), (1000000 - i) / 1000);
306 }
307 svg("\n");
308
309 /* now plot the graph itself */
310 for (i = 1; i < samples ; i++) {
311 int bottom;
312 int top;
313
314 bottom = 0;
315 top = 0;
316
317 /* put all the small pss blocks into the bottom */
318 ps = ps_first;
319 while (ps->next_ps) {
320 ps = ps->next_ps;
321 if (!ps)
322 continue;
323 if (ps->sample[i].pss <= (100 * scale_y))
324 top += ps->sample[i].pss;
325 };
326 svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
327 "rgb(64,64,64)",
328 time_to_graph(sampletime[i - 1] - graph_start),
329 kb_to_graph(1000000.0 - top),
330 time_to_graph(sampletime[i] - sampletime[i - 1]),
331 kb_to_graph(top - bottom));
332
333 bottom = top;
334
335 /* now plot the ones that are of significant size */
336 ps = ps_first;
337 while (ps->next_ps) {
338 ps = ps->next_ps;
339 if (!ps)
340 continue;
341 /* don't draw anything smaller than 2mb */
342 if (ps->sample[i].pss > (100 * scale_y)) {
343 top = bottom + ps->sample[i].pss;
344 svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
345 colorwheel[ps->pid % 12],
346 time_to_graph(sampletime[i - 1] - graph_start),
347 kb_to_graph(1000000.0 - top),
348 time_to_graph(sampletime[i] - sampletime[i - 1]),
349 kb_to_graph(top - bottom));
350 bottom = top;
351 }
352 }
353 }
354
355 /* overlay all the text labels */
356 for (i = 1; i < samples ; i++) {
357 int bottom;
358 int top;
359
360 bottom = 0;
361 top = 0;
362
363 /* put all the small pss blocks into the bottom */
364 ps = ps_first;
365 while (ps->next_ps) {
366 ps = ps->next_ps;
367 if (!ps)
368 continue;
369 if (ps->sample[i].pss <= (100 * scale_y))
370 top += ps->sample[i].pss;
371 };
372
373 bottom = top;
374
375 /* now plot the ones that are of significant size */
376 ps = ps_first;
377 while (ps->next_ps) {
378 ps = ps->next_ps;
379 if (!ps)
380 continue;
381 /* don't draw anything smaller than 2mb */
382 if (ps->sample[i].pss > (100 * scale_y)) {
383 top = bottom + ps->sample[i].pss;
384 /* draw a label with the process / PID */
385 if ((i == 1) || (ps->sample[i - 1].pss <= (100 * scale_y)))
386 svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n",
387 time_to_graph(sampletime[i] - graph_start),
388 kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)),
389 ps->name,
390 ps->pid);
391 bottom = top;
392 }
393 }
394 }
395
396 /* debug output - full data dump */
397 svg("\n\n<!-- PSS map - csv format -->\n");
398 ps = ps_first;
399 while (ps->next_ps) {
400 char _cleanup_free_*enc_name;
401 ps = ps->next_ps;
402 if (!ps)
403 continue;
404
405 enc_name = xml_comment_encode(ps->name);
406 if(!enc_name)
407 continue;
408
409 svg("<!-- %s [%d] pss=", enc_name, ps->pid);
410
411 for (i = 0; i < samples ; i++) {
412 svg("%d," , ps->sample[i].pss);
413 }
414 svg(" -->\n");
415 }
416
417 }
418
419 static void svg_io_bi_bar(void)
420 {
421 double max = 0.0;
422 double range;
423 int max_here = 0;
424 int i;
425
426 svg("<!-- IO utilization graph - In -->\n");
427
428 svg("<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - read</text>\n");
429
430 /*
431 * calculate rounding range
432 *
433 * We need to round IO data since IO block data is not updated on
434 * each poll. Applying a smoothing function loses some burst data,
435 * so keep the smoothing range short.
436 */
437 range = 0.25 / (1.0 / hz);
438 if (range < 2.0)
439 range = 2.0; /* no smoothing */
440
441 /* surrounding box */
442 svg_graph_box(5);
443
444 /* find the max IO first */
445 for (i = 1; i < samples; i++) {
446 int start;
447 int stop;
448 double tot;
449
450 start = max(i - ((range / 2) - 1), 0);
451 stop = min(i + (range / 2), samples - 1);
452
453 tot = (double)(blockstat[stop].bi - blockstat[start].bi)
454 / (stop - start);
455 if (tot > max) {
456 max = tot;
457 max_here = i;
458 }
459 tot = (double)(blockstat[stop].bo - blockstat[start].bo)
460 / (stop - start);
461 if (tot > max)
462 max = tot;
463 }
464
465 /* plot bi */
466 for (i = 1; i < samples; i++) {
467 int start;
468 int stop;
469 double tot;
470 double pbi;
471
472 start = max(i - ((range / 2) - 1), 0);
473 stop = min(i + (range / 2), samples);
474
475 tot = (double)(blockstat[stop].bi - blockstat[start].bi)
476 / (stop - start);
477 pbi = tot / max;
478
479 if (pbi > 0.001)
480 svg("<rect class=\"bi\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
481 time_to_graph(sampletime[i - 1] - graph_start),
482 (scale_y * 5) - (pbi * (scale_y * 5)),
483 time_to_graph(sampletime[i] - sampletime[i - 1]),
484 pbi * (scale_y * 5));
485
486 /* labels around highest value */
487 if (i == max_here) {
488 svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
489 time_to_graph(sampletime[i] - graph_start) + 5,
490 ((scale_y * 5) - (pbi * (scale_y * 5))) + 15,
491 max / 1024.0 / (interval / 1000000000.0));
492 }
493 }
494 }
495
496 static void svg_io_bo_bar(void)
497 {
498 double max = 0.0;
499 double range;
500 int max_here = 0;
501 int i;
502
503 svg("<!-- IO utilization graph - out -->\n");
504
505 svg("<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - write</text>\n");
506
507 /*
508 * calculate rounding range
509 *
510 * We need to round IO data since IO block data is not updated on
511 * each poll. Applying a smoothing function loses some burst data,
512 * so keep the smoothing range short.
513 */
514 range = 0.25 / (1.0 / hz);
515 if (range < 2.0)
516 range = 2.0; /* no smoothing */
517
518 /* surrounding box */
519 svg_graph_box(5);
520
521 /* find the max IO first */
522 for (i = 1; i < samples; i++) {
523 int start;
524 int stop;
525 double tot;
526
527 start = max(i - ((range / 2) - 1), 0);
528 stop = min(i + (range / 2), samples - 1);
529
530 tot = (double)(blockstat[stop].bi - blockstat[start].bi)
531 / (stop - start);
532 if (tot > max)
533 max = tot;
534 tot = (double)(blockstat[stop].bo - blockstat[start].bo)
535 / (stop - start);
536 if (tot > max) {
537 max = tot;
538 max_here = i;
539 }
540 }
541
542 /* plot bo */
543 for (i = 1; i < samples; i++) {
544 int start;
545 int stop;
546 double tot;
547 double pbo;
548
549 start = max(i - ((range / 2) - 1), 0);
550 stop = min(i + (range / 2), samples);
551
552 tot = (double)(blockstat[stop].bo - blockstat[start].bo)
553 / (stop - start);
554 pbo = tot / max;
555
556 if (pbo > 0.001)
557 svg("<rect class=\"bo\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
558 time_to_graph(sampletime[i - 1] - graph_start),
559 (scale_y * 5) - (pbo * (scale_y * 5)),
560 time_to_graph(sampletime[i] - sampletime[i - 1]),
561 pbo * (scale_y * 5));
562
563 /* labels around highest bo value */
564 if (i == max_here) {
565 svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
566 time_to_graph(sampletime[i] - graph_start) + 5,
567 ((scale_y * 5) - (pbo * (scale_y * 5))),
568 max / 1024.0 / (interval / 1000000000.0));
569 }
570 }
571 }
572
573
574 static void svg_cpu_bar(void)
575 {
576 int i;
577
578 svg("<!-- CPU utilization graph -->\n");
579
580 svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU utilization</text>\n");
581 /* surrounding box */
582 svg_graph_box(5);
583
584 /* bars for each sample, proportional to the CPU util. */
585 for (i = 1; i < samples; i++) {
586 int c;
587 double trt;
588 double ptrt;
589
590 ptrt = trt = 0.0;
591
592 for (c = 0; c < cpus; c++)
593 trt += cpustat[c].sample[i].runtime - cpustat[c].sample[i - 1].runtime;
594
595 trt = trt / 1000000000.0;
596
597 trt = trt / (double)cpus;
598
599 if (trt > 0.0)
600 ptrt = trt / (sampletime[i] - sampletime[i - 1]);
601
602 if (ptrt > 1.0)
603 ptrt = 1.0;
604
605 if (ptrt > 0.001) {
606 svg("<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
607 time_to_graph(sampletime[i - 1] - graph_start),
608 (scale_y * 5) - (ptrt * (scale_y * 5)),
609 time_to_graph(sampletime[i] - sampletime[i - 1]),
610 ptrt * (scale_y * 5));
611 }
612 }
613 }
614
615 static void svg_wait_bar(void)
616 {
617 int i;
618
619 svg("<!-- Wait time aggregation box -->\n");
620
621 svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU wait</text>\n");
622
623 /* surrounding box */
624 svg_graph_box(5);
625
626 /* bars for each sample, proportional to the CPU util. */
627 for (i = 1; i < samples; i++) {
628 int c;
629 double twt;
630 double ptwt;
631
632 ptwt = twt = 0.0;
633
634 for (c = 0; c < cpus; c++)
635 twt += cpustat[c].sample[i].waittime - cpustat[c].sample[i - 1].waittime;
636
637 twt = twt / 1000000000.0;
638
639 twt = twt / (double)cpus;
640
641 if (twt > 0.0)
642 ptwt = twt / (sampletime[i] - sampletime[i - 1]);
643
644 if (ptwt > 1.0)
645 ptwt = 1.0;
646
647 if (ptwt > 0.001) {
648 svg("<rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
649 time_to_graph(sampletime[i - 1] - graph_start),
650 ((scale_y * 5) - (ptwt * (scale_y * 5))),
651 time_to_graph(sampletime[i] - sampletime[i - 1]),
652 ptwt * (scale_y * 5));
653 }
654 }
655 }
656
657
658 static void svg_entropy_bar(void)
659 {
660 int i;
661
662 svg("<!-- entropy pool graph -->\n");
663
664 svg("<text class=\"t2\" x=\"5\" y=\"-15\">Entropy pool size</text>\n");
665 /* surrounding box */
666 svg_graph_box(5);
667
668 /* bars for each sample, scale 0-4096 */
669 for (i = 1; i < samples; i++) {
670 /* svg("<!-- entropy %.03f %i -->\n", sampletime[i], entropy_avail[i]); */
671 svg("<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
672 time_to_graph(sampletime[i - 1] - graph_start),
673 ((scale_y * 5) - ((entropy_avail[i] / 4096.) * (scale_y * 5))),
674 time_to_graph(sampletime[i] - sampletime[i - 1]),
675 (entropy_avail[i] / 4096.) * (scale_y * 5));
676 }
677 }
678
679
680 static struct ps_struct *get_next_ps(struct ps_struct *ps)
681 {
682 /*
683 * walk the list of processes and return the next one to be
684 * painted
685 */
686 if (ps == ps_first)
687 return ps->next_ps;
688
689 /* go deep */
690 if (ps->children)
691 return ps->children;
692
693 /* find siblings */
694 if (ps->next)
695 return ps->next;
696
697 /* go back for parent siblings */
698 while (1) {
699 if (ps->parent)
700 if (ps->parent->next)
701 return ps->parent->next;
702 ps = ps->parent;
703 if (!ps)
704 return ps;
705 }
706
707 return NULL;
708 }
709
710
711 static int ps_filter(struct ps_struct *ps)
712 {
713 if (!filter)
714 return 0;
715
716 /* can't draw data when there is only 1 sample (need start + stop) */
717 if (ps->first == ps->last)
718 return -1;
719
720 /* don't filter kthreadd */
721 if (ps->pid == 2)
722 return 0;
723
724 /* drop stuff that doesn't use any real CPU time */
725 if (ps->total <= 0.001)
726 return -1;
727
728 return 0;
729 }
730
731
732 static void svg_do_initcall(int count_only)
733 {
734 FILE _cleanup_pclose_ *f = NULL;
735 double t;
736 char func[256];
737 int ret;
738 int usecs;
739
740 /* can't plot initcall when disabled or in relative mode */
741 if (!initcall || relative) {
742 kcount = 0;
743 return;
744 }
745
746 if (!count_only) {
747 svg("<!-- initcall -->\n");
748
749 svg("<text class=\"t2\" x=\"5\" y=\"-15\">Kernel init threads</text>\n");
750 /* surrounding box */
751 svg_graph_box(kcount);
752 }
753
754 kcount = 0;
755
756 /*
757 * Initcall graphing - parses dmesg buffer and displays kernel threads
758 * This somewhat uses the same methods and scaling to show processes
759 * but looks a lot simpler. It's overlaid entirely onto the PS graph
760 * when appropriate.
761 */
762
763 f = popen("dmesg", "r");
764 if (!f)
765 return;
766
767 while (!feof(f)) {
768 int c;
769 int z = 0;
770 char l[256];
771
772 if (fgets(l, sizeof(l) - 1, f) == NULL)
773 continue;
774
775 c = sscanf(l, "[%lf] initcall %s %*s %d %*s %d %*s",
776 &t, func, &ret, &usecs);
777 if (c != 4) {
778 /* also parse initcalls done by module loading */
779 c = sscanf(l, "[%lf] initcall %s %*s %*s %d %*s %d %*s",
780 &t, func, &ret, &usecs);
781 if (c != 4)
782 continue;
783 }
784
785 /* chop the +0xXX/0xXX stuff */
786 while(func[z] != '+')
787 z++;
788 func[z] = 0;
789
790 if (count_only) {
791 /* filter out irrelevant stuff */
792 if (usecs >= 1000)
793 kcount++;
794 continue;
795 }
796
797 svg("<!-- thread=\"%s\" time=\"%.3f\" elapsed=\"%d\" result=\"%d\" -->\n",
798 func, t, usecs, ret);
799
800 if (usecs < 1000)
801 continue;
802
803 /* rect */
804 svg(" <rect class=\"krnl\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
805 time_to_graph(t - (usecs / 1000000.0)),
806 ps_to_graph(kcount),
807 time_to_graph(usecs / 1000000.0),
808 ps_to_graph(1));
809
810 /* label */
811 svg(" <text x=\"%.03f\" y=\"%.03f\">%s <tspan class=\"run\">%.03fs</tspan></text>\n",
812 time_to_graph(t - (usecs / 1000000.0)) + 5,
813 ps_to_graph(kcount) + 15,
814 func,
815 usecs / 1000000.0);
816
817 kcount++;
818 }
819 }
820
821
822 static void svg_ps_bars(void)
823 {
824 struct ps_struct *ps;
825 int i = 0;
826 int j = 0;
827 int w;
828 int pid;
829
830 svg("<!-- Process graph -->\n");
831
832 svg("<text class=\"t2\" x=\"5\" y=\"-15\">Processes</text>\n");
833
834 /* surrounding box */
835 svg_graph_box(pcount);
836
837 /* pass 2 - ps boxes */
838 ps = ps_first;
839 while ((ps = get_next_ps(ps))) {
840 char _cleanup_free_*enc_name;
841
842 double starttime;
843 int t;
844
845 if (!ps)
846 continue;
847
848 enc_name = xml_comment_encode(ps->name);
849 if(!enc_name)
850 continue;
851
852 /* leave some trace of what we actually filtered etc. */
853 svg("<!-- %s [%i] ppid=%i runtime=%.03fs -->\n", enc_name, ps->pid,
854 ps->ppid, ps->total);
855
856 /* it would be nice if we could use exec_start from /proc/pid/sched,
857 * but it's unreliable and gives bogus numbers */
858 starttime = sampletime[ps->first];
859
860 if (!ps_filter(ps)) {
861 /* remember where _to_ our children need to draw a line */
862 ps->pos_x = time_to_graph(starttime - graph_start);
863 ps->pos_y = ps_to_graph(j+1); /* bottom left corner */
864 } else {
865 /* hook children to our parent coords instead */
866 ps->pos_x = ps->parent->pos_x;
867 ps->pos_y = ps->parent->pos_y;
868
869 /* if this is the last child, we might still need to draw a connecting line */
870 if ((!ps->next) && (ps->parent))
871 svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
872 ps->parent->pos_x,
873 ps_to_graph(j-1) + 10.0, /* whee, use the last value here */
874 ps->parent->pos_x,
875 ps->parent->pos_y);
876 continue;
877 }
878
879 svg(" <rect class=\"ps\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
880 time_to_graph(starttime - graph_start),
881 ps_to_graph(j),
882 time_to_graph(sampletime[ps->last] - starttime),
883 ps_to_graph(1));
884
885 /* paint cpu load over these */
886 for (t = ps->first + 1; t < ps->last; t++) {
887 double rt, prt;
888 double wt, wrt;
889
890 /* calculate over interval */
891 rt = ps->sample[t].runtime - ps->sample[t-1].runtime;
892 wt = ps->sample[t].waittime - ps->sample[t-1].waittime;
893
894 prt = (rt / 1000000000) / (sampletime[t] - sampletime[t-1]);
895 wrt = (wt / 1000000000) / (sampletime[t] - sampletime[t-1]);
896
897 /* this can happen if timekeeping isn't accurate enough */
898 if (prt > 1.0)
899 prt = 1.0;
900 if (wrt > 1.0)
901 wrt = 1.0;
902
903 if ((prt < 0.1) && (wrt < 0.1)) /* =~ 26 (color threshold) */
904 continue;
905
906 svg(" <rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
907 time_to_graph(sampletime[t - 1] - graph_start),
908 ps_to_graph(j),
909 time_to_graph(sampletime[t] - sampletime[t - 1]),
910 ps_to_graph(wrt));
911
912 /* draw cpu over wait - TODO figure out how/why run + wait > interval */
913 svg(" <rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
914 time_to_graph(sampletime[t - 1] - graph_start),
915 ps_to_graph(j + (1.0 - prt)),
916 time_to_graph(sampletime[t] - sampletime[t - 1]),
917 ps_to_graph(prt));
918 }
919
920 /* determine where to display the process name */
921 if (sampletime[ps->last] - sampletime[ps->first] < 1.5)
922 /* too small to fit label inside the box */
923 w = ps->last;
924 else
925 w = ps->first;
926
927 /* text label of process name */
928 svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]<tspan class=\"run\">%.03fs</tspan></text>\n",
929 time_to_graph(sampletime[w] - graph_start) + 5.0,
930 ps_to_graph(j) + 14.0,
931 ps->name,
932 ps->pid,
933 (ps->sample[ps->last].runtime - ps->sample[ps->first].runtime) / 1000000000.0);
934 /* paint lines to the parent process */
935 if (ps->parent) {
936 /* horizontal part */
937 svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
938 time_to_graph(starttime - graph_start),
939 ps_to_graph(j) + 10.0,
940 ps->parent->pos_x,
941 ps_to_graph(j) + 10.0);
942
943 /* one vertical line connecting all the horizontal ones up */
944 if (!ps->next)
945 svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
946 ps->parent->pos_x,
947 ps_to_graph(j) + 10.0,
948 ps->parent->pos_x,
949 ps->parent->pos_y);
950 }
951
952 j++; /* count boxes */
953
954 svg("\n");
955 }
956
957 /* last pass - determine when idle */
958 pid = getpid();
959 /* make sure we start counting from the point where we actually have
960 * data: assume that bootchart's first sample is when data started
961 */
962 ps = ps_first;
963 while (ps->next_ps) {
964 ps = ps->next_ps;
965 if (ps->pid == pid)
966 break;
967 }
968
969 for (i = ps->first; i < samples - (hz / 2); i++) {
970 double crt;
971 double brt;
972 int c;
973
974 /* subtract bootchart cpu utilization from total */
975 crt = 0.0;
976 for (c = 0; c < cpus; c++)
977 crt += cpustat[c].sample[i + ((int)hz / 2)].runtime - cpustat[c].sample[i].runtime;
978 brt = ps->sample[i + ((int)hz / 2)].runtime - ps->sample[i].runtime;
979
980 /*
981 * our definition of "idle":
982 *
983 * if for (hz / 2) we've used less CPU than (interval / 2) ...
984 * defaults to 4.0%, which experimentally, is where atom idles
985 */
986 if ((crt - brt) < (interval / 2.0)) {
987 idletime = sampletime[i] - graph_start;
988 svg("\n<!-- idle detected at %.03f seconds -->\n",
989 idletime);
990 svg("<line class=\"idle\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
991 time_to_graph(idletime),
992 -scale_y,
993 time_to_graph(idletime),
994 ps_to_graph(pcount) + scale_y);
995 svg("<text class=\"idle\" x=\"%.03f\" y=\"%.03f\">%.01fs</text>\n",
996 time_to_graph(idletime) + 5.0,
997 ps_to_graph(pcount) + scale_y,
998 idletime);
999 break;
1000 }
1001 }
1002 }
1003
1004
1005 static void svg_top_ten_cpu(void)
1006 {
1007 struct ps_struct *top[10];
1008 struct ps_struct emptyps;
1009 struct ps_struct *ps;
1010 int n, m;
1011
1012 memset(&emptyps, 0, sizeof(struct ps_struct));
1013 for (n=0; n < 10; n++)
1014 top[n] = &emptyps;
1015
1016 /* walk all ps's and setup ptrs */
1017 ps = ps_first;
1018 while ((ps = get_next_ps(ps))) {
1019 for (n = 0; n < 10; n++) {
1020 if (ps->total <= top[n]->total)
1021 continue;
1022 /* cascade insert */
1023 for (m = 9; m > n; m--)
1024 top[m] = top[m-1];
1025 top[n] = ps;
1026 break;
1027 }
1028 }
1029
1030 svg("<text class=\"t2\" x=\"20\" y=\"0\">Top CPU consumers:</text>\n");
1031 for (n = 0; n < 10; n++)
1032 svg("<text class=\"t3\" x=\"20\" y=\"%d\">%3.03fs - <![CDATA[%s]]> [%d]</text>\n",
1033 20 + (n * 13),
1034 top[n]->total,
1035 top[n]->name,
1036 top[n]->pid);
1037 }
1038
1039
1040 static void svg_top_ten_pss(void)
1041 {
1042 struct ps_struct *top[10];
1043 struct ps_struct emptyps;
1044 struct ps_struct *ps;
1045 int n, m;
1046
1047 memset(&emptyps, 0, sizeof(struct ps_struct));
1048 for (n=0; n < 10; n++)
1049 top[n] = &emptyps;
1050
1051 /* walk all ps's and setup ptrs */
1052 ps = ps_first;
1053 while ((ps = get_next_ps(ps))) {
1054 for (n = 0; n < 10; n++) {
1055 if (ps->pss_max <= top[n]->pss_max)
1056 continue;
1057 /* cascade insert */
1058 for (m = 9; m > n; m--)
1059 top[m] = top[m-1];
1060 top[n] = ps;
1061 break;
1062 }
1063 }
1064
1065 svg("<text class=\"t2\" x=\"20\" y=\"0\">Top PSS consumers:</text>\n");
1066 for (n = 0; n < 10; n++)
1067 svg("<text class=\"t3\" x=\"20\" y=\"%d\">%dK - <![CDATA[%s]]> [%d]</text>\n",
1068 20 + (n * 13),
1069 top[n]->pss_max,
1070 top[n]->name,
1071 top[n]->pid);
1072 }
1073
1074
1075 void svg_do(const char *build)
1076 {
1077 struct ps_struct *ps;
1078
1079 memset(&str, 0, sizeof(str));
1080
1081 ps = ps_first;
1082
1083 /* count initcall thread count first */
1084 svg_do_initcall(1);
1085 ksize = (kcount ? ps_to_graph(kcount) + (scale_y * 2) : 0);
1086
1087 /* then count processes */
1088 while ((ps = get_next_ps(ps))) {
1089 if (!ps_filter(ps))
1090 pcount++;
1091 else
1092 pfiltered++;
1093 }
1094 psize = ps_to_graph(pcount) + (scale_y * 2);
1095
1096 esize = (entropy ? scale_y * 7 : 0);
1097
1098 /* after this, we can draw the header with proper sizing */
1099 svg_header();
1100
1101 svg("<g transform=\"translate(10,400)\">\n");
1102 svg_io_bi_bar();
1103 svg("</g>\n\n");
1104
1105 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (scale_y * 7.0));
1106 svg_io_bo_bar();
1107 svg("</g>\n\n");
1108
1109 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (scale_y * 14.0));
1110 svg_cpu_bar();
1111 svg("</g>\n\n");
1112
1113 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (scale_y * 21.0));
1114 svg_wait_bar();
1115 svg("</g>\n\n");
1116
1117 if (kcount) {
1118 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (scale_y * 28.0));
1119 svg_do_initcall(0);
1120 svg("</g>\n\n");
1121 }
1122
1123 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (scale_y * 28.0) + ksize);
1124 svg_ps_bars();
1125 svg("</g>\n\n");
1126
1127 svg("<g transform=\"translate(10, 0)\">\n");
1128 svg_title(build);
1129 svg("</g>\n\n");
1130
1131 svg("<g transform=\"translate(10,200)\">\n");
1132 svg_top_ten_cpu();
1133 svg("</g>\n\n");
1134
1135 if (entropy) {
1136 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (scale_y * 28.0) + ksize + psize);
1137 svg_entropy_bar();
1138 svg("</g>\n\n");
1139 }
1140
1141 if (pss) {
1142 svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (scale_y * 28.0) + ksize + psize + esize);
1143 svg_pss_graph();
1144 svg("</g>\n\n");
1145
1146 svg("<g transform=\"translate(410,200)\">\n");
1147 svg_top_ten_pss();
1148 svg("</g>\n\n");
1149 }
1150
1151 /* svg footer */
1152 svg("\n</svg>\n");
1153 }