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