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