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