| /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
| |
| /*** |
| This file is part of systemd. |
| |
| Copyright (C) 2009-2013 Intel Coproration |
| |
| Authors: |
| Auke Kok <auke-jan.h.kok@intel.com> |
| |
| systemd is free software; you can redistribute it and/or modify it |
| under the terms of the GNU Lesser General Public License as published by |
| the Free Software Foundation; either version 2.1 of the License, or |
| (at your option) any later version. |
| |
| systemd is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public License |
| along with systemd; If not, see <http://www.gnu.org/licenses/>. |
| ***/ |
| |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <limits.h> |
| #include <unistd.h> |
| #include <sys/utsname.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| |
| #include "util.h" |
| #include "macro.h" |
| #include "store.h" |
| #include "svg.h" |
| #include "bootchart.h" |
| |
| #define time_to_graph(t) ((t) * arg_scale_x) |
| #define ps_to_graph(n) ((n) * arg_scale_y) |
| #define kb_to_graph(m) ((m) * arg_scale_y * 0.0001) |
| #define to_color(n) (192.0 - ((n) * 192.0)) |
| |
| #define max(x, y) (((x) > (y)) ? (x) : (y)) |
| #define min(x, y) (((x) < (y)) ? (x) : (y)) |
| |
| static char str[8092]; |
| |
| #define svg(a...) do { snprintf(str, 8092, ## a); fputs(str, of); fflush(of); } while (0) |
| |
| static const char * const colorwheel[12] = { |
| "rgb(255,32,32)", // red |
| "rgb(32,192,192)", // cyan |
| "rgb(255,128,32)", // orange |
| "rgb(128,32,192)", // blue-violet |
| "rgb(255,255,32)", // yellow |
| "rgb(192,32,128)", // red-violet |
| "rgb(32,255,32)", // green |
| "rgb(255,64,32)", // red-orange |
| "rgb(32,32,255)", // blue |
| "rgb(255,192,32)", // yellow-orange |
| "rgb(192,32,192)", // violet |
| "rgb(32,192,32)" // yellow-green |
| }; |
| |
| static double idletime = -1.0; |
| static int pfiltered = 0; |
| static int pcount = 0; |
| static int kcount = 0; |
| static float psize = 0; |
| static float ksize = 0; |
| static float esize = 0; |
| |
| static void svg_header(void) { |
| float w; |
| float h; |
| |
| /* min width is about 1600px due to the label */ |
| w = 150.0 + 10.0 + time_to_graph(sampletime[samples-1] - graph_start); |
| w = ((w < 1600.0) ? 1600.0 : w); |
| |
| /* height is variable based on pss, psize, ksize */ |
| h = 400.0 + (arg_scale_y * 30.0) /* base graphs and title */ |
| + (arg_pss ? (100.0 * arg_scale_y) + (arg_scale_y * 7.0) : 0.0) /* pss estimate */ |
| + psize + ksize + esize; |
| |
| svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"); |
| svg("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "); |
| svg("\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); |
| |
| //svg("<g transform=\"translate(10,%d)\">\n", 1000 + 150 + (pcount * 20)); |
| svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" ", |
| w, h); |
| svg("xmlns=\"http://www.w3.org/2000/svg\">\n\n"); |
| |
| /* write some basic info as a comment, including some help */ |
| svg("<!-- This file is a bootchart SVG file. It is best rendered in a browser -->\n"); |
| svg("<!-- such as Chrome, Chromium, or Firefox. Other applications that -->\n"); |
| svg("<!-- render these files properly but more slowly are ImageMagick, gimp, -->\n"); |
| svg("<!-- inkscape, etc. To display the files on your system, just point -->\n"); |
| svg("<!-- your browser to file:///run/log/ and click. This bootchart was -->\n\n"); |
| |
| svg("<!-- generated by bootchart version %s, running with options: -->\n", VERSION); |
| svg("<!-- hz=\"%f\" n=\"%d\" -->\n", arg_hz, arg_samples_len); |
| svg("<!-- x=\"%f\" y=\"%f\" -->\n", arg_scale_x, arg_scale_y); |
| svg("<!-- rel=\"%d\" f=\"%d\" -->\n", arg_relative, arg_filter); |
| svg("<!-- p=\"%d\" e=\"%d\" -->\n", arg_pss, arg_entropy); |
| svg("<!-- o=\"%s\" i=\"%s\" -->\n\n", arg_output_path, arg_init_path); |
| |
| /* style sheet */ |
| svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"); |
| |
| svg(" rect { stroke-width: 1; }\n"); |
| svg(" rect.cpu { fill: rgb(64,64,240); stroke-width: 0; fill-opacity: 0.7; }\n"); |
| svg(" rect.wait { fill: rgb(240,240,0); stroke-width: 0; fill-opacity: 0.7; }\n"); |
| svg(" rect.bi { fill: rgb(240,128,128); stroke-width: 0; fill-opacity: 0.7; }\n"); |
| svg(" rect.bo { fill: rgb(192,64,64); stroke-width: 0; fill-opacity: 0.7; }\n"); |
| svg(" rect.ps { fill: rgb(192,192,192); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n"); |
| svg(" rect.krnl { fill: rgb(240,240,0); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n"); |
| svg(" rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"); |
| svg(" rect.clrw { stroke-width: 0; fill-opacity: 0.7;}\n"); |
| svg(" line { stroke: rgb(64,64,64); stroke-width: 1; }\n"); |
| svg("// line.sec1 { }\n"); |
| svg(" line.sec5 { stroke-width: 2; }\n"); |
| svg(" line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"); |
| svg(" line.dot { stroke-dasharray: 2 4; }\n"); |
| svg(" line.idle { stroke: rgb(64,64,64); stroke-dasharray: 10 6; stroke-opacity: 0.7; }\n"); |
| |
| svg(" .run { font-size: 8; font-style: italic; }\n"); |
| svg(" text { font-family: Verdana, Helvetica; font-size: 10; }\n"); |
| svg(" text.sec { font-size: 8; }\n"); |
| svg(" text.t1 { font-size: 24; }\n"); |
| svg(" text.t2 { font-size: 12; }\n"); |
| svg(" text.idle { font-size: 18; }\n"); |
| |
| svg(" ]]>\n </style>\n</defs>\n\n"); |
| } |
| |
| static void svg_title(const char *build) { |
| char cmdline[256] = ""; |
| char filename[PATH_MAX]; |
| char buf[256]; |
| char rootbdev[16] = "Unknown"; |
| char model[256] = "Unknown"; |
| char date[256] = "Unknown"; |
| char cpu[256] = "Unknown"; |
| char *c; |
| FILE *f; |
| time_t t; |
| int fd; |
| struct utsname uts; |
| |
| /* grab /proc/cmdline */ |
| fd = openat(procfd, "cmdline", O_RDONLY); |
| f = fdopen(fd, "r"); |
| if (f) { |
| if (!fgets(cmdline, 255, f)) |
| sprintf(cmdline, "Unknown"); |
| fclose(f); |
| } |
| |
| /* extract root fs so we can find disk model name in sysfs */ |
| /* FIXME: this works only in the simple case */ |
| c = strstr(cmdline, "root=/dev/"); |
| if (c) { |
| strncpy(rootbdev, &c[10], 3); |
| rootbdev[3] = '\0'; |
| sprintf(filename, "block/%s/device/model", rootbdev); |
| fd = openat(sysfd, filename, O_RDONLY); |
| f = fdopen(fd, "r"); |
| if (f) { |
| if (!fgets(model, 255, f)) |
| fprintf(stderr, "Error reading disk model for %s\n", rootbdev); |
| fclose(f); |
| } |
| } |
| |
| /* various utsname parameters */ |
| if (uname(&uts)) |
| fprintf(stderr, "Error getting uname info\n"); |
| |
| /* date */ |
| t = time(NULL); |
| strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&t)); |
| |
| /* CPU type */ |
| fd = openat(procfd, "cpuinfo", O_RDONLY); |
| f = fdopen(fd, "r"); |
| if (f) { |
| while (fgets(buf, 255, f)) { |
| if (strstr(buf, "model name")) { |
| strncpy(cpu, &buf[13], 255); |
| break; |
| } |
| } |
| fclose(f); |
| } |
| |
| svg("<text class=\"t1\" x=\"0\" y=\"30\">Bootchart for %s - %s</text>\n", |
| uts.nodename, date); |
| svg("<text class=\"t2\" x=\"20\" y=\"50\">System: %s %s %s %s</text>\n", |
| uts.sysname, uts.release, uts.version, uts.machine); |
| svg("<text class=\"t2\" x=\"20\" y=\"65\">CPU: %s</text>\n", |
| cpu); |
| svg("<text class=\"t2\" x=\"20\" y=\"80\">Disk: %s</text>\n", |
| model); |
| svg("<text class=\"t2\" x=\"20\" y=\"95\">Boot options: %s</text>\n", |
| cmdline); |
| svg("<text class=\"t2\" x=\"20\" y=\"110\">Build: %s</text>\n", |
| build); |
| svg("<text class=\"t2\" x=\"20\" y=\"125\">Log start time: %.03fs</text>\n", log_start); |
| svg("<text class=\"t2\" x=\"20\" y=\"140\">Idle time: "); |
| |
| if (idletime >= 0.0) |
| svg("%.03fs", idletime); |
| else |
| svg("Not detected"); |
| svg("</text>\n"); |
| svg("<text class=\"sec\" x=\"20\" y=\"155\">Graph data: %.03f samples/sec, recorded %i total, dropped %i samples, %i processes, %i filtered</text>\n", |
| arg_hz, arg_samples_len, overrun, pscount, pfiltered); |
| } |
| |
| static void svg_graph_box(int height) { |
| double d = 0.0; |
| int i = 0; |
| |
| /* outside box, fill */ |
| svg("<rect class=\"box\" x=\"%.03f\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(0.0), |
| time_to_graph(sampletime[samples-1] - graph_start), |
| ps_to_graph(height)); |
| |
| for (d = graph_start; d <= sampletime[samples-1]; |
| d += (arg_scale_x < 2.0 ? 60.0 : arg_scale_x < 10.0 ? 1.0 : 0.1)) { |
| /* lines for each second */ |
| if (i % 50 == 0) |
| svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n", |
| time_to_graph(d - graph_start), |
| time_to_graph(d - graph_start), |
| ps_to_graph(height)); |
| else if (i % 10 == 0) |
| svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n", |
| time_to_graph(d - graph_start), |
| time_to_graph(d - graph_start), |
| ps_to_graph(height)); |
| else |
| svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n", |
| time_to_graph(d - graph_start), |
| time_to_graph(d - graph_start), |
| ps_to_graph(height)); |
| |
| /* time label */ |
| if (i % 10 == 0) |
| svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n", |
| time_to_graph(d - graph_start), |
| -5.0, |
| d - graph_start); |
| |
| i++; |
| } |
| } |
| |
| /* xml comments must not contain "--" */ |
| static char* xml_comment_encode(const char* name) { |
| char *enc_name, *p; |
| |
| enc_name = strdup(name); |
| if (!enc_name) |
| return NULL; |
| |
| for (p = enc_name; *p; p++) |
| if (p[0] == '-' && p[1] == '-') |
| p[1] = '_'; |
| |
| return enc_name; |
| } |
| |
| static void svg_pss_graph(void) { |
| struct ps_struct *ps; |
| int i; |
| |
| svg("\n\n<!-- Pss memory size graph -->\n"); |
| |
| svg("\n <text class=\"t2\" x=\"5\" y=\"-15\">Memory allocation - Pss</text>\n"); |
| |
| /* vsize 1000 == 1000mb */ |
| svg_graph_box(100); |
| /* draw some hlines for usable memory sizes */ |
| for (i = 100000; i < 1000000; i += 100000) { |
| svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"%.0f\" x2=\"%.03f\" y2=\"%.0f\"/>\n", |
| time_to_graph(.0), |
| kb_to_graph(i), |
| time_to_graph(sampletime[samples-1] - graph_start), |
| kb_to_graph(i)); |
| svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.0f\">%dM</text>\n", |
| time_to_graph(sampletime[samples-1] - graph_start) + 5, |
| kb_to_graph(i), (1000000 - i) / 1000); |
| } |
| svg("\n"); |
| |
| /* now plot the graph itself */ |
| for (i = 1; i < samples ; i++) { |
| int bottom; |
| int top; |
| |
| bottom = 0; |
| top = 0; |
| |
| /* put all the small pss blocks into the bottom */ |
| ps = ps_first; |
| while (ps->next_ps) { |
| ps = ps->next_ps; |
| if (!ps) |
| continue; |
| if (ps->sample[i].pss <= (100 * arg_scale_y)) |
| top += ps->sample[i].pss; |
| }; |
| svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| "rgb(64,64,64)", |
| time_to_graph(sampletime[i - 1] - graph_start), |
| kb_to_graph(1000000.0 - top), |
| time_to_graph(sampletime[i] - sampletime[i - 1]), |
| kb_to_graph(top - bottom)); |
| |
| bottom = top; |
| |
| /* now plot the ones that are of significant size */ |
| ps = ps_first; |
| while (ps->next_ps) { |
| ps = ps->next_ps; |
| if (!ps) |
| continue; |
| /* don't draw anything smaller than 2mb */ |
| if (ps->sample[i].pss > (100 * arg_scale_y)) { |
| top = bottom + ps->sample[i].pss; |
| svg(" <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| colorwheel[ps->pid % 12], |
| time_to_graph(sampletime[i - 1] - graph_start), |
| kb_to_graph(1000000.0 - top), |
| time_to_graph(sampletime[i] - sampletime[i - 1]), |
| kb_to_graph(top - bottom)); |
| bottom = top; |
| } |
| } |
| } |
| |
| /* overlay all the text labels */ |
| for (i = 1; i < samples ; i++) { |
| int bottom; |
| int top; |
| |
| bottom = 0; |
| top = 0; |
| |
| /* put all the small pss blocks into the bottom */ |
| ps = ps_first; |
| while (ps->next_ps) { |
| ps = ps->next_ps; |
| if (!ps) |
| continue; |
| if (ps->sample[i].pss <= (100 * arg_scale_y)) |
| top += ps->sample[i].pss; |
| }; |
| |
| bottom = top; |
| |
| /* now plot the ones that are of significant size */ |
| ps = ps_first; |
| while (ps->next_ps) { |
| ps = ps->next_ps; |
| if (!ps) |
| continue; |
| /* don't draw anything smaller than 2mb */ |
| if (ps->sample[i].pss > (100 * arg_scale_y)) { |
| top = bottom + ps->sample[i].pss; |
| /* draw a label with the process / PID */ |
| if ((i == 1) || (ps->sample[i - 1].pss <= (100 * arg_scale_y))) |
| svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n", |
| time_to_graph(sampletime[i] - graph_start), |
| kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), |
| ps->name, |
| ps->pid); |
| bottom = top; |
| } |
| } |
| } |
| |
| /* debug output - full data dump */ |
| svg("\n\n<!-- PSS map - csv format -->\n"); |
| ps = ps_first; |
| while (ps->next_ps) { |
| char _cleanup_free_ *enc_name = NULL; |
| ps = ps->next_ps; |
| if (!ps) |
| continue; |
| |
| enc_name = xml_comment_encode(ps->name); |
| if(!enc_name) |
| continue; |
| |
| svg("<!-- %s [%d] pss=", enc_name, ps->pid); |
| |
| for (i = 0; i < samples ; i++) { |
| svg("%d," , ps->sample[i].pss); |
| } |
| svg(" -->\n"); |
| } |
| |
| } |
| |
| static void svg_io_bi_bar(void) { |
| double max = 0.0; |
| double range; |
| int max_here = 0; |
| int i; |
| |
| svg("<!-- IO utilization graph - In -->\n"); |
| |
| svg("<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - read</text>\n"); |
| |
| /* |
| * calculate rounding range |
| * |
| * We need to round IO data since IO block data is not updated on |
| * each poll. Applying a smoothing function loses some burst data, |
| * so keep the smoothing range short. |
| */ |
| range = 0.25 / (1.0 / arg_hz); |
| if (range < 2.0) |
| range = 2.0; /* no smoothing */ |
| |
| /* surrounding box */ |
| svg_graph_box(5); |
| |
| /* find the max IO first */ |
| for (i = 1; i < samples; i++) { |
| int start; |
| int stop; |
| double tot; |
| |
| start = max(i - ((range / 2) - 1), 0); |
| stop = min(i + (range / 2), samples - 1); |
| |
| tot = (double)(blockstat[stop].bi - blockstat[start].bi) |
| / (stop - start); |
| if (tot > max) { |
| max = tot; |
| max_here = i; |
| } |
| tot = (double)(blockstat[stop].bo - blockstat[start].bo) |
| / (stop - start); |
| if (tot > max) |
| max = tot; |
| } |
| |
| /* plot bi */ |
| for (i = 1; i < samples; i++) { |
| int start; |
| int stop; |
| double tot; |
| double pbi; |
| |
| start = max(i - ((range / 2) - 1), 0); |
| stop = min(i + (range / 2), samples); |
| |
| tot = (double)(blockstat[stop].bi - blockstat[start].bi) |
| / (stop - start); |
| pbi = tot / max; |
| |
| if (pbi > 0.001) |
| svg("<rect class=\"bi\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(sampletime[i - 1] - graph_start), |
| (arg_scale_y * 5) - (pbi * (arg_scale_y * 5)), |
| time_to_graph(sampletime[i] - sampletime[i - 1]), |
| pbi * (arg_scale_y * 5)); |
| |
| /* labels around highest value */ |
| if (i == max_here) { |
| svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n", |
| time_to_graph(sampletime[i] - graph_start) + 5, |
| ((arg_scale_y * 5) - (pbi * (arg_scale_y * 5))) + 15, |
| max / 1024.0 / (interval / 1000000000.0)); |
| } |
| } |
| } |
| |
| static void svg_io_bo_bar(void) { |
| double max = 0.0; |
| double range; |
| int max_here = 0; |
| int i; |
| |
| svg("<!-- IO utilization graph - out -->\n"); |
| |
| svg("<text class=\"t2\" x=\"5\" y=\"-15\">IO utilization - write</text>\n"); |
| |
| /* |
| * calculate rounding range |
| * |
| * We need to round IO data since IO block data is not updated on |
| * each poll. Applying a smoothing function loses some burst data, |
| * so keep the smoothing range short. |
| */ |
| range = 0.25 / (1.0 / arg_hz); |
| if (range < 2.0) |
| range = 2.0; /* no smoothing */ |
| |
| /* surrounding box */ |
| svg_graph_box(5); |
| |
| /* find the max IO first */ |
| for (i = 1; i < samples; i++) { |
| int start; |
| int stop; |
| double tot; |
| |
| start = max(i - ((range / 2) - 1), 0); |
| stop = min(i + (range / 2), samples - 1); |
| |
| tot = (double)(blockstat[stop].bi - blockstat[start].bi) |
| / (stop - start); |
| if (tot > max) |
| max = tot; |
| tot = (double)(blockstat[stop].bo - blockstat[start].bo) |
| / (stop - start); |
| if (tot > max) { |
| max = tot; |
| max_here = i; |
| } |
| } |
| |
| /* plot bo */ |
| for (i = 1; i < samples; i++) { |
| int start; |
| int stop; |
| double tot; |
| double pbo; |
| |
| start = max(i - ((range / 2) - 1), 0); |
| stop = min(i + (range / 2), samples); |
| |
| tot = (double)(blockstat[stop].bo - blockstat[start].bo) |
| / (stop - start); |
| pbo = tot / max; |
| |
| if (pbo > 0.001) |
| svg("<rect class=\"bo\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(sampletime[i - 1] - graph_start), |
| (arg_scale_y * 5) - (pbo * (arg_scale_y * 5)), |
| time_to_graph(sampletime[i] - sampletime[i - 1]), |
| pbo * (arg_scale_y * 5)); |
| |
| /* labels around highest bo value */ |
| if (i == max_here) { |
| svg(" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n", |
| time_to_graph(sampletime[i] - graph_start) + 5, |
| ((arg_scale_y * 5) - (pbo * (arg_scale_y * 5))), |
| max / 1024.0 / (interval / 1000000000.0)); |
| } |
| } |
| } |
| |
| static void svg_cpu_bar(void) { |
| int i; |
| |
| svg("<!-- CPU utilization graph -->\n"); |
| |
| svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU utilization</text>\n"); |
| /* surrounding box */ |
| svg_graph_box(5); |
| |
| /* bars for each sample, proportional to the CPU util. */ |
| for (i = 1; i < samples; i++) { |
| int c; |
| double trt; |
| double ptrt; |
| |
| ptrt = trt = 0.0; |
| |
| for (c = 0; c < cpus; c++) |
| trt += cpustat[c].sample[i].runtime - cpustat[c].sample[i - 1].runtime; |
| |
| trt = trt / 1000000000.0; |
| |
| trt = trt / (double)cpus; |
| |
| if (trt > 0.0) |
| ptrt = trt / (sampletime[i] - sampletime[i - 1]); |
| |
| if (ptrt > 1.0) |
| ptrt = 1.0; |
| |
| if (ptrt > 0.001) { |
| svg("<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(sampletime[i - 1] - graph_start), |
| (arg_scale_y * 5) - (ptrt * (arg_scale_y * 5)), |
| time_to_graph(sampletime[i] - sampletime[i - 1]), |
| ptrt * (arg_scale_y * 5)); |
| } |
| } |
| } |
| |
| static void svg_wait_bar(void) { |
| int i; |
| |
| svg("<!-- Wait time aggregation box -->\n"); |
| |
| svg("<text class=\"t2\" x=\"5\" y=\"-15\">CPU wait</text>\n"); |
| |
| /* surrounding box */ |
| svg_graph_box(5); |
| |
| /* bars for each sample, proportional to the CPU util. */ |
| for (i = 1; i < samples; i++) { |
| int c; |
| double twt; |
| double ptwt; |
| |
| ptwt = twt = 0.0; |
| |
| for (c = 0; c < cpus; c++) |
| twt += cpustat[c].sample[i].waittime - cpustat[c].sample[i - 1].waittime; |
| |
| twt = twt / 1000000000.0; |
| |
| twt = twt / (double)cpus; |
| |
| if (twt > 0.0) |
| ptwt = twt / (sampletime[i] - sampletime[i - 1]); |
| |
| if (ptwt > 1.0) |
| ptwt = 1.0; |
| |
| if (ptwt > 0.001) { |
| svg("<rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(sampletime[i - 1] - graph_start), |
| ((arg_scale_y * 5) - (ptwt * (arg_scale_y * 5))), |
| time_to_graph(sampletime[i] - sampletime[i - 1]), |
| ptwt * (arg_scale_y * 5)); |
| } |
| } |
| } |
| |
| |
| static void svg_entropy_bar(void) { |
| int i; |
| |
| svg("<!-- entropy pool graph -->\n"); |
| |
| svg("<text class=\"t2\" x=\"5\" y=\"-15\">Entropy pool size</text>\n"); |
| /* surrounding box */ |
| svg_graph_box(5); |
| |
| /* bars for each sample, scale 0-4096 */ |
| for (i = 1; i < samples; i++) { |
| /* svg("<!-- entropy %.03f %i -->\n", sampletime[i], entropy_avail[i]); */ |
| svg("<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(sampletime[i - 1] - graph_start), |
| ((arg_scale_y * 5) - ((entropy_avail[i] / 4096.) * (arg_scale_y * 5))), |
| time_to_graph(sampletime[i] - sampletime[i - 1]), |
| (entropy_avail[i] / 4096.) * (arg_scale_y * 5)); |
| } |
| } |
| |
| static struct ps_struct *get_next_ps(struct ps_struct *ps) { |
| /* |
| * walk the list of processes and return the next one to be |
| * painted |
| */ |
| if (ps == ps_first) |
| return ps->next_ps; |
| |
| /* go deep */ |
| if (ps->children) |
| return ps->children; |
| |
| /* find siblings */ |
| if (ps->next) |
| return ps->next; |
| |
| /* go back for parent siblings */ |
| while (1) { |
| if (ps->parent) |
| if (ps->parent->next) |
| return ps->parent->next; |
| ps = ps->parent; |
| if (!ps) |
| return ps; |
| } |
| |
| return NULL; |
| } |
| |
| static int ps_filter(struct ps_struct *ps) { |
| if (!arg_filter) |
| return 0; |
| |
| /* can't draw data when there is only 1 sample (need start + stop) */ |
| if (ps->first == ps->last) |
| return -1; |
| |
| /* don't filter kthreadd */ |
| if (ps->pid == 2) |
| return 0; |
| |
| /* drop stuff that doesn't use any real CPU time */ |
| if (ps->total <= 0.001) |
| return -1; |
| |
| return 0; |
| } |
| |
| static void svg_do_initcall(int count_only) { |
| FILE _cleanup_pclose_ *f = NULL; |
| double t; |
| char func[256]; |
| int ret; |
| int usecs; |
| |
| /* can't plot initcall when disabled or in relative mode */ |
| if (!initcall || arg_relative) { |
| kcount = 0; |
| return; |
| } |
| |
| if (!count_only) { |
| svg("<!-- initcall -->\n"); |
| |
| svg("<text class=\"t2\" x=\"5\" y=\"-15\">Kernel init threads</text>\n"); |
| /* surrounding box */ |
| svg_graph_box(kcount); |
| } |
| |
| kcount = 0; |
| |
| /* |
| * Initcall graphing - parses dmesg buffer and displays kernel threads |
| * This somewhat uses the same methods and scaling to show processes |
| * but looks a lot simpler. It's overlaid entirely onto the PS graph |
| * when appropriate. |
| */ |
| |
| f = popen("dmesg", "r"); |
| if (!f) |
| return; |
| |
| while (!feof(f)) { |
| int c; |
| int z = 0; |
| char l[256]; |
| |
| if (fgets(l, sizeof(l) - 1, f) == NULL) |
| continue; |
| |
| c = sscanf(l, "[%lf] initcall %s %*s %d %*s %d %*s", |
| &t, func, &ret, &usecs); |
| if (c != 4) { |
| /* also parse initcalls done by module loading */ |
| c = sscanf(l, "[%lf] initcall %s %*s %*s %d %*s %d %*s", |
| &t, func, &ret, &usecs); |
| if (c != 4) |
| continue; |
| } |
| |
| /* chop the +0xXX/0xXX stuff */ |
| while(func[z] != '+') |
| z++; |
| func[z] = 0; |
| |
| if (count_only) { |
| /* filter out irrelevant stuff */ |
| if (usecs >= 1000) |
| kcount++; |
| continue; |
| } |
| |
| svg("<!-- thread=\"%s\" time=\"%.3f\" elapsed=\"%d\" result=\"%d\" -->\n", |
| func, t, usecs, ret); |
| |
| if (usecs < 1000) |
| continue; |
| |
| /* rect */ |
| svg(" <rect class=\"krnl\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(t - (usecs / 1000000.0)), |
| ps_to_graph(kcount), |
| time_to_graph(usecs / 1000000.0), |
| ps_to_graph(1)); |
| |
| /* label */ |
| svg(" <text x=\"%.03f\" y=\"%.03f\">%s <tspan class=\"run\">%.03fs</tspan></text>\n", |
| time_to_graph(t - (usecs / 1000000.0)) + 5, |
| ps_to_graph(kcount) + 15, |
| func, |
| usecs / 1000000.0); |
| |
| kcount++; |
| } |
| } |
| |
| static void svg_ps_bars(void) { |
| struct ps_struct *ps; |
| int i = 0; |
| int j = 0; |
| int w; |
| int pid; |
| |
| svg("<!-- Process graph -->\n"); |
| |
| svg("<text class=\"t2\" x=\"5\" y=\"-15\">Processes</text>\n"); |
| |
| /* surrounding box */ |
| svg_graph_box(pcount); |
| |
| /* pass 2 - ps boxes */ |
| ps = ps_first; |
| while ((ps = get_next_ps(ps))) { |
| char _cleanup_free_ *enc_name = NULL; |
| |
| double starttime; |
| int t; |
| |
| if (!ps) |
| continue; |
| |
| enc_name = xml_comment_encode(ps->name); |
| if(!enc_name) |
| continue; |
| |
| /* leave some trace of what we actually filtered etc. */ |
| svg("<!-- %s [%i] ppid=%i runtime=%.03fs -->\n", enc_name, ps->pid, |
| ps->ppid, ps->total); |
| |
| /* it would be nice if we could use exec_start from /proc/pid/sched, |
| * but it's unreliable and gives bogus numbers */ |
| starttime = sampletime[ps->first]; |
| |
| if (!ps_filter(ps)) { |
| /* remember where _to_ our children need to draw a line */ |
| ps->pos_x = time_to_graph(starttime - graph_start); |
| ps->pos_y = ps_to_graph(j+1); /* bottom left corner */ |
| } else { |
| /* hook children to our parent coords instead */ |
| ps->pos_x = ps->parent->pos_x; |
| ps->pos_y = ps->parent->pos_y; |
| |
| /* if this is the last child, we might still need to draw a connecting line */ |
| if ((!ps->next) && (ps->parent)) |
| svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n", |
| ps->parent->pos_x, |
| ps_to_graph(j-1) + 10.0, /* whee, use the last value here */ |
| ps->parent->pos_x, |
| ps->parent->pos_y); |
| continue; |
| } |
| |
| svg(" <rect class=\"ps\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(starttime - graph_start), |
| ps_to_graph(j), |
| time_to_graph(sampletime[ps->last] - starttime), |
| ps_to_graph(1)); |
| |
| /* paint cpu load over these */ |
| for (t = ps->first + 1; t < ps->last; t++) { |
| double rt, prt; |
| double wt, wrt; |
| |
| /* calculate over interval */ |
| rt = ps->sample[t].runtime - ps->sample[t-1].runtime; |
| wt = ps->sample[t].waittime - ps->sample[t-1].waittime; |
| |
| prt = (rt / 1000000000) / (sampletime[t] - sampletime[t-1]); |
| wrt = (wt / 1000000000) / (sampletime[t] - sampletime[t-1]); |
| |
| /* this can happen if timekeeping isn't accurate enough */ |
| if (prt > 1.0) |
| prt = 1.0; |
| if (wrt > 1.0) |
| wrt = 1.0; |
| |
| if ((prt < 0.1) && (wrt < 0.1)) /* =~ 26 (color threshold) */ |
| continue; |
| |
| svg(" <rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(sampletime[t - 1] - graph_start), |
| ps_to_graph(j), |
| time_to_graph(sampletime[t] - sampletime[t - 1]), |
| ps_to_graph(wrt)); |
| |
| /* draw cpu over wait - TODO figure out how/why run + wait > interval */ |
| svg(" <rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", |
| time_to_graph(sampletime[t - 1] - graph_start), |
| ps_to_graph(j + (1.0 - prt)), |
| time_to_graph(sampletime[t] - sampletime[t - 1]), |
| ps_to_graph(prt)); |
| } |
| |
| /* determine where to display the process name */ |
| if (sampletime[ps->last] - sampletime[ps->first] < 1.5) |
| /* too small to fit label inside the box */ |
| w = ps->last; |
| else |
| w = ps->first; |
| |
| /* text label of process name */ |
| svg(" <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]<tspan class=\"run\">%.03fs</tspan></text>\n", |
| time_to_graph(sampletime[w] - graph_start) + 5.0, |
| ps_to_graph(j) + 14.0, |
| ps->name, |
| ps->pid, |
| (ps->sample[ps->last].runtime - ps->sample[ps->first].runtime) / 1000000000.0); |
| /* paint lines to the parent process */ |
| if (ps->parent) { |
| /* horizontal part */ |
| svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n", |
| time_to_graph(starttime - graph_start), |
| ps_to_graph(j) + 10.0, |
| ps->parent->pos_x, |
| ps_to_graph(j) + 10.0); |
| |
| /* one vertical line connecting all the horizontal ones up */ |
| if (!ps->next) |
| svg(" <line class=\"dot\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n", |
| ps->parent->pos_x, |
| ps_to_graph(j) + 10.0, |
| ps->parent->pos_x, |
| ps->parent->pos_y); |
| } |
| |
| j++; /* count boxes */ |
| |
| svg("\n"); |
| } |
| |
| /* last pass - determine when idle */ |
| pid = getpid(); |
| /* make sure we start counting from the point where we actually have |
| * data: assume that bootchart's first sample is when data started |
| */ |
| ps = ps_first; |
| while (ps->next_ps) { |
| ps = ps->next_ps; |
| if (ps->pid == pid) |
| break; |
| } |
| |
| for (i = ps->first; i < samples - (arg_hz / 2); i++) { |
| double crt; |
| double brt; |
| int c; |
| |
| /* subtract bootchart cpu utilization from total */ |
| crt = 0.0; |
| for (c = 0; c < cpus; c++) |
| crt += cpustat[c].sample[i + ((int)arg_hz / 2)].runtime - cpustat[c].sample[i].runtime; |
| brt = ps->sample[i + ((int)arg_hz / 2)].runtime - ps->sample[i].runtime; |
| |
| /* |
| * our definition of "idle": |
| * |
| * if for (hz / 2) we've used less CPU than (interval / 2) ... |
| * defaults to 4.0%, which experimentally, is where atom idles |
| */ |
| if ((crt - brt) < (interval / 2.0)) { |
| idletime = sampletime[i] - graph_start; |
| svg("\n<!-- idle detected at %.03f seconds -->\n", |
| idletime); |
| svg("<line class=\"idle\" x1=\"%.03f\" y1=\"%.03f\" x2=\"%.03f\" y2=\"%.03f\" />\n", |
| time_to_graph(idletime), |
| -arg_scale_y, |
| time_to_graph(idletime), |
| ps_to_graph(pcount) + arg_scale_y); |
| svg("<text class=\"idle\" x=\"%.03f\" y=\"%.03f\">%.01fs</text>\n", |
| time_to_graph(idletime) + 5.0, |
| ps_to_graph(pcount) + arg_scale_y, |
| idletime); |
| break; |
| } |
| } |
| } |
| |
| static void svg_top_ten_cpu(void) { |
| struct ps_struct *top[10]; |
| struct ps_struct emptyps; |
| struct ps_struct *ps; |
| int n, m; |
| |
| memset(&emptyps, 0, sizeof(struct ps_struct)); |
| for (n=0; n < 10; n++) |
| top[n] = &emptyps; |
| |
| /* walk all ps's and setup ptrs */ |
| ps = ps_first; |
| while ((ps = get_next_ps(ps))) { |
| for (n = 0; n < 10; n++) { |
| if (ps->total <= top[n]->total) |
| continue; |
| /* cascade insert */ |
| for (m = 9; m > n; m--) |
| top[m] = top[m-1]; |
| top[n] = ps; |
| break; |
| } |
| } |
| |
| svg("<text class=\"t2\" x=\"20\" y=\"0\">Top CPU consumers:</text>\n"); |
| for (n = 0; n < 10; n++) |
| svg("<text class=\"t3\" x=\"20\" y=\"%d\">%3.03fs - <![CDATA[%s]]> [%d]</text>\n", |
| 20 + (n * 13), |
| top[n]->total, |
| top[n]->name, |
| top[n]->pid); |
| } |
| |
| static void svg_top_ten_pss(void) { |
| struct ps_struct *top[10]; |
| struct ps_struct emptyps; |
| struct ps_struct *ps; |
| int n, m; |
| |
| memset(&emptyps, 0, sizeof(struct ps_struct)); |
| for (n=0; n < 10; n++) |
| top[n] = &emptyps; |
| |
| /* walk all ps's and setup ptrs */ |
| ps = ps_first; |
| while ((ps = get_next_ps(ps))) { |
| for (n = 0; n < 10; n++) { |
| if (ps->pss_max <= top[n]->pss_max) |
| continue; |
| /* cascade insert */ |
| for (m = 9; m > n; m--) |
| top[m] = top[m-1]; |
| top[n] = ps; |
| break; |
| } |
| } |
| |
| svg("<text class=\"t2\" x=\"20\" y=\"0\">Top PSS consumers:</text>\n"); |
| for (n = 0; n < 10; n++) |
| svg("<text class=\"t3\" x=\"20\" y=\"%d\">%dK - <![CDATA[%s]]> [%d]</text>\n", |
| 20 + (n * 13), |
| top[n]->pss_max, |
| top[n]->name, |
| top[n]->pid); |
| } |
| |
| void svg_do(const char *build) { |
| struct ps_struct *ps; |
| |
| memset(&str, 0, sizeof(str)); |
| |
| ps = ps_first; |
| |
| /* count initcall thread count first */ |
| svg_do_initcall(1); |
| ksize = (kcount ? ps_to_graph(kcount) + (arg_scale_y * 2) : 0); |
| |
| /* then count processes */ |
| while ((ps = get_next_ps(ps))) { |
| if (!ps_filter(ps)) |
| pcount++; |
| else |
| pfiltered++; |
| } |
| psize = ps_to_graph(pcount) + (arg_scale_y * 2); |
| |
| esize = (arg_entropy ? arg_scale_y * 7 : 0); |
| |
| /* after this, we can draw the header with proper sizing */ |
| svg_header(); |
| |
| svg("<g transform=\"translate(10,400)\">\n"); |
| svg_io_bi_bar(); |
| svg("</g>\n\n"); |
| |
| svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 7.0)); |
| svg_io_bo_bar(); |
| svg("</g>\n\n"); |
| |
| svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 14.0)); |
| svg_cpu_bar(); |
| svg("</g>\n\n"); |
| |
| svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 21.0)); |
| svg_wait_bar(); |
| svg("</g>\n\n"); |
| |
| if (kcount) { |
| svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 28.0)); |
| svg_do_initcall(0); |
| svg("</g>\n\n"); |
| } |
| |
| svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 28.0) + ksize); |
| svg_ps_bars(); |
| svg("</g>\n\n"); |
| |
| svg("<g transform=\"translate(10, 0)\">\n"); |
| svg_title(build); |
| svg("</g>\n\n"); |
| |
| svg("<g transform=\"translate(10,200)\">\n"); |
| svg_top_ten_cpu(); |
| svg("</g>\n\n"); |
| |
| if (arg_entropy) { |
| svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 28.0) + ksize + psize); |
| svg_entropy_bar(); |
| svg("</g>\n\n"); |
| } |
| |
| if (arg_pss) { |
| svg("<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * 28.0) + ksize + psize + esize); |
| svg_pss_graph(); |
| svg("</g>\n\n"); |
| |
| svg("<g transform=\"translate(410,200)\">\n"); |
| svg_top_ten_pss(); |
| svg("</g>\n\n"); |
| } |
| |
| /* svg footer */ |
| svg("\n</svg>\n"); |
| } |