]>
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>
38 #include "bootchart.h"
42 #define time_to_graph(t) ((t) * arg_scale_x)
43 #define ps_to_graph(n) ((n) * arg_scale_y)
44 #define kb_to_graph(m) ((m) * arg_scale_y * 0.0001)
45 #define to_color(n) (192.0 - ((n) * 192.0))
47 static const char * const colorwheel
[12] = {
48 "rgb(255,32,32)", // red
49 "rgb(32,192,192)", // cyan
50 "rgb(255,128,32)", // orange
51 "rgb(128,32,192)", // blue-violet
52 "rgb(255,255,32)", // yellow
53 "rgb(192,32,128)", // red-violet
54 "rgb(32,255,32)", // green
55 "rgb(255,64,32)", // red-orange
56 "rgb(32,32,255)", // blue
57 "rgb(255,192,32)", // yellow-orange
58 "rgb(192,32,192)", // violet
59 "rgb(32,192,32)" // yellow-green
62 static double idletime
= -1.0;
63 static int pfiltered
= 0;
64 static int pcount
= 0;
65 static int kcount
= 0;
66 static double psize
= 0;
67 static double ksize
= 0;
68 static double esize
= 0;
69 static struct list_sample_data
*sampledata
;
70 static struct list_sample_data
*prev_sampledata
;
72 static void svg_header(FILE *of
, struct list_sample_data
*head
, double graph_start
, int n_cpus
) {
75 struct list_sample_data
*sampledata_last
;
79 sampledata_last
= head
;
80 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
81 sampledata_last
= sampledata
;
84 /* min width is about 1600px due to the label */
85 w
= 150.0 + 10.0 + time_to_graph(sampledata_last
->sampletime
- graph_start
);
86 w
= ((w
< 1600.0) ? 1600.0 : w
);
88 /* height is variable based on pss, psize, ksize */
89 h
= 400.0 + (arg_scale_y
* 30.0) /* base graphs and title */
90 + (arg_pss
? (100.0 * arg_scale_y
) + (arg_scale_y
* 7.0) : 0.0) /* pss estimate */
91 + psize
+ ksize
+ esize
+ (n_cpus
* 15 * arg_scale_y
);
93 fprintf(of
, "<?xml version=\"1.0\" standalone=\"no\"?>\n");
94 fprintf(of
, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" ");
95 fprintf(of
, "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
97 //fprintf(of, "<g transform=\"translate(10,%d)\">\n", 1000 + 150 + (pcount * 20));
98 fprintf(of
, "<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" ", w
, h
);
99 fprintf(of
, "xmlns=\"http://www.w3.org/2000/svg\">\n\n");
101 /* write some basic info as a comment, including some help */
102 fprintf(of
, "<!-- This file is a bootchart SVG file. It is best rendered in a browser -->\n");
103 fprintf(of
, "<!-- such as Chrome, Chromium, or Firefox. Other applications that -->\n");
104 fprintf(of
, "<!-- render these files properly but more slowly are ImageMagick, gimp, -->\n");
105 fprintf(of
, "<!-- inkscape, etc. To display the files on your system, just point -->\n");
106 fprintf(of
, "<!-- your browser to file:///run/log/ and click. This bootchart was -->\n\n");
108 fprintf(of
, "<!-- generated by bootchart version %s, running with options: -->\n", VERSION
);
109 fprintf(of
, "<!-- hz=\"%f\" n=\"%d\" -->\n", arg_hz
, arg_samples_len
);
110 fprintf(of
, "<!-- x=\"%f\" y=\"%f\" -->\n", arg_scale_x
, arg_scale_y
);
111 fprintf(of
, "<!-- rel=\"%d\" f=\"%d\" -->\n", arg_relative
, arg_filter
);
112 fprintf(of
, "<!-- p=\"%d\" e=\"%d\" -->\n", arg_pss
, arg_entropy
);
113 fprintf(of
, "<!-- o=\"%s\" i=\"%s\" -->\n\n", arg_output_path
, arg_init_path
);
116 fprintf(of
, "<defs>\n <style type=\"text/css\">\n <![CDATA[\n");
118 fprintf(of
, " rect { stroke-width: 1; }\n");
119 fprintf(of
, " rect.bg { fill: rgb(255,255,255); }\n");
120 fprintf(of
, " rect.cpu { fill: rgb(64,64,240); stroke-width: 0; fill-opacity: 0.7; }\n");
121 fprintf(of
, " rect.wait { fill: rgb(240,240,0); stroke-width: 0; fill-opacity: 0.7; }\n");
122 fprintf(of
, " rect.bi { fill: rgb(240,128,128); stroke-width: 0; fill-opacity: 0.7; }\n");
123 fprintf(of
, " rect.bo { fill: rgb(192,64,64); stroke-width: 0; fill-opacity: 0.7; }\n");
124 fprintf(of
, " rect.ps { fill: rgb(192,192,192); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
125 fprintf(of
, " rect.krnl { fill: rgb(240,240,0); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n");
126 fprintf(of
, " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n");
127 fprintf(of
, " rect.clrw { stroke-width: 0; fill-opacity: 0.7;}\n");
128 fprintf(of
, " line { stroke: rgb(64,64,64); stroke-width: 1; }\n");
129 fprintf(of
, "// line.sec1 { }\n");
130 fprintf(of
, " line.sec5 { stroke-width: 2; }\n");
131 fprintf(of
, " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n");
132 fprintf(of
, " line.dot { stroke-dasharray: 2 4; }\n");
133 fprintf(of
, " line.idle { stroke: rgb(64,64,64); stroke-dasharray: 10 6; stroke-opacity: 0.7; }\n");
135 fprintf(of
, " .run { font-size: 8; font-style: italic; }\n");
136 fprintf(of
, " text { font-family: Verdana, Helvetica; font-size: 10; }\n");
137 fprintf(of
, " text.sec { font-size: 8; }\n");
138 fprintf(of
, " text.t1 { font-size: 24; }\n");
139 fprintf(of
, " text.t2 { font-size: 12; }\n");
140 fprintf(of
, " text.idle { font-size: 18; }\n");
142 fprintf(of
, " ]]>\n </style>\n</defs>\n\n");
145 static int svg_title(FILE *of
, const char *build
, int pscount
, double log_start
, int overrun
) {
146 _cleanup_free_
char *cmdline
= NULL
;
147 _cleanup_free_
char *model
= NULL
;
148 _cleanup_free_
char *buf
= NULL
;
149 char date
[256] = "Unknown";
156 r
= read_one_line_file("/proc/cmdline", &cmdline
);
158 log_error_errno(r
, "Unable to read cmdline: %m");
162 /* extract root fs so we can find disk model name in sysfs */
163 /* FIXME: this works only in the simple case */
164 c
= strstr(cmdline
, "root=/dev/");
169 strncpy(rootbdev
, &c
[10], sizeof(rootbdev
) - 1);
171 snprintf(filename
, sizeof(filename
), "/sys/block/%s/device/model", rootbdev
);
173 r
= read_one_line_file(filename
, &model
);
175 log_info("Error reading disk model for %s: %m\n", rootbdev
);
178 /* various utsname parameters */
181 log_error("Error getting uname info\n");
187 r
= strftime(date
, sizeof(date
), "%a, %d %b %Y %H:%M:%S %z", localtime(&t
));
191 r
= read_full_file("/proc/cpuinfo", &buf
, NULL
);
193 return log_error_errno(r
, "Unable to read cpuinfo: %m");
195 cpu
= strstr(buf
, "model name");
197 log_error("Unable to read module name from cpuinfo.\n");
202 c
= strchr(cpu
, '\n');
206 fprintf(of
, "<text class=\"t1\" x=\"0\" y=\"30\">Bootchart for %s - %s</text>\n",
208 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"50\">System: %s %s %s %s</text>\n",
209 uts
.sysname
, uts
.release
, uts
.version
, uts
.machine
);
210 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"65\">CPU: %s</text>\n", cpu
);
212 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"80\">Disk: %s</text>\n", model
);
213 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"95\">Boot options: %s</text>\n", cmdline
);
214 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"110\">Build: %s</text>\n", build
);
215 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"125\">Log start time: %.03fs</text>\n", log_start
);
216 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"140\">Idle time: ");
219 fprintf(of
, "%.03fs", idletime
);
221 fprintf(of
, "Not detected");
223 fprintf(of
, "</text>\n");
224 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",
225 arg_hz
, arg_samples_len
, overrun
, pscount
, pfiltered
);
230 static void svg_graph_box(FILE *of
, struct list_sample_data
*head
, int height
, double graph_start
) {
233 double finalsample
= 0.0;
234 struct list_sample_data
*sampledata_last
;
236 sampledata_last
= head
;
237 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
238 sampledata_last
= sampledata
;
241 finalsample
= sampledata_last
->sampletime
;
243 /* outside box, fill */
244 fprintf(of
, "<rect class=\"box\" x=\"%.03f\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
246 time_to_graph(finalsample
- graph_start
),
247 ps_to_graph(height
));
249 for (d
= graph_start
; d
<= finalsample
;
250 d
+= (arg_scale_x
< 2.0 ? 60.0 : arg_scale_x
< 10.0 ? 1.0 : 0.1)) {
251 /* lines for each second */
253 fprintf(of
, " <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
254 time_to_graph(d
- graph_start
),
255 time_to_graph(d
- graph_start
),
256 ps_to_graph(height
));
257 else if (i
% 10 == 0)
258 fprintf(of
, " <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
259 time_to_graph(d
- graph_start
),
260 time_to_graph(d
- graph_start
),
261 ps_to_graph(height
));
263 fprintf(of
, " <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
264 time_to_graph(d
- graph_start
),
265 time_to_graph(d
- graph_start
),
266 ps_to_graph(height
));
270 fprintf(of
, " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
271 time_to_graph(d
- graph_start
),
272 -5.0, d
- graph_start
);
278 /* xml comments must not contain "--" */
279 static char* xml_comment_encode(const char* name
) {
282 enc_name
= strdup(name
);
286 for (p
= enc_name
; *p
; p
++)
287 if (p
[0] == '-' && p
[1] == '-')
293 static void svg_pss_graph(FILE *of
,
294 struct list_sample_data
*head
,
295 struct ps_struct
*ps_first
,
296 double graph_start
) {
297 struct ps_struct
*ps
;
299 struct list_sample_data
*sampledata_last
;
301 sampledata_last
= head
;
302 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
303 sampledata_last
= sampledata
;
307 fprintf(of
, "\n\n<!-- Pss memory size graph -->\n");
309 fprintf(of
, "\n <text class=\"t2\" x=\"5\" y=\"-15\">Memory allocation - Pss</text>\n");
311 /* vsize 1000 == 1000mb */
312 svg_graph_box(of
, head
, 100, graph_start
);
313 /* draw some hlines for usable memory sizes */
314 for (i
= 100000; i
< 1000000; i
+= 100000) {
315 fprintf(of
, " <line class=\"sec01\" x1=\"%.03f\" y1=\"%.0f\" x2=\"%.03f\" y2=\"%.0f\"/>\n",
318 time_to_graph(sampledata_last
->sampletime
- graph_start
),
320 fprintf(of
, " <text class=\"sec\" x=\"%.03f\" y=\"%.0f\">%dM</text>\n",
321 time_to_graph(sampledata_last
->sampletime
- graph_start
) + 5,
322 kb_to_graph(i
), (1000000 - i
) / 1000);
326 /* now plot the graph itself */
328 prev_sampledata
= head
;
329 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
332 struct ps_sched_struct
*cross_place
;
337 /* put all the small pss blocks into the bottom */
339 while (ps
->next_ps
) {
343 ps
->sample
= ps
->first
;
344 while (ps
->sample
->next
) {
345 ps
->sample
= ps
->sample
->next
;
346 if (ps
->sample
->sampledata
== sampledata
)
349 if (ps
->sample
->sampledata
== sampledata
) {
350 if (ps
->sample
->pss
<= (100 * arg_scale_y
))
351 top
+= ps
->sample
->pss
;
355 while (ps
->sample
->cross
) {
356 cross_place
= ps
->sample
->cross
;
357 ps
= ps
->sample
->cross
->ps_new
;
358 ps
->sample
= cross_place
;
359 if (ps
->sample
->pss
<= (100 * arg_scale_y
))
360 top
+= ps
->sample
->pss
;
363 fprintf(of
, " <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
365 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
366 kb_to_graph(1000000.0 - top
),
367 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
368 kb_to_graph(top
- bottom
));
371 /* now plot the ones that are of significant size */
373 while (ps
->next_ps
) {
377 ps
->sample
= ps
->first
;
378 while (ps
->sample
->next
) {
379 ps
->sample
= ps
->sample
->next
;
380 if (ps
->sample
->sampledata
== sampledata
)
383 /* don't draw anything smaller than 2mb */
384 if (ps
->sample
->sampledata
!= sampledata
)
386 if (ps
->sample
->pss
> (100 * arg_scale_y
)) {
387 top
= bottom
+ ps
->sample
->pss
;
388 fprintf(of
, " <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
389 colorwheel
[ps
->pid
% 12],
390 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
391 kb_to_graph(1000000.0 - top
),
392 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
393 kb_to_graph(top
- bottom
));
399 while ((cross_place
= ps
->sample
->cross
)) {
400 ps
= ps
->sample
->cross
->ps_new
;
401 ps
->sample
= cross_place
;
402 if (ps
->sample
->pss
> (100 * arg_scale_y
)) {
403 top
= bottom
+ ps
->sample
->pss
;
404 fprintf(of
, " <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
405 colorwheel
[ps
->pid
% 12],
406 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
407 kb_to_graph(1000000.0 - top
),
408 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
409 kb_to_graph(top
- bottom
));
414 prev_sampledata
= sampledata
;
418 /* overlay all the text labels */
420 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
423 struct ps_sched_struct
*prev_sample
;
424 struct ps_sched_struct
*cross_place
;
426 /* put all the small pss blocks into the bottom */
427 ps
= ps_first
->next_ps
;
428 while (ps
->next_ps
) {
433 ps
->sample
= ps
->first
;
434 while (ps
->sample
->next
) {
435 ps
->sample
= ps
->sample
->next
;
436 if (ps
->sample
->sampledata
== sampledata
)
440 if (ps
->sample
->sampledata
== sampledata
) {
441 if (ps
->sample
->pss
<= (100 * arg_scale_y
))
442 top
+= ps
->sample
->pss
;
448 while ((cross_place
= ps
->sample
->cross
)) {
449 ps
= ps
->sample
->cross
->ps_new
;
450 ps
->sample
= cross_place
;
451 if (ps
->sample
->pss
<= (100 * arg_scale_y
))
452 top
+= ps
->sample
->pss
;
456 /* now plot the ones that are of significant size */
458 while (ps
->next_ps
) {
459 prev_sample
= ps
->sample
;
463 ps
->sample
= ps
->first
;
464 while (ps
->sample
->next
) {
465 prev_sample
= ps
->sample
;
466 ps
->sample
= ps
->sample
->next
;
467 if (ps
->sample
->sampledata
== sampledata
)
470 /* don't draw anything smaller than 2mb */
471 if (ps
->sample
->sampledata
== sampledata
) {
472 if (ps
->sample
->pss
> (100 * arg_scale_y
)) {
473 top
= bottom
+ ps
->sample
->pss
;
474 /* draw a label with the process / PID */
475 if ((i
== 1) || (prev_sample
->pss
<= (100 * arg_scale_y
)))
476 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n",
477 time_to_graph(sampledata
->sampletime
- graph_start
),
478 kb_to_graph(1000000.0 - bottom
- ((top
- bottom
) / 2)),
485 while ((cross_place
= ps
->sample
->cross
)) {
486 ps
= ps
->sample
->cross
->ps_new
;
487 ps
->sample
= cross_place
;
488 prev_sample
= ps
->sample
->prev
;
489 if (ps
->sample
->pss
> (100 * arg_scale_y
)) {
490 top
= bottom
+ ps
->sample
->pss
;
491 /* draw a label with the process / PID */
492 if ((i
== 1) || (prev_sample
->pss
<= (100 * arg_scale_y
)))
493 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n",
494 time_to_graph(sampledata
->sampletime
- graph_start
),
495 kb_to_graph(1000000.0 - bottom
- ((top
- bottom
) / 2)),
504 /* debug output - full data dump */
505 fprintf(of
, "\n\n<!-- PSS map - csv format -->\n");
507 while (ps
->next_ps
) {
508 _cleanup_free_
char *enc_name
= NULL
;
513 enc_name
= xml_comment_encode(ps
->name
);
517 fprintf(of
, "<!-- %s [%d] pss=", enc_name
, ps
->pid
);
519 ps
->sample
= ps
->first
;
520 while (ps
->sample
->next
) {
521 ps
->sample
= ps
->sample
->next
;
522 fprintf(of
, "%d," , ps
->sample
->pss
);
525 fprintf(of
, " -->\n");
530 static void svg_io_bi_bar(FILE *of
,
531 struct list_sample_data
*head
,
541 struct list_sample_data
*start_sampledata
;
542 struct list_sample_data
*stop_sampledata
;
544 fprintf(of
, "<!-- IO utilization graph - In -->\n");
545 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - read</text>\n");
548 * calculate rounding range
550 * We need to round IO data since IO block data is not updated on
551 * each poll. Applying a smoothing function loses some burst data,
552 * so keep the smoothing range short.
554 range
= 0.25 / (1.0 / arg_hz
);
556 range
= 2.0; /* no smoothing */
558 /* surrounding box */
559 svg_graph_box(of
, head
, 5, graph_start
);
561 /* find the max IO first */
563 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
569 start
= MAX(i
- ((range
/ 2) - 1), 0);
570 stop
= MIN(i
+ (range
/ 2), n_samples
- 1);
571 diff
= (stop
- start
);
573 start_sampledata
= sampledata
;
574 stop_sampledata
= sampledata
;
576 for (k
= 0; k
< ((range
/2) - 1) && start_sampledata
->link_next
; k
++)
577 start_sampledata
= start_sampledata
->link_next
;
579 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
580 stop_sampledata
= stop_sampledata
->link_prev
;
582 tot
= (double)(stop_sampledata
->blockstat
.bi
- start_sampledata
->blockstat
.bi
) / diff
;
589 tot
= (double)(stop_sampledata
->blockstat
.bo
- start_sampledata
->blockstat
.bo
) / diff
;
599 prev_sampledata
= head
;
600 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
607 start
= MAX(i
- ((range
/ 2) - 1), 0);
608 stop
= MIN(i
+ (range
/ 2), n_samples
);
609 diff
= (stop
- start
);
611 start_sampledata
= sampledata
;
612 stop_sampledata
= sampledata
;
614 for (k
= 0; k
< ((range
/2)-1) && start_sampledata
->link_next
; k
++)
615 start_sampledata
= start_sampledata
->link_next
;
617 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
618 stop_sampledata
= stop_sampledata
->link_prev
;
620 tot
= (double)(stop_sampledata
->blockstat
.bi
- start_sampledata
->blockstat
.bi
) / diff
;
626 fprintf(of
, "<rect class=\"bi\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
627 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
628 (arg_scale_y
* 5) - (pbi
* (arg_scale_y
* 5)),
629 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
630 pbi
* (arg_scale_y
* 5));
632 /* labels around highest value */
634 fprintf(of
, " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
635 time_to_graph(sampledata
->sampletime
- graph_start
) + 5,
636 ((arg_scale_y
* 5) - (pbi
* (arg_scale_y
* 5))) + 15,
637 max
/ 1024.0 / (interval
/ 1000000000.0));
640 prev_sampledata
= sampledata
;
644 static void svg_io_bo_bar(FILE *of
,
645 struct list_sample_data
*head
,
654 struct list_sample_data
*start_sampledata
;
655 struct list_sample_data
*stop_sampledata
;
657 fprintf(of
, "<!-- IO utilization graph - out -->\n");
658 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - write</text>\n");
661 * calculate rounding range
663 * We need to round IO data since IO block data is not updated on
664 * each poll. Applying a smoothing function loses some burst data,
665 * so keep the smoothing range short.
667 range
= 0.25 / (1.0 / arg_hz
);
669 range
= 2.0; /* no smoothing */
671 /* surrounding box */
672 svg_graph_box(of
, head
, 5, graph_start
);
674 /* find the max IO first */
676 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
682 start
= MAX(i
- ((range
/ 2) - 1), 0);
683 stop
= MIN(i
+ (range
/ 2), n_samples
- 1);
684 diff
= (stop
- start
);
686 start_sampledata
= sampledata
;
687 stop_sampledata
= sampledata
;
689 for (k
= 0; k
< (range
/2) - 1 && start_sampledata
->link_next
; k
++)
690 start_sampledata
= start_sampledata
->link_next
;
692 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
693 stop_sampledata
= stop_sampledata
->link_prev
;
695 tot
= (double)(stop_sampledata
->blockstat
.bi
- start_sampledata
->blockstat
.bi
) / diff
;
699 tot
= (double)(stop_sampledata
->blockstat
.bo
- start_sampledata
->blockstat
.bo
) / diff
;
709 prev_sampledata
= head
;
712 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
713 int start
, stop
, diff
;
718 start
= MAX(i
- ((range
/ 2) - 1), 0);
719 stop
= MIN(i
+ (range
/ 2), n_samples
);
720 diff
= (stop
- start
);
722 start_sampledata
= sampledata
;
723 stop_sampledata
= sampledata
;
725 for (k
= 0; k
< ((range
/2)-1) && start_sampledata
->link_next
; k
++)
726 start_sampledata
= start_sampledata
->link_next
;
728 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
729 stop_sampledata
= stop_sampledata
->link_prev
;
731 tot
= (double)(stop_sampledata
->blockstat
.bo
- start_sampledata
->blockstat
.bo
)
738 fprintf(of
, "<rect class=\"bo\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
739 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
740 (arg_scale_y
* 5) - (pbo
* (arg_scale_y
* 5)),
741 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
742 pbo
* (arg_scale_y
* 5));
744 /* labels around highest bo value */
746 fprintf(of
, " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
747 time_to_graph(sampledata
->sampletime
- graph_start
) + 5,
748 ((arg_scale_y
* 5) - (pbo
* (arg_scale_y
* 5))),
749 max
/ 1024.0 / (interval
/ 1000000000.0));
752 prev_sampledata
= sampledata
;
756 static void svg_cpu_bar(FILE *of
, struct list_sample_data
*head
, int n_cpus
, int cpu_num
, double graph_start
) {
758 fprintf(of
, "<!-- CPU utilization graph -->\n");
761 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] utilization</text>\n");
763 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] utilization</text>\n", cpu_num
);
765 /* surrounding box */
766 svg_graph_box(of
, head
, 5, graph_start
);
768 /* bars for each sample, proportional to the CPU util. */
769 prev_sampledata
= head
;
770 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
778 for (c
= 0; c
< n_cpus
; c
++)
779 trt
+= sampledata
->runtime
[c
] - prev_sampledata
->runtime
[c
];
781 trt
= sampledata
->runtime
[cpu_num
] - prev_sampledata
->runtime
[cpu_num
];
783 trt
= trt
/ 1000000000.0;
786 trt
= trt
/ (double)n_cpus
;
789 ptrt
= trt
/ (sampledata
->sampletime
- prev_sampledata
->sampletime
);
795 fprintf(of
, "<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
796 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
797 (arg_scale_y
* 5) - (ptrt
* (arg_scale_y
* 5)),
798 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
799 ptrt
* (arg_scale_y
* 5));
801 prev_sampledata
= sampledata
;
805 static void svg_wait_bar(FILE *of
, struct list_sample_data
*head
, int n_cpus
, int cpu_num
, double graph_start
) {
807 fprintf(of
, "<!-- Wait time aggregation box -->\n");
810 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] wait</text>\n");
812 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] wait</text>\n", cpu_num
);
814 /* surrounding box */
815 svg_graph_box(of
, head
, 5, graph_start
);
817 /* bars for each sample, proportional to the CPU util. */
818 prev_sampledata
= head
;
819 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
827 for (c
= 0; c
< n_cpus
; c
++)
828 twt
+= sampledata
->waittime
[c
] - prev_sampledata
->waittime
[c
];
830 twt
= sampledata
->waittime
[cpu_num
] - prev_sampledata
->waittime
[cpu_num
];
832 twt
= twt
/ 1000000000.0;
835 twt
= twt
/ (double)n_cpus
;
838 ptwt
= twt
/ (sampledata
->sampletime
- prev_sampledata
->sampletime
);
844 fprintf(of
, "<rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
845 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
846 ((arg_scale_y
* 5) - (ptwt
* (arg_scale_y
* 5))),
847 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
848 ptwt
* (arg_scale_y
* 5));
850 prev_sampledata
= sampledata
;
854 static void svg_entropy_bar(FILE *of
, struct list_sample_data
*head
, double graph_start
) {
856 fprintf(of
, "<!-- entropy pool graph -->\n");
858 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">Entropy pool size</text>\n");
859 /* surrounding box */
860 svg_graph_box(of
, head
, 5, graph_start
);
862 /* bars for each sample, scale 0-4096 */
863 prev_sampledata
= head
;
864 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
865 fprintf(of
, "<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
866 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
867 ((arg_scale_y
* 5) - ((sampledata
->entropy_avail
/ 4096.) * (arg_scale_y
* 5))),
868 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
869 (sampledata
->entropy_avail
/ 4096.) * (arg_scale_y
* 5));
870 prev_sampledata
= sampledata
;
874 static struct ps_struct
*get_next_ps(struct ps_struct
*ps
, struct ps_struct
*ps_first
) {
876 * walk the list of processes and return the next one to be
890 /* go back for parent siblings */
892 if (ps
->parent
&& ps
->parent
->next
)
893 return ps
->parent
->next
;
903 static bool ps_filter(struct ps_struct
*ps
) {
907 /* can't draw data when there is only 1 sample (need start + stop) */
908 if (ps
->first
== ps
->last
)
911 /* don't filter kthreadd */
915 /* drop stuff that doesn't use any real CPU time */
916 if (ps
->total
<= 0.001)
922 static void svg_do_initcall(FILE *of
, struct list_sample_data
*head
, int count_only
, double graph_start
) {
923 _cleanup_pclose_
FILE *f
= NULL
;
929 /* can't plot initcall when disabled or in relative mode */
930 if (!arg_initcall
|| arg_relative
) {
936 fprintf(of
, "<!-- initcall -->\n");
937 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">Kernel init threads</text>\n");
938 /* surrounding box */
939 svg_graph_box(of
, head
, kcount
, graph_start
);
945 * Initcall graphing - parses dmesg buffer and displays kernel threads
946 * This somewhat uses the same methods and scaling to show processes
947 * but looks a lot simpler. It's overlaid entirely onto the PS graph
951 f
= popen("dmesg", "r");
960 if (fgets(l
, sizeof(l
) - 1, f
) == NULL
)
963 c
= sscanf(l
, "[%lf] initcall %s %*s %d %*s %d %*s",
964 &t
, func
, &ret
, &usecs
);
966 /* also parse initcalls done by module loading */
967 c
= sscanf(l
, "[%lf] initcall %s %*s %*s %d %*s %d %*s",
968 &t
, func
, &ret
, &usecs
);
973 /* chop the +0xXX/0xXX stuff */
974 while(func
[z
] != '+')
979 /* filter out irrelevant stuff */
985 fprintf(of
, "<!-- thread=\"%s\" time=\"%.3f\" elapsed=\"%d\" result=\"%d\" -->\n",
986 func
, t
, usecs
, ret
);
992 fprintf(of
, " <rect class=\"krnl\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
993 time_to_graph(t
- (usecs
/ 1000000.0)),
995 time_to_graph(usecs
/ 1000000.0),
999 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\">%s <tspan class=\"run\">%.03fs</tspan></text>\n",
1000 time_to_graph(t
- (usecs
/ 1000000.0)) + 5,
1001 ps_to_graph(kcount
) + 15,
1002 func
, usecs
/ 1000000.0);
1008 static void svg_ps_bars(FILE *of
,
1009 struct list_sample_data
*head
,
1012 struct ps_struct
*ps_first
,
1016 struct ps_struct
*ps
;
1022 fprintf(of
, "<!-- Process graph -->\n");
1023 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">Processes</text>\n");
1025 /* surrounding box */
1026 svg_graph_box(of
, head
, pcount
, graph_start
);
1028 /* pass 2 - ps boxes */
1030 while ((ps
= get_next_ps(ps
, ps_first
))) {
1031 _cleanup_free_
char *enc_name
= NULL
, *escaped
= NULL
;
1036 if (!utf8_is_printable(ps
->name
, strlen(ps
->name
)))
1037 escaped
= utf8_escape_non_printable(ps
->name
);
1039 enc_name
= xml_comment_encode(escaped
? escaped
: ps
->name
);
1043 /* leave some trace of what we actually filtered etc. */
1044 fprintf(of
, "<!-- %s [%i] ppid=%i runtime=%.03fs -->\n", enc_name
, ps
->pid
,
1045 ps
->ppid
, ps
->total
);
1047 starttime
= ps
->first
->sampledata
->sampletime
;
1049 if (!ps_filter(ps
)) {
1050 /* remember where _to_ our children need to draw a line */
1051 ps
->pos_x
= time_to_graph(starttime
- graph_start
);
1052 ps
->pos_y
= ps_to_graph(j
+1); /* bottom left corner */
1053 } else if (ps
->parent
){
1054 /* hook children to our parent coords instead */
1055 ps
->pos_x
= ps
->parent
->pos_x
;
1056 ps
->pos_y
= ps
->parent
->pos_y
;
1058 /* if this is the last child, we might still need to draw a connecting line */
1059 if ((!ps
->next
) && (ps
->parent
))
1060 fprintf(of
, " <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1062 ps_to_graph(j
-1) + 10.0, /* whee, use the last value here */
1068 endtime
= ps
->last
->sampledata
->sampletime
;
1069 fprintf(of
, " <rect class=\"ps\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1070 time_to_graph(starttime
- graph_start
),
1072 time_to_graph(ps
->last
->sampledata
->sampletime
- starttime
),
1075 /* paint cpu load over these */
1076 ps
->sample
= ps
->first
;
1078 while (ps
->sample
->next
) {
1081 struct ps_sched_struct
*prev
;
1084 ps
->sample
= ps
->sample
->next
;
1086 /* calculate over interval */
1087 rt
= ps
->sample
->runtime
- prev
->runtime
;
1088 wt
= ps
->sample
->waittime
- prev
->waittime
;
1090 prt
= (rt
/ 1000000000) / (ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
);
1091 wrt
= (wt
/ 1000000000) / (ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
);
1093 /* this can happen if timekeeping isn't accurate enough */
1099 if ((prt
< 0.1) && (wrt
< 0.1)) /* =~ 26 (color threshold) */
1102 fprintf(of
, " <rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1103 time_to_graph(prev
->sampledata
->sampletime
- graph_start
),
1105 time_to_graph(ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
),
1108 /* draw cpu over wait - TODO figure out how/why run + wait > interval */
1109 fprintf(of
, " <rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1110 time_to_graph(prev
->sampledata
->sampletime
- graph_start
),
1111 ps_to_graph(j
+ (1.0 - prt
)),
1112 time_to_graph(ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
),
1117 /* determine where to display the process name */
1118 if ((endtime
- starttime
) < 1.5)
1119 /* too small to fit label inside the box */
1124 /* text label of process name */
1125 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]<tspan class=\"run\">%.03fs</tspan> %s</text>\n",
1126 time_to_graph(w
- graph_start
) + 5.0,
1127 ps_to_graph(j
) + 14.0,
1128 escaped
? escaped
: ps
->name
,
1130 (ps
->last
->runtime
- ps
->first
->runtime
) / 1000000000.0,
1131 arg_show_cgroup
? ps
->cgroup
: "");
1132 /* paint lines to the parent process */
1134 /* horizontal part */
1135 fprintf(of
, " <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1136 time_to_graph(starttime
- graph_start
),
1137 ps_to_graph(j
) + 10.0,
1139 ps_to_graph(j
) + 10.0);
1141 /* one vertical line connecting all the horizontal ones up */
1143 fprintf(of
, " <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1145 ps_to_graph(j
) + 10.0,
1150 j
++; /* count boxes */
1155 /* last pass - determine when idle */
1157 /* make sure we start counting from the point where we actually have
1158 * data: assume that bootchart's first sample is when data started
1162 while (ps
->next_ps
) {
1168 /* need to know last node first */
1169 ps
->sample
= ps
->first
;
1170 i
= ps
->sample
->next
->sampledata
->counter
;
1172 while (ps
->sample
->next
&& i
<(n_samples
-(arg_hz
/2))) {
1177 struct ps_sched_struct
*sample_hz
;
1179 ps
->sample
= ps
->sample
->next
;
1180 sample_hz
= ps
->sample
;
1181 for (ii
= 0; (ii
< (int)arg_hz
/2) && sample_hz
->next
; ii
++)
1182 sample_hz
= sample_hz
->next
;
1184 /* subtract bootchart cpu utilization from total */
1186 for (c
= 0; c
< n_cpus
; c
++)
1187 crt
+= sample_hz
->sampledata
->runtime
[c
] - ps
->sample
->sampledata
->runtime
[c
];
1189 brt
= sample_hz
->runtime
- ps
->sample
->runtime
;
1191 * our definition of "idle":
1193 * if for (hz / 2) we've used less CPU than (interval / 2) ...
1194 * defaults to 4.0%, which experimentally, is where atom idles
1196 if ((crt
- brt
) < (interval
/ 2.0)) {
1197 idletime
= ps
->sample
->sampledata
->sampletime
- graph_start
;
1198 fprintf(of
, "\n<!-- idle detected at %.03f seconds -->\n", idletime
);
1199 fprintf(of
, "<line class=\"idle\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1200 time_to_graph(idletime
),
1202 time_to_graph(idletime
),
1203 ps_to_graph(pcount
) + arg_scale_y
);
1204 fprintf(of
, "<text class=\"idle\" x=\"%.03f\" y=\"%.03f\">%.01fs</text>\n",
1205 time_to_graph(idletime
) + 5.0,
1206 ps_to_graph(pcount
) + arg_scale_y
,
1215 static void svg_top_ten_cpu(FILE *of
, struct ps_struct
*ps_first
) {
1216 struct ps_struct
*top
[10];
1217 struct ps_struct emptyps
= {};
1218 struct ps_struct
*ps
;
1221 for (n
= 0; n
< (int) ELEMENTSOF(top
); n
++)
1224 /* walk all ps's and setup ptrs */
1226 while ((ps
= get_next_ps(ps
, ps_first
))) {
1227 for (n
= 0; n
< 10; n
++) {
1228 if (ps
->total
<= top
[n
]->total
)
1230 /* cascade insert */
1231 for (m
= 9; m
> n
; m
--)
1238 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"0\">Top CPU consumers:</text>\n");
1239 for (n
= 0; n
< 10; n
++)
1240 fprintf(of
, "<text class=\"t3\" x=\"20\" y=\"%d\">%3.03fs - <![CDATA[%s]]> [%d]</text>\n",
1247 static void svg_top_ten_pss(FILE *of
, struct ps_struct
*ps_first
) {
1248 struct ps_struct
*top
[10];
1249 struct ps_struct emptyps
= {};
1250 struct ps_struct
*ps
;
1253 for (n
= 0; n
< (int) ELEMENTSOF(top
); n
++)
1256 /* walk all ps's and setup ptrs */
1258 while ((ps
= get_next_ps(ps
, ps_first
))) {
1259 for (n
= 0; n
< 10; n
++) {
1260 if (ps
->pss_max
<= top
[n
]->pss_max
)
1263 /* cascade insert */
1264 for (m
= 9; m
> n
; m
--)
1271 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"0\">Top PSS consumers:</text>\n");
1272 for (n
= 0; n
< 10; n
++)
1273 fprintf(of
, "<text class=\"t3\" x=\"20\" y=\"%d\">%dK - <![CDATA[%s]]> [%d]</text>\n",
1280 int svg_do(FILE *of
,
1282 struct list_sample_data
*head
,
1283 struct ps_struct
*ps_first
,
1292 struct ps_struct
*ps
;
1297 LIST_FIND_TAIL(link
, sampledata
, head
);
1300 /* count initcall thread count first */
1301 svg_do_initcall(of
, head
, 1, graph_start
);
1302 ksize
= kcount
? ps_to_graph(kcount
) + (arg_scale_y
* 2) : 0;
1304 /* then count processes */
1305 while ((ps
= get_next_ps(ps
, ps_first
))) {
1311 psize
= ps_to_graph(pcount
) + (arg_scale_y
* 2);
1313 esize
= (arg_entropy
? arg_scale_y
* 7 : 0);
1315 /* after this, we can draw the header with proper sizing */
1316 svg_header(of
, head
, graph_start
, arg_percpu
? n_cpus
: 0);
1317 fprintf(of
, "<rect class=\"bg\" width=\"100%%\" height=\"100%%\" />\n\n");
1319 fprintf(of
, "<g transform=\"translate(10,400)\">\n");
1320 svg_io_bi_bar(of
, head
, n_samples
, graph_start
, interval
);
1321 fprintf(of
, "</g>\n\n");
1323 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1324 svg_io_bo_bar(of
, head
, n_samples
, graph_start
, interval
);
1325 fprintf(of
, "</g>\n\n");
1327 for (c
= -1; c
< (arg_percpu
? n_cpus
: 0); c
++) {
1329 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1330 svg_cpu_bar(of
, head
, n_cpus
, c
, graph_start
);
1331 fprintf(of
, "</g>\n\n");
1334 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1335 svg_wait_bar(of
, head
, n_cpus
, c
, graph_start
);
1336 fprintf(of
, "</g>\n\n");
1341 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1342 svg_do_initcall(of
, head
, 0, graph_start
);
1343 fprintf(of
, "</g>\n\n");
1347 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
) + ksize
);
1348 svg_ps_bars(of
, head
, n_samples
, n_cpus
, ps_first
, graph_start
, interval
);
1349 fprintf(of
, "</g>\n\n");
1351 fprintf(of
, "<g transform=\"translate(10, 0)\">\n");
1352 r
= svg_title(of
, build
, pscount
, log_start
, overrun
);
1353 fprintf(of
, "</g>\n\n");
1358 fprintf(of
, "<g transform=\"translate(10,200)\">\n");
1359 svg_top_ten_cpu(of
, ps_first
);
1360 fprintf(of
, "</g>\n\n");
1363 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
) + ksize
+ psize
);
1364 svg_entropy_bar(of
, head
, graph_start
);
1365 fprintf(of
, "</g>\n\n");
1369 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
) + ksize
+ psize
+ esize
);
1370 svg_pss_graph(of
, head
, ps_first
, graph_start
);
1371 fprintf(of
, "</g>\n\n");
1373 fprintf(of
, "<g transform=\"translate(410,200)\">\n");
1374 svg_top_ten_pss(of
, ps_first
);
1375 fprintf(of
, "</g>\n\n");
1378 /* fprintf footer */
1379 fprintf(of
, "\n</svg>\n");