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