]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/svg.c
a7ef653d5d0d78d2c9d2920e3419eb14c367db82
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));
641 prev_sampledata
= sampledata
;
645 static void svg_io_bo_bar(FILE *of
,
646 struct list_sample_data
*head
,
655 struct list_sample_data
*start_sampledata
;
656 struct list_sample_data
*stop_sampledata
;
658 fprintf(of
, "<!-- IO utilization graph - out -->\n");
659 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - write</text>\n");
662 * calculate rounding range
664 * We need to round IO data since IO block data is not updated on
665 * each poll. Applying a smoothing function loses some burst data,
666 * so keep the smoothing range short.
668 range
= 0.25 / (1.0 / arg_hz
);
670 range
= 2.0; /* no smoothing */
672 /* surrounding box */
673 svg_graph_box(of
, head
, 5, graph_start
);
675 /* find the max IO first */
677 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
683 start
= MAX(i
- ((range
/ 2) - 1), 0);
684 stop
= MIN(i
+ (range
/ 2), n_samples
- 1);
685 diff
= (stop
- start
);
687 start_sampledata
= sampledata
;
688 stop_sampledata
= sampledata
;
690 for (k
= 0; k
< (range
/2) - 1 && start_sampledata
->link_next
; k
++)
691 start_sampledata
= start_sampledata
->link_next
;
693 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
694 stop_sampledata
= stop_sampledata
->link_prev
;
696 tot
= (double)(stop_sampledata
->blockstat
.bi
- start_sampledata
->blockstat
.bi
) / diff
;
700 tot
= (double)(stop_sampledata
->blockstat
.bo
- start_sampledata
->blockstat
.bo
) / diff
;
710 prev_sampledata
= head
;
713 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
714 int start
, stop
, diff
;
719 start
= MAX(i
- ((range
/ 2) - 1), 0);
720 stop
= MIN(i
+ (range
/ 2), n_samples
);
721 diff
= (stop
- start
);
723 start_sampledata
= sampledata
;
724 stop_sampledata
= sampledata
;
726 for (k
= 0; k
< ((range
/2)-1) && start_sampledata
->link_next
; k
++)
727 start_sampledata
= start_sampledata
->link_next
;
729 for (k
= 0; k
< (range
/2) && stop_sampledata
->link_prev
; k
++)
730 stop_sampledata
= stop_sampledata
->link_prev
;
732 tot
= (double)(stop_sampledata
->blockstat
.bo
- start_sampledata
->blockstat
.bo
)
739 fprintf(of
, "<rect class=\"bo\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
740 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
741 (arg_scale_y
* 5) - (pbo
* (arg_scale_y
* 5)),
742 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
743 pbo
* (arg_scale_y
* 5));
745 /* labels around highest bo value */
747 fprintf(of
, " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n",
748 time_to_graph(sampledata
->sampletime
- graph_start
) + 5,
749 ((arg_scale_y
* 5) - (pbo
* (arg_scale_y
* 5))),
750 max
/ 1024.0 / (interval
/ 1000000000.0));
754 prev_sampledata
= sampledata
;
758 static void svg_cpu_bar(FILE *of
, struct list_sample_data
*head
, int n_cpus
, int cpu_num
, double graph_start
) {
760 fprintf(of
, "<!-- CPU utilization graph -->\n");
763 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] utilization</text>\n");
765 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] utilization</text>\n", cpu_num
);
767 /* surrounding box */
768 svg_graph_box(of
, head
, 5, graph_start
);
770 /* bars for each sample, proportional to the CPU util. */
771 prev_sampledata
= head
;
772 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
780 for (c
= 0; c
< n_cpus
; c
++)
781 trt
+= sampledata
->runtime
[c
] - prev_sampledata
->runtime
[c
];
783 trt
= sampledata
->runtime
[cpu_num
] - prev_sampledata
->runtime
[cpu_num
];
785 trt
= trt
/ 1000000000.0;
788 trt
= trt
/ (double)n_cpus
;
791 ptrt
= trt
/ (sampledata
->sampletime
- prev_sampledata
->sampletime
);
797 fprintf(of
, "<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
798 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
799 (arg_scale_y
* 5) - (ptrt
* (arg_scale_y
* 5)),
800 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
801 ptrt
* (arg_scale_y
* 5));
803 prev_sampledata
= sampledata
;
807 static void svg_wait_bar(FILE *of
, struct list_sample_data
*head
, int n_cpus
, int cpu_num
, double graph_start
) {
809 fprintf(of
, "<!-- Wait time aggregation box -->\n");
812 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] wait</text>\n");
814 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] wait</text>\n", cpu_num
);
816 /* surrounding box */
817 svg_graph_box(of
, head
, 5, graph_start
);
819 /* bars for each sample, proportional to the CPU util. */
820 prev_sampledata
= head
;
821 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
829 for (c
= 0; c
< n_cpus
; c
++)
830 twt
+= sampledata
->waittime
[c
] - prev_sampledata
->waittime
[c
];
832 twt
= sampledata
->waittime
[cpu_num
] - prev_sampledata
->waittime
[cpu_num
];
834 twt
= twt
/ 1000000000.0;
837 twt
= twt
/ (double)n_cpus
;
840 ptwt
= twt
/ (sampledata
->sampletime
- prev_sampledata
->sampletime
);
846 fprintf(of
, "<rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
847 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
848 ((arg_scale_y
* 5) - (ptwt
* (arg_scale_y
* 5))),
849 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
850 ptwt
* (arg_scale_y
* 5));
852 prev_sampledata
= sampledata
;
856 static void svg_entropy_bar(FILE *of
, struct list_sample_data
*head
, double graph_start
) {
858 fprintf(of
, "<!-- entropy pool graph -->\n");
860 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">Entropy pool size</text>\n");
861 /* surrounding box */
862 svg_graph_box(of
, head
, 5, graph_start
);
864 /* bars for each sample, scale 0-4096 */
865 prev_sampledata
= head
;
866 LIST_FOREACH_BEFORE(link
, sampledata
, head
) {
867 fprintf(of
, "<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
868 time_to_graph(prev_sampledata
->sampletime
- graph_start
),
869 ((arg_scale_y
* 5) - ((sampledata
->entropy_avail
/ 4096.) * (arg_scale_y
* 5))),
870 time_to_graph(sampledata
->sampletime
- prev_sampledata
->sampletime
),
871 (sampledata
->entropy_avail
/ 4096.) * (arg_scale_y
* 5));
872 prev_sampledata
= sampledata
;
876 static struct ps_struct
*get_next_ps(struct ps_struct
*ps
, struct ps_struct
*ps_first
) {
878 * walk the list of processes and return the next one to be
892 /* go back for parent siblings */
894 if (ps
->parent
&& ps
->parent
->next
)
895 return ps
->parent
->next
;
905 static bool ps_filter(struct ps_struct
*ps
) {
909 /* can't draw data when there is only 1 sample (need start + stop) */
910 if (ps
->first
== ps
->last
)
913 /* don't filter kthreadd */
917 /* drop stuff that doesn't use any real CPU time */
918 if (ps
->total
<= 0.001)
924 static void svg_do_initcall(FILE *of
, struct list_sample_data
*head
, int count_only
, double graph_start
) {
925 _cleanup_pclose_
FILE *f
= NULL
;
931 /* can't plot initcall when disabled or in relative mode */
932 if (!arg_initcall
|| arg_relative
) {
938 fprintf(of
, "<!-- initcall -->\n");
939 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">Kernel init threads</text>\n");
940 /* surrounding box */
941 svg_graph_box(of
, head
, kcount
, graph_start
);
947 * Initcall graphing - parses dmesg buffer and displays kernel threads
948 * This somewhat uses the same methods and scaling to show processes
949 * but looks a lot simpler. It's overlaid entirely onto the PS graph
953 f
= popen("dmesg", "r");
962 if (fgets(l
, sizeof(l
) - 1, f
) == NULL
)
965 c
= sscanf(l
, "[%lf] initcall %s %*s %d %*s %d %*s",
966 &t
, func
, &ret
, &usecs
);
968 /* also parse initcalls done by module loading */
969 c
= sscanf(l
, "[%lf] initcall %s %*s %*s %d %*s %d %*s",
970 &t
, func
, &ret
, &usecs
);
975 /* chop the +0xXX/0xXX stuff */
976 while(func
[z
] != '+')
981 /* filter out irrelevant stuff */
987 fprintf(of
, "<!-- thread=\"%s\" time=\"%.3f\" elapsed=\"%d\" result=\"%d\" -->\n",
988 func
, t
, usecs
, ret
);
994 fprintf(of
, " <rect class=\"krnl\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
995 time_to_graph(t
- (usecs
/ 1000000.0)),
997 time_to_graph(usecs
/ 1000000.0),
1001 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\">%s <tspan class=\"run\">%.03fs</tspan></text>\n",
1002 time_to_graph(t
- (usecs
/ 1000000.0)) + 5,
1003 ps_to_graph(kcount
) + 15,
1004 func
, usecs
/ 1000000.0);
1010 static void svg_ps_bars(FILE *of
,
1011 struct list_sample_data
*head
,
1014 struct ps_struct
*ps_first
,
1018 struct ps_struct
*ps
;
1024 fprintf(of
, "<!-- Process graph -->\n");
1025 fprintf(of
, "<text class=\"t2\" x=\"5\" y=\"-15\">Processes</text>\n");
1027 /* surrounding box */
1028 svg_graph_box(of
, head
, pcount
, graph_start
);
1030 /* pass 2 - ps boxes */
1032 while ((ps
= get_next_ps(ps
, ps_first
))) {
1033 _cleanup_free_
char *enc_name
= NULL
, *escaped
= NULL
;
1038 if (!utf8_is_printable(ps
->name
, strlen(ps
->name
)))
1039 escaped
= utf8_escape_non_printable(ps
->name
);
1041 enc_name
= xml_comment_encode(escaped
? escaped
: ps
->name
);
1045 /* leave some trace of what we actually filtered etc. */
1046 fprintf(of
, "<!-- %s [%i] ppid=%i runtime=%.03fs -->\n", enc_name
, ps
->pid
,
1047 ps
->ppid
, ps
->total
);
1049 starttime
= ps
->first
->sampledata
->sampletime
;
1051 if (!ps_filter(ps
)) {
1052 /* remember where _to_ our children need to draw a line */
1053 ps
->pos_x
= time_to_graph(starttime
- graph_start
);
1054 ps
->pos_y
= ps_to_graph(j
+1); /* bottom left corner */
1055 } else if (ps
->parent
){
1056 /* hook children to our parent coords instead */
1057 ps
->pos_x
= ps
->parent
->pos_x
;
1058 ps
->pos_y
= ps
->parent
->pos_y
;
1060 /* if this is the last child, we might still need to draw a connecting line */
1061 if ((!ps
->next
) && (ps
->parent
))
1062 fprintf(of
, " <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1064 ps_to_graph(j
-1) + 10.0, /* whee, use the last value here */
1070 endtime
= ps
->last
->sampledata
->sampletime
;
1071 fprintf(of
, " <rect class=\"ps\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1072 time_to_graph(starttime
- graph_start
),
1074 time_to_graph(ps
->last
->sampledata
->sampletime
- starttime
),
1077 /* paint cpu load over these */
1078 ps
->sample
= ps
->first
;
1080 while (ps
->sample
->next
) {
1083 struct ps_sched_struct
*prev
;
1086 ps
->sample
= ps
->sample
->next
;
1088 /* calculate over interval */
1089 rt
= ps
->sample
->runtime
- prev
->runtime
;
1090 wt
= ps
->sample
->waittime
- prev
->waittime
;
1092 prt
= (rt
/ 1000000000) / (ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
);
1093 wrt
= (wt
/ 1000000000) / (ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
);
1095 /* this can happen if timekeeping isn't accurate enough */
1101 if ((prt
< 0.1) && (wrt
< 0.1)) /* =~ 26 (color threshold) */
1104 fprintf(of
, " <rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1105 time_to_graph(prev
->sampledata
->sampletime
- graph_start
),
1107 time_to_graph(ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
),
1110 /* draw cpu over wait - TODO figure out how/why run + wait > interval */
1111 fprintf(of
, " <rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n",
1112 time_to_graph(prev
->sampledata
->sampletime
- graph_start
),
1113 ps_to_graph(j
+ (1.0 - prt
)),
1114 time_to_graph(ps
->sample
->sampledata
->sampletime
- prev
->sampledata
->sampletime
),
1119 /* determine where to display the process name */
1120 if ((endtime
- starttime
) < 1.5)
1121 /* too small to fit label inside the box */
1126 /* text label of process name */
1127 fprintf(of
, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]<tspan class=\"run\">%.03fs</tspan> %s</text>\n",
1128 time_to_graph(w
- graph_start
) + 5.0,
1129 ps_to_graph(j
) + 14.0,
1130 escaped
? escaped
: ps
->name
,
1132 (ps
->last
->runtime
- ps
->first
->runtime
) / 1000000000.0,
1133 arg_show_cgroup
? ps
->cgroup
: "");
1134 /* paint lines to the parent process */
1136 /* horizontal part */
1137 fprintf(of
, " <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1138 time_to_graph(starttime
- graph_start
),
1139 ps_to_graph(j
) + 10.0,
1141 ps_to_graph(j
) + 10.0);
1143 /* one vertical line connecting all the horizontal ones up */
1145 fprintf(of
, " <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1147 ps_to_graph(j
) + 10.0,
1152 j
++; /* count boxes */
1157 /* last pass - determine when idle */
1159 /* make sure we start counting from the point where we actually have
1160 * data: assume that bootchart's first sample is when data started
1164 while (ps
->next_ps
) {
1170 /* need to know last node first */
1171 ps
->sample
= ps
->first
;
1172 i
= ps
->sample
->next
->sampledata
->counter
;
1174 while (ps
->sample
->next
&& i
<(n_samples
-(arg_hz
/2))) {
1179 struct ps_sched_struct
*sample_hz
;
1181 ps
->sample
= ps
->sample
->next
;
1182 sample_hz
= ps
->sample
;
1183 for (ii
= 0; (ii
< (int)arg_hz
/2) && sample_hz
->next
; ii
++)
1184 sample_hz
= sample_hz
->next
;
1186 /* subtract bootchart cpu utilization from total */
1188 for (c
= 0; c
< n_cpus
; c
++)
1189 crt
+= sample_hz
->sampledata
->runtime
[c
] - ps
->sample
->sampledata
->runtime
[c
];
1191 brt
= sample_hz
->runtime
- ps
->sample
->runtime
;
1193 * our definition of "idle":
1195 * if for (hz / 2) we've used less CPU than (interval / 2) ...
1196 * defaults to 4.0%, which experimentally, is where atom idles
1198 if ((crt
- brt
) < (interval
/ 2.0)) {
1199 idletime
= ps
->sample
->sampledata
->sampletime
- graph_start
;
1200 fprintf(of
, "\n<!-- idle detected at %.03f seconds -->\n", idletime
);
1201 fprintf(of
, "<line class=\"idle\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n",
1202 time_to_graph(idletime
),
1204 time_to_graph(idletime
),
1205 ps_to_graph(pcount
) + arg_scale_y
);
1206 fprintf(of
, "<text class=\"idle\" x=\"%.03f\" y=\"%.03f\">%.01fs</text>\n",
1207 time_to_graph(idletime
) + 5.0,
1208 ps_to_graph(pcount
) + arg_scale_y
,
1217 static void svg_top_ten_cpu(FILE *of
, struct ps_struct
*ps_first
) {
1218 struct ps_struct
*top
[10];
1219 struct ps_struct emptyps
= {};
1220 struct ps_struct
*ps
;
1223 for (n
= 0; n
< (int) ELEMENTSOF(top
); n
++)
1226 /* walk all ps's and setup ptrs */
1228 while ((ps
= get_next_ps(ps
, ps_first
))) {
1229 for (n
= 0; n
< 10; n
++) {
1230 if (ps
->total
<= top
[n
]->total
)
1232 /* cascade insert */
1233 for (m
= 9; m
> n
; m
--)
1240 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"0\">Top CPU consumers:</text>\n");
1241 for (n
= 0; n
< 10; n
++)
1242 fprintf(of
, "<text class=\"t3\" x=\"20\" y=\"%d\">%3.03fs - <![CDATA[%s]]> [%d]</text>\n",
1249 static void svg_top_ten_pss(FILE *of
, struct ps_struct
*ps_first
) {
1250 struct ps_struct
*top
[10];
1251 struct ps_struct emptyps
= {};
1252 struct ps_struct
*ps
;
1255 for (n
= 0; n
< (int) ELEMENTSOF(top
); n
++)
1258 /* walk all ps's and setup ptrs */
1260 while ((ps
= get_next_ps(ps
, ps_first
))) {
1261 for (n
= 0; n
< 10; n
++) {
1262 if (ps
->pss_max
<= top
[n
]->pss_max
)
1265 /* cascade insert */
1266 for (m
= 9; m
> n
; m
--)
1273 fprintf(of
, "<text class=\"t2\" x=\"20\" y=\"0\">Top PSS consumers:</text>\n");
1274 for (n
= 0; n
< 10; n
++)
1275 fprintf(of
, "<text class=\"t3\" x=\"20\" y=\"%d\">%dK - <![CDATA[%s]]> [%d]</text>\n",
1282 int svg_do(FILE *of
,
1284 struct list_sample_data
*head
,
1285 struct ps_struct
*ps_first
,
1294 struct ps_struct
*ps
;
1299 LIST_FIND_TAIL(link
, sampledata
, head
);
1302 /* count initcall thread count first */
1303 svg_do_initcall(of
, head
, 1, graph_start
);
1304 ksize
= kcount
? ps_to_graph(kcount
) + (arg_scale_y
* 2) : 0;
1306 /* then count processes */
1307 while ((ps
= get_next_ps(ps
, ps_first
))) {
1313 psize
= ps_to_graph(pcount
) + (arg_scale_y
* 2);
1315 esize
= (arg_entropy
? arg_scale_y
* 7 : 0);
1317 /* after this, we can draw the header with proper sizing */
1318 svg_header(of
, head
, graph_start
, arg_percpu
? n_cpus
: 0);
1319 fprintf(of
, "<rect class=\"bg\" width=\"100%%\" height=\"100%%\" />\n\n");
1321 fprintf(of
, "<g transform=\"translate(10,400)\">\n");
1322 svg_io_bi_bar(of
, head
, n_samples
, graph_start
, interval
);
1323 fprintf(of
, "</g>\n\n");
1325 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1326 svg_io_bo_bar(of
, head
, n_samples
, graph_start
, interval
);
1327 fprintf(of
, "</g>\n\n");
1329 for (c
= -1; c
< (arg_percpu
? n_cpus
: 0); c
++) {
1331 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1332 svg_cpu_bar(of
, head
, n_cpus
, c
, graph_start
);
1333 fprintf(of
, "</g>\n\n");
1336 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1337 svg_wait_bar(of
, head
, n_cpus
, c
, graph_start
);
1338 fprintf(of
, "</g>\n\n");
1343 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
));
1344 svg_do_initcall(of
, head
, 0, graph_start
);
1345 fprintf(of
, "</g>\n\n");
1349 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
) + ksize
);
1350 svg_ps_bars(of
, head
, n_samples
, n_cpus
, ps_first
, graph_start
, interval
);
1351 fprintf(of
, "</g>\n\n");
1353 fprintf(of
, "<g transform=\"translate(10, 0)\">\n");
1354 r
= svg_title(of
, build
, pscount
, log_start
, overrun
);
1355 fprintf(of
, "</g>\n\n");
1360 fprintf(of
, "<g transform=\"translate(10,200)\">\n");
1361 svg_top_ten_cpu(of
, ps_first
);
1362 fprintf(of
, "</g>\n\n");
1365 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
) + ksize
+ psize
);
1366 svg_entropy_bar(of
, head
, graph_start
);
1367 fprintf(of
, "</g>\n\n");
1371 fprintf(of
, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y
* offset
) + ksize
+ psize
+ esize
);
1372 svg_pss_graph(of
, head
, ps_first
, graph_start
);
1373 fprintf(of
, "</g>\n\n");
1375 fprintf(of
, "<g transform=\"translate(410,200)\">\n");
1376 svg_top_ten_pss(of
, ps_first
);
1377 fprintf(of
, "</g>\n\n");
1380 /* fprintf footer */
1381 fprintf(of
, "\n</svg>\n");