]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/svg.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2009-2013 Intel Corporation
9 Auke Kok <auke-jan.h.kok@intel.com>
11 systemd is free software; you can redistribute it and/or modify it
12 under the terms of the GNU Lesser General Public License as published by
13 the Free Software Foundation; either version 2.1 of the License, or
14 (at your option) any later version.
16 systemd is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public License
22 along with systemd; If not, see <http://www.gnu.org/licenses/>.
30 #include <sys/utsname.h>
33 #include "alloc-util.h"
34 #include "architecture.h"
40 #include "bootchart.h"
45 #define time_to_graph(t) ((t) * arg_scale_x)
46 #define ps_to_graph(n) ((n) * arg_scale_y)
47 #define kb_to_graph(m) ((m) * arg_scale_y * 0.0001)
48 #define to_color(n) (192.0 - ((n) * 192.0))
50 static const char * const colorwheel
[12] = {
51 "rgb(255,32,32)", // red
52 "rgb(32,192,192)", // cyan
53 "rgb(255,128,32)", // orange
54 "rgb(128,32,192)", // blue-violet
55 "rgb(255,255,32)", // yellow
56 "rgb(192,32,128)", // red-violet
57 "rgb(32,255,32)", // green
58 "rgb(255,64,32)", // red-orange
59 "rgb(32,32,255)", // blue
60 "rgb(255,192,32)", // yellow-orange
61 "rgb(192,32,192)", // violet
62 "rgb(32,192,32)" // yellow-green
65 static double idletime
= -1.0;
66 static int pfiltered
= 0;
67 static int pcount
= 0;
68 static int kcount
= 0;
69 static double psize
= 0;
70 static double ksize
= 0;
71 static double esize
= 0;
72 static struct list_sample_data
*sampledata
;
73 static struct list_sample_data
*prev_sampledata
;
75 static void svg_header(FILE *of
, struct list_sample_data
*head
, double graph_start
, int n_cpus
) {
78 struct list_sample_data
*sampledata_last
;
82 sampledata_last
= head
;
83 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
84 sampledata_last
= sampledata
;
87 /* min width is about 1600px due to the label */
88 w
= 150.0 + 10.0 + time_to_graph(sampledata_last
->sampletime
- graph_start
);
89 w
= ((w
< 1600.0) ? 1600.0 : w
);
91 /* height is variable based on pss, psize, ksize */
92 h
= 400.0 + (arg_scale_y
* 30.0) /* base graphs and title */
93 + (arg_pss
? (100.0 * arg_scale_y
) + (arg_scale_y
* 7.0) : 0.0) /* pss estimate */
94 + psize
+ ksize
+ esize
+ (n_cpus
* 15 * arg_scale_y
);
96 fprintf(of
, "<?xml version=\"1.0\" standalone=\"no\"?>\n");
97 fprintf(of
, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" ");
98 fprintf(of
, "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
100 //fprintf(of, "<g transform=\"translate(10,%d)\">\n", 1000 + 150 + (pcount * 20));
101 fprintf(of
, "<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" ", w
, h
);
102 fprintf(of
, "xmlns=\"http://www.w3.org/2000/svg\">\n\n");
104 /* write some basic info as a comment, including some help */
105 fprintf(of
, "<!-- This file is a bootchart SVG file. It is best rendered in a browser -->\n");
106 fprintf(of
, "<!-- such as Chrome, Chromium, or Firefox. Other applications that -->\n");
107 fprintf(of
, "<!-- render these files properly but more slowly are ImageMagick, gimp, -->\n");
108 fprintf(of
, "<!-- inkscape, etc. To display the files on your system, just point -->\n");
109 fprintf(of
, "<!-- your browser to file:///run/log/ and click. This bootchart was -->\n\n");
111 fprintf(of
, "<!-- generated by bootchart version %s, running with options: -->\n", VERSION
);
112 fprintf(of
, "<!-- hz=\"%f\" n=\"%d\" -->\n", arg_hz
, arg_samples_len
);
113 fprintf(of
, "<!-- x=\"%f\" y=\"%f\" -->\n", arg_scale_x
, arg_scale_y
);
114 fprintf(of
, "<!-- rel=\"%d\" f=\"%d\" -->\n", arg_relative
, arg_filter
);
115 fprintf(of
, "<!-- p=\"%d\" e=\"%d\" -->\n", arg_pss
, arg_entropy
);
116 fprintf(of
, "<!-- o=\"%s\" i=\"%s\" -->\n\n", arg_output_path
, arg_init_path
);
119 fprintf(of
, "<defs>\n <style type=\"text/css\">\n <![CDATA[\n");
121 fprintf(of
, " rect { stroke-width: 1; }\n");
122 fprintf(of
, " rect.bg { fill: rgb(255,255,255); }\n");
123 fprintf(of
, " rect.cpu { fill: rgb(64,64,240); stroke-width: 0; fill-opacity: 0.7; }\n");
124 fprintf(of
, " rect.wait { fill: rgb(240,240,0); stroke-width: 0; fill-opacity: 0.7; }\n");
125 fprintf(of
, " rect.bi { fill: rgb(240,128,128); stroke-width: 0; fill-opacity: 0.7; }\n");
126 fprintf(of
, " rect.bo { fill: rgb(192,64,64); stroke-width: 0; fill-opacity: 0.7; }\n");
127 fprintf(of
, " rect.ps { fill: rgb(192,192,192); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
128 fprintf(of
, " rect.krnl { fill: rgb(240,240,0); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
129 fprintf(of
, " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n");
130 fprintf(of
, " rect.clrw { stroke-width: 0; fill-opacity: 0.7;}\n");
131 fprintf(of
, " line { stroke: rgb(64,64,64); stroke-width: 1; }\n");
132 fprintf(of
, "// line.sec1 { }\n");
133 fprintf(of
, " line.sec5 { stroke-width: 2; }\n");
134 fprintf(of
, " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n");
135 fprintf(of
, " line.dot { stroke-dasharray: 2 4; }\n");
136 fprintf(of
, " line.idle { stroke: rgb(64,64,64); stroke-dasharray: 10 6; stroke-opacity: 0.7; }\n");
138 fprintf(of
, " .run { font-size: 8; font-style: italic; }\n");
139 fprintf(of
, " text { font-family: Verdana, Helvetica; font-size: 10; }\n");
140 fprintf(of
, " text.sec { font-size: 8; }\n");
141 fprintf(of
, " text.t1 { font-size: 24; }\n");
142 fprintf(of
, " text.t2 { font-size: 12; }\n");
143 fprintf(of
, " text.idle { font-size: 18; }\n");
145 fprintf(of
, " ]]>\n </style>\n</defs>\n\n");
148 static int svg_title(FILE *of
, const char *build
, int pscount
, double log_start
, int overrun
) {
149 _cleanup_free_
char *cmdline
= NULL
;
150 _cleanup_free_
char *model
= NULL
;
151 _cleanup_free_
char *buf
= NULL
;
152 char date
[256] = "Unknown";
159 r
= read_one_line_file("/proc/cmdline", &cmdline
);
161 log_error_errno(r
, "Unable to read cmdline: %m");
165 /* extract root fs so we can find disk model name in sysfs */
166 /* FIXME: this works only in the simple case */
167 c
= strstr(cmdline
, "root=/dev/");
172 strncpy(rootbdev
, &c
[10], sizeof(rootbdev
) - 1);
174 snprintf(filename
, sizeof(filename
), "/sys/block/%s/device/model", rootbdev
);
176 r
= read_one_line_file(filename
, &model
);
178 log_info("Error reading disk model for %s: %m\n", rootbdev
);
181 /* various utsname parameters */
184 log_error("Error getting uname info\n");
190 r
= strftime(date
, sizeof(date
), "%a, %d %b %Y %H:%M:%S %z", localtime(&t
));
194 r
= get_proc_field("/proc/cpuinfo", PROC_CPUINFO_MODEL
, "\n", &buf
);
200 fprintf(of
, "<text class=\"t1\" x=\"0\" y=\"30\">Bootchart for %s - %s</text>\n",
202 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"50\">System: %s %s %s %s</text>\n",
203 uts
.sysname
, uts
.release
, uts
.version
, uts
.machine
);
204 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"65\">CPU: %s</text>\n", cpu
);
206 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"80\">Disk: %s</text>\n", model
);
207 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"95\">Boot options: %s</text>\n", cmdline
);
208 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"110\">Build: %s</text>\n", build
);
209 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"125\">Log start time: %.03fs</text>\n", log_start
);
210 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"140\">Idle time: ");
213 fprintf(of
, "%.03fs", idletime
);
215 fprintf(of
, "Not detected");
217 fprintf(of
, "</text>\n");
218 fprintf(of
, "<text class=\"sec\" x=\"20\" y=\"155\">Graph data: %.03f samples/sec, recorded %i total, dropped %i samples, %i processes, %i filtered</text>\n",
219 arg_hz
, arg_samples_len
, overrun
, pscount
, pfiltered
);
224 static void svg_graph_box(FILE *of
, struct list_sample_data
*head
, int height
, double graph_start
) {
227 double finalsample
= 0.0;
228 struct list_sample_data
*sampledata_last
;
230 sampledata_last
= head
;
231 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
232 sampledata_last
= sampledata
;
235 finalsample
= sampledata_last
->sampletime
;
237 /* outside box, fill */
238 fprintf(of
, "<rect class=\"box\" x=\"%.03f\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
240 time_to_graph(finalsample
- graph_start
),
241 ps_to_graph(height
));
243 for (d
= graph_start
; d
<= finalsample
;
244 d
+= (arg_scale_x
< 2.0 ? 60.0 : arg_scale_x
< 10.0 ? 1.0 : 0.1)) {
245 /* lines for each second */
247 fprintf(of
, " <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
248 time_to_graph(d
- graph_start
),
249 time_to_graph(d
- graph_start
),
250 ps_to_graph(height
));
251 else if (i
% 10 == 0)
252 fprintf(of
, " <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
253 time_to_graph(d
- graph_start
),
254 time_to_graph(d
- graph_start
),
255 ps_to_graph(height
));
257 fprintf(of
, " <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
258 time_to_graph(d
- graph_start
),
259 time_to_graph(d
- graph_start
),
260 ps_to_graph(height
));
264 fprintf(of
, " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
265 time_to_graph(d
- graph_start
),
266 -5.0, d
- graph_start
);
272 /* xml comments must not contain "--" */
273 static char* xml_comment_encode(const char* name
) {
276 enc_name
= strdup(name
);
280 for (p
= enc_name
; *p
; p
++)
281 if (p
[0] == '-' && p
[1] == '-')
287 static void svg_pss_graph(FILE *of
,
288 struct list_sample_data
*head
,
289 struct ps_struct
*ps_first
,
290 double graph_start
) {
291 struct ps_struct
*ps
;
293 struct list_sample_data
*sampledata_last
;
295 sampledata_last
= head
;
296 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
297 sampledata_last
= sampledata
;
301 fprintf(of
, "\n\n<!-- Pss memory size graph -->\n");
303 fprintf(of
, "\n <text class=\"t2\" x=\"5\" y=\"-15\">Memory allocation - Pss</text>\n");
305 /* vsize 1000 == 1000mb */
306 svg_graph_box(of
, head
, 100, graph_start
);
307 /* draw some hlines for usable memory sizes */
308 for (i
= 100000; i
< 1000000; i
+= 100000) {
309 fprintf(of
, " <line class=\"sec01\" x1=\"%.03f\" y1=\"%.0f\" x2=\"%.03f\" y2=\"%.0f\"/>\n",
312 time_to_graph(sampledata_last
->sampletime
- graph_start
),
314 fprintf(of
, " <text class=\"sec\" x=\"%.03f\" y=\"%.0f\">%dM</text>\n",
315 time_to_graph(sampledata_last
->sampletime
- graph_start
) + 5,
316 kb_to_graph(i
), (1000000 - i
) / 1000);
320 /* now plot the graph itself */
322 prev_sampledata
= head
;
323 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
326 struct ps_sched_struct
*cross_place
;
331 /* put all the small pss blocks into the bottom */
333 while (ps
->next_ps
) {
337 ps
->sample
= ps
->first
;
338 while (ps
->sample
->next
) {
339 ps
->sample
= ps
->sample
->next
;
340 if (ps
->sample
->sampledata
== sampledata
)
343 if (ps
->sample
->sampledata
== sampledata
) {
344 if (ps
->sample
->pss
<= (100 * arg_scale_y
))
345 top
+= ps
->sample
->pss
;
349 while (ps
->sample
->cross
) {
350 cross_place
= ps
->sample
->cross
;
351 ps
= ps
->sample
->cross
->ps_new
;
352 ps
->sample
= cross_place
;
353 if (ps
->sample
->pss
<= (100 * arg_scale_y
))
354 top
+= ps
->sample
->pss
;
357 fprintf(of
, " <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
359 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
360 kb_to_graph(1000000.0 - top
),
361 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
362 kb_to_graph(top
- bottom
));
365 /* now plot the ones that are of significant size */
367 while (ps
->next_ps
) {
371 ps
->sample
= ps
->first
;
372 while (ps
->sample
->next
) {
373 ps
->sample
= ps
->sample
->next
;
374 if (ps
->sample
->sampledata
== sampledata
)
377 /* don't draw anything smaller than 2mb */
378 if (ps
->sample
->sampledata
!= sampledata
)
380 if (ps
->sample
->pss
> (100 * arg_scale_y
)) {
381 top
= bottom
+ ps
->sample
->pss
;
382 fprintf(of
, " <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
383 colorwheel
[ps
->pid
% 12],
384 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
385 kb_to_graph(1000000.0 - top
),
386 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
387 kb_to_graph(top
- bottom
));
393 while ((cross_place
= ps
->sample
->cross
)) {
394 ps
= ps
->sample
->cross
->ps_new
;
395 ps
->sample
= cross_place
;
396 if (ps
->sample
->pss
> (100 * arg_scale_y
)) {
397 top
= bottom
+ ps
->sample
->pss
;
398 fprintf(of
, " <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
399 colorwheel
[ps
->pid
% 12],
400 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
401 kb_to_graph(1000000.0 - top
),
402 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
403 kb_to_graph(top
- bottom
));
408 prev_sampledata
= sampledata
;
412 /* overlay all the text labels */
414 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
417 struct ps_sched_struct
*prev_sample
;
418 struct ps_sched_struct
*cross_place
;
420 /* put all the small pss blocks into the bottom */
421 ps
= ps_first
->next_ps
;
422 while (ps
->next_ps
) {
427 ps
->sample
= ps
->first
;
428 while (ps
->sample
->next
) {
429 ps
->sample
= ps
->sample
->next
;
430 if (ps
->sample
->sampledata
== sampledata
)
434 if (ps
->sample
->sampledata
== sampledata
) {
435 if (ps
->sample
->pss
<= (100 * arg_scale_y
))
436 top
+= ps
->sample
->pss
;
442 while ((cross_place
= ps
->sample
->cross
)) {
443 ps
= ps
->sample
->cross
->ps_new
;
444 ps
->sample
= cross_place
;
445 if (ps
->sample
->pss
<= (100 * arg_scale_y
))
446 top
+= ps
->sample
->pss
;
450 /* now plot the ones that are of significant size */
452 while (ps
->next_ps
) {
453 prev_sample
= ps
->sample
;
457 ps
->sample
= ps
->first
;
458 while (ps
->sample
->next
) {
459 prev_sample
= ps
->sample
;
460 ps
->sample
= ps
->sample
->next
;
461 if (ps
->sample
->sampledata
== sampledata
)
464 /* don't draw anything smaller than 2mb */
465 if (ps
->sample
->sampledata
== sampledata
) {
466 if (ps
->sample
->pss
> (100 * arg_scale_y
)) {
467 top
= bottom
+ ps
->sample
->pss
;
468 /* draw a label with the process / PID */
469 if ((i
== 1) || (prev_sample
->pss
<= (100 * arg_scale_y
)))
470 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n",
471 time_to_graph(sampledata
->sampletime
- graph_start
),
472 kb_to_graph(1000000.0 - bottom
- ((top
- bottom
) / 2)),
479 while ((cross_place
= ps
->sample
->cross
)) {
480 ps
= ps
->sample
->cross
->ps_new
;
481 ps
->sample
= cross_place
;
482 prev_sample
= ps
->sample
->prev
;
483 if (ps
->sample
->pss
> (100 * arg_scale_y
)) {
484 top
= bottom
+ ps
->sample
->pss
;
485 /* draw a label with the process / PID */
486 if ((i
== 1) || (prev_sample
->pss
<= (100 * arg_scale_y
)))
487 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n",
488 time_to_graph(sampledata
->sampletime
- graph_start
),
489 kb_to_graph(1000000.0 - bottom
- ((top
- bottom
) / 2)),
498 /* debug output - full data dump */
499 fprintf(of
, "\n\n<!-- PSS map - csv format -->\n");
501 while (ps
->next_ps
) {
502 _cleanup_free_
char *enc_name
= NULL
;
507 enc_name
= xml_comment_encode(ps
->name
);
511 fprintf(of
, "<!-- %s [%d] pss=", enc_name
, ps
->pid
);
513 ps
->sample
= ps
->first
;
514 while (ps
->sample
->next
) {
515 ps
->sample
= ps
->sample
->next
;
516 fprintf(of
, "%d," , ps
->sample
->pss
);
519 fprintf(of
, " -->\n");
524 static void svg_io_bi_bar(FILE *of
,
525 struct list_sample_data
*head
,
535 struct list_sample_data
*start_sampledata
;
536 struct list_sample_data
*stop_sampledata
;
538 fprintf(of
, "<!-- IO utilization graph - In -->\n");
539 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - read</text>\n");
542 * calculate rounding range
544 * We need to round IO data since IO block data is not updated on
545 * each poll. Applying a smoothing function loses some burst data,
546 * so keep the smoothing range short.
548 range
= 0.25 / (1.0 / arg_hz
);
550 range
= 2.0; /* no smoothing */
552 /* surrounding box */
553 svg_graph_box(of
, head
, 5, graph_start
);
555 /* find the max IO first */
557 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
563 start
= MAX(i
- ((range
/ 2) - 1), 0);
564 stop
= MIN(i
+ (range
/ 2), n_samples
- 1);
565 diff
= (stop
- start
);
567 start_sampledata
= sampledata
;
568 stop_sampledata
= sampledata
;
570 for (k
= 0; k
< ((range
/2) - 1) && start_sampledata
->link_next
; k
++)
571 start_sampledata
= start_sampledata
->link_next
;
573 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
574 stop_sampledata
= stop_sampledata
->link_prev
;
576 tot
= (double)(stop_sampledata
->blockstat
.bi
- start_sampledata
->blockstat
.bi
) / diff
;
583 tot
= (double)(stop_sampledata
->blockstat
.bo
- start_sampledata
->blockstat
.bo
) / diff
;
593 prev_sampledata
= head
;
594 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
601 start
= MAX(i
- ((range
/ 2) - 1), 0);
602 stop
= MIN(i
+ (range
/ 2), n_samples
);
603 diff
= (stop
- start
);
605 start_sampledata
= sampledata
;
606 stop_sampledata
= sampledata
;
608 for (k
= 0; k
< ((range
/2)-1) && start_sampledata
->link_next
; k
++)
609 start_sampledata
= start_sampledata
->link_next
;
611 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
612 stop_sampledata
= stop_sampledata
->link_prev
;
614 tot
= (double)(stop_sampledata
->blockstat
.bi
- start_sampledata
->blockstat
.bi
) / diff
;
620 fprintf(of
, "<rect class=\"bi\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
621 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
622 (arg_scale_y
* 5) - (pbi
* (arg_scale_y
* 5)),
623 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
624 pbi
* (arg_scale_y
* 5));
626 /* labels around highest value */
628 fprintf(of
, " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
629 time_to_graph(sampledata
->sampletime
- graph_start
) + 5,
630 ((arg_scale_y
* 5) - (pbi
* (arg_scale_y
* 5))) + 15,
631 max
/ 1024.0 / (interval
/ 1000000000.0));
634 prev_sampledata
= sampledata
;
638 static void svg_io_bo_bar(FILE *of
,
639 struct list_sample_data
*head
,
648 struct list_sample_data
*start_sampledata
;
649 struct list_sample_data
*stop_sampledata
;
651 fprintf(of
, "<!-- IO utilization graph - out -->\n");
652 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - write</text>\n");
655 * calculate rounding range
657 * We need to round IO data since IO block data is not updated on
658 * each poll. Applying a smoothing function loses some burst data,
659 * so keep the smoothing range short.
661 range
= 0.25 / (1.0 / arg_hz
);
663 range
= 2.0; /* no smoothing */
665 /* surrounding box */
666 svg_graph_box(of
, head
, 5, graph_start
);
668 /* find the max IO first */
670 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
676 start
= MAX(i
- ((range
/ 2) - 1), 0);
677 stop
= MIN(i
+ (range
/ 2), n_samples
- 1);
678 diff
= (stop
- start
);
680 start_sampledata
= sampledata
;
681 stop_sampledata
= sampledata
;
683 for (k
= 0; k
< (range
/2) - 1 && start_sampledata
->link_next
; k
++)
684 start_sampledata
= start_sampledata
->link_next
;
686 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
687 stop_sampledata
= stop_sampledata
->link_prev
;
689 tot
= (double)(stop_sampledata
->blockstat
.bi
- start_sampledata
->blockstat
.bi
) / diff
;
693 tot
= (double)(stop_sampledata
->blockstat
.bo
- start_sampledata
->blockstat
.bo
) / diff
;
703 prev_sampledata
= head
;
706 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
707 int start
, stop
, diff
;
712 start
= MAX(i
- ((range
/ 2) - 1), 0);
713 stop
= MIN(i
+ (range
/ 2), n_samples
);
714 diff
= (stop
- start
);
716 start_sampledata
= sampledata
;
717 stop_sampledata
= sampledata
;
719 for (k
= 0; k
< ((range
/2)-1) && start_sampledata
->link_next
; k
++)
720 start_sampledata
= start_sampledata
->link_next
;
722 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
723 stop_sampledata
= stop_sampledata
->link_prev
;
725 tot
= (double)(stop_sampledata
->blockstat
.bo
- start_sampledata
->blockstat
.bo
)
732 fprintf(of
, "<rect class=\"bo\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
733 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
734 (arg_scale_y
* 5) - (pbo
* (arg_scale_y
* 5)),
735 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
736 pbo
* (arg_scale_y
* 5));
738 /* labels around highest bo value */
740 fprintf(of
, " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
741 time_to_graph(sampledata
->sampletime
- graph_start
) + 5,
742 ((arg_scale_y
* 5) - (pbo
* (arg_scale_y
* 5))),
743 max
/ 1024.0 / (interval
/ 1000000000.0));
746 prev_sampledata
= sampledata
;
750 static void svg_cpu_bar(FILE *of
, struct list_sample_data
*head
, int n_cpus
, int cpu_num
, double graph_start
) {
752 fprintf(of
, "<!-- CPU utilization graph -->\n");
755 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] utilization</text>\n");
757 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] utilization</text>\n", cpu_num
);
759 /* surrounding box */
760 svg_graph_box(of
, head
, 5, graph_start
);
762 /* bars for each sample, proportional to the CPU util. */
763 prev_sampledata
= head
;
764 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
772 for (c
= 0; c
< n_cpus
; c
++)
773 trt
+= sampledata
->runtime
[c
] - prev_sampledata
->runtime
[c
];
775 trt
= sampledata
->runtime
[cpu_num
] - prev_sampledata
->runtime
[cpu_num
];
777 trt
= trt
/ 1000000000.0;
780 trt
= trt
/ (double)n_cpus
;
783 ptrt
= trt
/ (sampledata
->sampletime
- prev_sampledata
->sampletime
);
789 fprintf(of
, "<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
790 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
791 (arg_scale_y
* 5) - (ptrt
* (arg_scale_y
* 5)),
792 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
793 ptrt
* (arg_scale_y
* 5));
795 prev_sampledata
= sampledata
;
799 static void svg_wait_bar(FILE *of
, struct list_sample_data
*head
, int n_cpus
, int cpu_num
, double graph_start
) {
801 fprintf(of
, "<!-- Wait time aggregation box -->\n");
804 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] wait</text>\n");
806 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] wait</text>\n", cpu_num
);
808 /* surrounding box */
809 svg_graph_box(of
, head
, 5, graph_start
);
811 /* bars for each sample, proportional to the CPU util. */
812 prev_sampledata
= head
;
813 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
821 for (c
= 0; c
< n_cpus
; c
++)
822 twt
+= sampledata
->waittime
[c
] - prev_sampledata
->waittime
[c
];
824 twt
= sampledata
->waittime
[cpu_num
] - prev_sampledata
->waittime
[cpu_num
];
826 twt
= twt
/ 1000000000.0;
829 twt
= twt
/ (double)n_cpus
;
832 ptwt
= twt
/ (sampledata
->sampletime
- prev_sampledata
->sampletime
);
838 fprintf(of
, "<rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
839 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
840 ((arg_scale_y
* 5) - (ptwt
* (arg_scale_y
* 5))),
841 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
842 ptwt
* (arg_scale_y
* 5));
844 prev_sampledata
= sampledata
;
848 static void svg_entropy_bar(FILE *of
, struct list_sample_data
*head
, double graph_start
) {
850 fprintf(of
, "<!-- entropy pool graph -->\n");
852 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">Entropy pool size</text>\n");
853 /* surrounding box */
854 svg_graph_box(of
, head
, 5, graph_start
);
856 /* bars for each sample, scale 0-4096 */
857 prev_sampledata
= head
;
858 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
859 fprintf(of
, "<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
860 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
861 ((arg_scale_y
* 5) - ((sampledata
->entropy_avail
/ 4096.) * (arg_scale_y
* 5))),
862 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
863 (sampledata
->entropy_avail
/ 4096.) * (arg_scale_y
* 5));
864 prev_sampledata
= sampledata
;
868 static struct ps_struct
*get_next_ps(struct ps_struct
*ps
, struct ps_struct
*ps_first
) {
870 * walk the list of processes and return the next one to be
884 /* go back for parent siblings */
886 if (ps
->parent
&& ps
->parent
->next
)
887 return ps
->parent
->next
;
897 static bool ps_filter(struct ps_struct
*ps
) {
901 /* can't draw data when there is only 1 sample (need start + stop) */
902 if (ps
->first
== ps
->last
)
905 /* don't filter kthreadd */
909 /* drop stuff that doesn't use any real CPU time */
910 if (ps
->total
<= 0.001)
916 static void svg_do_initcall(FILE *of
, struct list_sample_data
*head
, int count_only
, double graph_start
) {
917 _cleanup_pclose_
FILE *f
= NULL
;
923 /* can't plot initcall when disabled or in relative mode */
924 if (!arg_initcall
|| arg_relative
) {
930 fprintf(of
, "<!-- initcall -->\n");
931 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">Kernel init threads</text>\n");
932 /* surrounding box */
933 svg_graph_box(of
, head
, kcount
, graph_start
);
939 * Initcall graphing - parses dmesg buffer and displays kernel threads
940 * This somewhat uses the same methods and scaling to show processes
941 * but looks a lot simpler. It's overlaid entirely onto the PS graph
945 f
= popen("dmesg", "r");
954 if (fgets(l
, sizeof(l
) - 1, f
) == NULL
)
957 c
= sscanf(l
, "[%lf] initcall %s %*s %d %*s %d %*s",
958 &t
, func
, &ret
, &usecs
);
960 /* also parse initcalls done by module loading */
961 c
= sscanf(l
, "[%lf] initcall %s %*s %*s %d %*s %d %*s",
962 &t
, func
, &ret
, &usecs
);
967 /* chop the +0xXX/0xXX stuff */
968 while(func
[z
] != '+')
973 /* filter out irrelevant stuff */
979 fprintf(of
, "<!-- thread=\"%s\" time=\"%.3f\" elapsed=\"%d\" result=\"%d\" -->\n",
980 func
, t
, usecs
, ret
);
986 fprintf(of
, " <rect class=\"krnl\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
987 time_to_graph(t
- (usecs
/ 1000000.0)),
989 time_to_graph(usecs
/ 1000000.0),
993 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\">%s <tspan class=\"run\">%.03fs</tspan></text>\n",
994 time_to_graph(t
- (usecs
/ 1000000.0)) + 5,
995 ps_to_graph(kcount
) + 15,
996 func
, usecs
/ 1000000.0);
1002 static void svg_ps_bars(FILE *of
,
1003 struct list_sample_data
*head
,
1006 struct ps_struct
*ps_first
,
1010 struct ps_struct
*ps
;
1016 fprintf(of
, "<!-- Process graph -->\n");
1017 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">Processes</text>\n");
1019 /* surrounding box */
1020 svg_graph_box(of
, head
, pcount
, graph_start
);
1022 /* pass 2 - ps boxes */
1024 while ((ps
= get_next_ps(ps
, ps_first
))) {
1025 _cleanup_free_
char *enc_name
= NULL
, *escaped
= NULL
;
1030 if (!utf8_is_printable(ps
->name
, strlen(ps
->name
)))
1031 escaped
= utf8_escape_non_printable(ps
->name
);
1033 enc_name
= xml_comment_encode(escaped
? escaped
: ps
->name
);
1037 /* leave some trace of what we actually filtered etc. */
1038 fprintf(of
, "<!-- %s [%i] ppid=%i runtime=%.03fs -->\n", enc_name
, ps
->pid
,
1039 ps
->ppid
, ps
->total
);
1041 starttime
= ps
->first
->sampledata
->sampletime
;
1043 if (!ps_filter(ps
)) {
1044 /* remember where _to_ our children need to draw a line */
1045 ps
->pos_x
= time_to_graph(starttime
- graph_start
);
1046 ps
->pos_y
= ps_to_graph(j
+1); /* bottom left corner */
1047 } else if (ps
->parent
){
1048 /* hook children to our parent coords instead */
1049 ps
->pos_x
= ps
->parent
->pos_x
;
1050 ps
->pos_y
= ps
->parent
->pos_y
;
1052 /* if this is the last child, we might still need to draw a connecting line */
1053 if ((!ps
->next
) && (ps
->parent
))
1054 fprintf(of
, " <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1056 ps_to_graph(j
-1) + 10.0, /* whee, use the last value here */
1062 endtime
= ps
->last
->sampledata
->sampletime
;
1063 fprintf(of
, " <rect class=\"ps\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1064 time_to_graph(starttime
- graph_start
),
1066 time_to_graph(ps
->last
->sampledata
->sampletime
- starttime
),
1069 /* paint cpu load over these */
1070 ps
->sample
= ps
->first
;
1072 while (ps
->sample
->next
) {
1075 struct ps_sched_struct
*prev
;
1078 ps
->sample
= ps
->sample
->next
;
1080 /* calculate over interval */
1081 rt
= ps
->sample
->runtime
- prev
->runtime
;
1082 wt
= ps
->sample
->waittime
- prev
->waittime
;
1084 prt
= (rt
/ 1000000000) / (ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
);
1085 wrt
= (wt
/ 1000000000) / (ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
);
1087 /* this can happen if timekeeping isn't accurate enough */
1093 if ((prt
< 0.1) && (wrt
< 0.1)) /* =~ 26 (color threshold) */
1096 fprintf(of
, " <rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1097 time_to_graph(prev
->sampledata
->sampletime
- graph_start
),
1099 time_to_graph(ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
),
1102 /* draw cpu over wait - TODO figure out how/why run + wait > interval */
1103 fprintf(of
, " <rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1104 time_to_graph(prev
->sampledata
->sampletime
- graph_start
),
1105 ps_to_graph(j
+ (1.0 - prt
)),
1106 time_to_graph(ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
),
1111 /* determine where to display the process name */
1112 if ((endtime
- starttime
) < 1.5)
1113 /* too small to fit label inside the box */
1118 /* text label of process name */
1119 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]<tspan class=\"run\">%.03fs</tspan> %s</text>\n",
1120 time_to_graph(w
- graph_start
) + 5.0,
1121 ps_to_graph(j
) + 14.0,
1122 escaped
? escaped
: ps
->name
,
1124 (ps
->last
->runtime
- ps
->first
->runtime
) / 1000000000.0,
1125 arg_show_cgroup
? ps
->cgroup
: "");
1126 /* paint lines to the parent process */
1128 /* horizontal part */
1129 fprintf(of
, " <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1130 time_to_graph(starttime
- graph_start
),
1131 ps_to_graph(j
) + 10.0,
1133 ps_to_graph(j
) + 10.0);
1135 /* one vertical line connecting all the horizontal ones up */
1137 fprintf(of
, " <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1139 ps_to_graph(j
) + 10.0,
1144 j
++; /* count boxes */
1149 /* last pass - determine when idle */
1151 /* make sure we start counting from the point where we actually have
1152 * data: assume that bootchart's first sample is when data started
1156 while (ps
->next_ps
) {
1162 /* need to know last node first */
1163 ps
->sample
= ps
->first
;
1164 i
= ps
->sample
->next
->sampledata
->counter
;
1166 while (ps
->sample
->next
&& i
<(n_samples
-(arg_hz
/2))) {
1171 struct ps_sched_struct
*sample_hz
;
1173 ps
->sample
= ps
->sample
->next
;
1174 sample_hz
= ps
->sample
;
1175 for (ii
= 0; (ii
< (int)arg_hz
/2) && sample_hz
->next
; ii
++)
1176 sample_hz
= sample_hz
->next
;
1178 /* subtract bootchart cpu utilization from total */
1180 for (c
= 0; c
< n_cpus
; c
++)
1181 crt
+= sample_hz
->sampledata
->runtime
[c
] - ps
->sample
->sampledata
->runtime
[c
];
1183 brt
= sample_hz
->runtime
- ps
->sample
->runtime
;
1185 * our definition of "idle":
1187 * if for (hz / 2) we've used less CPU than (interval / 2) ...
1188 * defaults to 4.0%, which experimentally, is where atom idles
1190 if ((crt
- brt
) < (interval
/ 2.0)) {
1191 idletime
= ps
->sample
->sampledata
->sampletime
- graph_start
;
1192 fprintf(of
, "\n<!-- idle detected at %.03f seconds -->\n", idletime
);
1193 fprintf(of
, "<line class=\"idle\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1194 time_to_graph(idletime
),
1196 time_to_graph(idletime
),
1197 ps_to_graph(pcount
) + arg_scale_y
);
1198 fprintf(of
, "<text class=\"idle\" x=\"%.03f\" y=\"%.03f\">%.01fs</text>\n",
1199 time_to_graph(idletime
) + 5.0,
1200 ps_to_graph(pcount
) + arg_scale_y
,
1209 static void svg_top_ten_cpu(FILE *of
, struct ps_struct
*ps_first
) {
1210 struct ps_struct
*top
[10];
1211 struct ps_struct emptyps
= {};
1212 struct ps_struct
*ps
;
1215 for (n
= 0; n
< (int) ELEMENTSOF(top
); n
++)
1218 /* walk all ps's and setup ptrs */
1220 while ((ps
= get_next_ps(ps
, ps_first
))) {
1221 for (n
= 0; n
< 10; n
++) {
1222 if (ps
->total
<= top
[n
]->total
)
1224 /* cascade insert */
1225 for (m
= 9; m
> n
; m
--)
1232 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"0\">Top CPU consumers:</text>\n");
1233 for (n
= 0; n
< 10; n
++)
1234 fprintf(of
, "<text class=\"t3\" x=\"20\" y=\"%d\">%3.03fs - <![CDATA[%s]]> [%d]</text>\n",
1241 static void svg_top_ten_pss(FILE *of
, struct ps_struct
*ps_first
) {
1242 struct ps_struct
*top
[10];
1243 struct ps_struct emptyps
= {};
1244 struct ps_struct
*ps
;
1247 for (n
= 0; n
< (int) ELEMENTSOF(top
); n
++)
1250 /* walk all ps's and setup ptrs */
1252 while ((ps
= get_next_ps(ps
, ps_first
))) {
1253 for (n
= 0; n
< 10; n
++) {
1254 if (ps
->pss_max
<= top
[n
]->pss_max
)
1257 /* cascade insert */
1258 for (m
= 9; m
> n
; m
--)
1265 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"0\">Top PSS consumers:</text>\n");
1266 for (n
= 0; n
< 10; n
++)
1267 fprintf(of
, "<text class=\"t3\" x=\"20\" y=\"%d\">%dK - <![CDATA[%s]]> [%d]</text>\n",
1274 int svg_do(FILE *of
,
1276 struct list_sample_data
*head
,
1277 struct ps_struct
*ps_first
,
1286 struct ps_struct
*ps
;
1291 LIST_FIND_TAIL(link
, sampledata
, head
);
1294 /* count initcall thread count first */
1295 svg_do_initcall(of
, head
, 1, graph_start
);
1296 ksize
= kcount
? ps_to_graph(kcount
) + (arg_scale_y
* 2) : 0;
1298 /* then count processes */
1299 while ((ps
= get_next_ps(ps
, ps_first
))) {
1305 psize
= ps_to_graph(pcount
) + (arg_scale_y
* 2);
1307 esize
= (arg_entropy
? arg_scale_y
* 7 : 0);
1309 /* after this, we can draw the header with proper sizing */
1310 svg_header(of
, head
, graph_start
, arg_percpu
? n_cpus
: 0);
1311 fprintf(of
, "<rect class=\"bg\" width=\"100%%\" height=\"100%%\" />\n\n");
1313 fprintf(of
, "<g transform=\"translate(10,400)\">\n");
1314 svg_io_bi_bar(of
, head
, n_samples
, graph_start
, interval
);
1315 fprintf(of
, "</g>\n\n");
1317 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1318 svg_io_bo_bar(of
, head
, n_samples
, graph_start
, interval
);
1319 fprintf(of
, "</g>\n\n");
1321 for (c
= -1; c
< (arg_percpu
? n_cpus
: 0); c
++) {
1323 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1324 svg_cpu_bar(of
, head
, n_cpus
, c
, graph_start
);
1325 fprintf(of
, "</g>\n\n");
1328 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1329 svg_wait_bar(of
, head
, n_cpus
, c
, graph_start
);
1330 fprintf(of
, "</g>\n\n");
1335 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1336 svg_do_initcall(of
, head
, 0, graph_start
);
1337 fprintf(of
, "</g>\n\n");
1341 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
) + ksize
);
1342 svg_ps_bars(of
, head
, n_samples
, n_cpus
, ps_first
, graph_start
, interval
);
1343 fprintf(of
, "</g>\n\n");
1345 fprintf(of
, "<g transform=\"translate(10, 0)\">\n");
1346 r
= svg_title(of
, build
, pscount
, log_start
, overrun
);
1347 fprintf(of
, "</g>\n\n");
1352 fprintf(of
, "<g transform=\"translate(10,200)\">\n");
1353 svg_top_ten_cpu(of
, ps_first
);
1354 fprintf(of
, "</g>\n\n");
1357 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
) + ksize
+ psize
);
1358 svg_entropy_bar(of
, head
, graph_start
);
1359 fprintf(of
, "</g>\n\n");
1363 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
) + ksize
+ psize
+ esize
);
1364 svg_pss_graph(of
, head
, ps_first
, graph_start
);
1365 fprintf(of
, "</g>\n\n");
1367 fprintf(of
, "<g transform=\"translate(410,200)\">\n");
1368 svg_top_ten_pss(of
, ps_first
);
1369 fprintf(of
, "</g>\n\n");
1372 /* fprintf footer */
1373 fprintf(of
, "\n</svg>\n");