diff options
author | Daniel Mack <daniel@zonque.org> | 2016-02-12 15:03:51 +0100 |
---|---|---|
committer | Daniel Mack <daniel@zonque.org> | 2016-02-23 13:30:09 +0100 |
commit | 232c84b2d22f2d96982b3c0390d29498bb430835 (patch) | |
tree | 168cae109aac24856fbaf3a91db7bd35067ee7d1 /src/bootchart/svg.c | |
parent | a9ae0709416fe498de85744ce639ba6a9fc50105 (diff) |
Remove systemd-bootchart
This commit rips out systemd-bootchart. It will be given a new home, outside
of the systemd repository. The code itself isn't actually specific to
systemd and can be used without systemd even, so let's put it somewhere
else.
Diffstat (limited to 'src/bootchart/svg.c')
-rw-r--r-- | src/bootchart/svg.c | 1375 |
1 files changed, 0 insertions, 1375 deletions
diff --git a/src/bootchart/svg.c b/src/bootchart/svg.c deleted file mode 100644 index f2af535061..0000000000 --- a/src/bootchart/svg.c +++ /dev/null @@ -1,1375 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - 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 <fcntl.h> -#include <limits.h> -#include <stdio.h> -#include <string.h> -#include <sys/utsname.h> -#include <time.h> -#include <unistd.h> - -#include "alloc-util.h" -#include "architecture.h" -#include "bootchart.h" -#include "fd-util.h" -#include "fileio.h" -#include "list.h" -#include "macro.h" -#include "stdio-util.h" -#include "store.h" -#include "svg.h" -#include "utf8.h" -#include "util.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)) - -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 double psize = 0; -static double ksize = 0; -static double esize = 0; -static struct list_sample_data *sampledata; -static struct list_sample_data *prev_sampledata; - -static void svg_header(FILE *of, struct list_sample_data *head, double graph_start, int n_cpus) { - double w; - double h; - struct list_sample_data *sampledata_last; - - assert(head); - - sampledata_last = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - sampledata_last = sampledata; - } - - /* min width is about 1600px due to the label */ - w = 150.0 + 10.0 + time_to_graph(sampledata_last->sampletime - 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 + (n_cpus * 15 * arg_scale_y); - - fprintf(of, "<?xml version=\"1.0\" standalone=\"no\"?>\n"); - fprintf(of, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "); - fprintf(of, "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); - - //fprintf(of, "<g transform=\"translate(10,%d)\">\n", 1000 + 150 + (pcount * 20)); - fprintf(of, "<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" ", w, h); - fprintf(of, "xmlns=\"http://www.w3.org/2000/svg\">\n\n"); - - /* write some basic info as a comment, including some help */ - fprintf(of, "<!-- This file is a bootchart SVG file. It is best rendered in a browser -->\n"); - fprintf(of, "<!-- such as Chrome, Chromium, or Firefox. Other applications that -->\n"); - fprintf(of, "<!-- render these files properly but more slowly are ImageMagick, gimp, -->\n"); - fprintf(of, "<!-- inkscape, etc. To display the files on your system, just point -->\n"); - fprintf(of, "<!-- your browser to file:///run/log/ and click. This bootchart was -->\n\n"); - - fprintf(of, "<!-- generated by bootchart version %s, running with options: -->\n", VERSION); - fprintf(of, "<!-- hz=\"%f\" n=\"%d\" -->\n", arg_hz, arg_samples_len); - fprintf(of, "<!-- x=\"%f\" y=\"%f\" -->\n", arg_scale_x, arg_scale_y); - fprintf(of, "<!-- rel=\"%d\" f=\"%d\" -->\n", arg_relative, arg_filter); - fprintf(of, "<!-- p=\"%d\" e=\"%d\" -->\n", arg_pss, arg_entropy); - fprintf(of, "<!-- o=\"%s\" i=\"%s\" -->\n\n", arg_output_path, arg_init_path); - - /* style sheet */ - fprintf(of, "<defs>\n <style type=\"text/css\">\n <![CDATA[\n"); - - fprintf(of, " rect { stroke-width: 1; }\n"); - fprintf(of, " rect.bg { fill: rgb(255,255,255); }\n"); - fprintf(of, " rect.cpu { fill: rgb(64,64,240); stroke-width: 0; fill-opacity: 0.7; }\n"); - fprintf(of, " rect.wait { fill: rgb(240,240,0); stroke-width: 0; fill-opacity: 0.7; }\n"); - fprintf(of, " rect.bi { fill: rgb(240,128,128); stroke-width: 0; fill-opacity: 0.7; }\n"); - fprintf(of, " rect.bo { fill: rgb(192,64,64); stroke-width: 0; fill-opacity: 0.7; }\n"); - fprintf(of, " rect.ps { fill: rgb(192,192,192); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n"); - fprintf(of, " rect.krnl { fill: rgb(240,240,0); stroke: rgb(128,128,128); fill-opacity: 0.7; }\n"); - fprintf(of, " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"); - fprintf(of, " rect.clrw { stroke-width: 0; fill-opacity: 0.7;}\n"); - fprintf(of, " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"); - fprintf(of, "// line.sec1 { }\n"); - fprintf(of, " line.sec5 { stroke-width: 2; }\n"); - fprintf(of, " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"); - fprintf(of, " line.dot { stroke-dasharray: 2 4; }\n"); - fprintf(of, " line.idle { stroke: rgb(64,64,64); stroke-dasharray: 10 6; stroke-opacity: 0.7; }\n"); - - fprintf(of, " .run { font-size: 8; font-style: italic; }\n"); - fprintf(of, " text { font-family: Verdana, Helvetica; font-size: 10; }\n"); - fprintf(of, " text.sec { font-size: 8; }\n"); - fprintf(of, " text.t1 { font-size: 24; }\n"); - fprintf(of, " text.t2 { font-size: 12; }\n"); - fprintf(of, " text.idle { font-size: 18; }\n"); - - fprintf(of, " ]]>\n </style>\n</defs>\n\n"); -} - -static int svg_title(FILE *of, const char *build, int pscount, double log_start, int overrun) { - _cleanup_free_ char *cmdline = NULL; - _cleanup_free_ char *model = NULL; - _cleanup_free_ char *buf = NULL; - char date[256] = "Unknown"; - const char *cpu; - char *c; - time_t t; - int r; - struct utsname uts; - - r = read_one_line_file("/proc/cmdline", &cmdline); - if (r < 0) { - log_error_errno(r, "Unable to read cmdline: %m"); - return r; - } - - /* 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) { - char rootbdev[4]; - char filename[32]; - - strncpy(rootbdev, &c[10], sizeof(rootbdev) - 1); - rootbdev[3] = '\0'; - xsprintf(filename, "/sys/block/%s/device/model", rootbdev); - - r = read_one_line_file(filename, &model); - if (r < 0) - log_info("Error reading disk model for %s: %m\n", rootbdev); - } - - /* various utsname parameters */ - r = uname(&uts); - if (r < 0) { - log_error("Error getting uname info\n"); - return -errno; - } - - /* date */ - t = time(NULL); - r = strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&t)); - assert_se(r > 0); - - /* CPU type */ - r = get_proc_field("/proc/cpuinfo", PROC_CPUINFO_MODEL, "\n", &buf); - if (r < 0) - cpu = "Unknown"; - else - cpu = buf; - - fprintf(of, "<text class=\"t1\" x=\"0\" y=\"30\">Bootchart for %s - %s</text>\n", - uts.nodename, date); - fprintf(of, "<text class=\"t2\" x=\"20\" y=\"50\">System: %s %s %s %s</text>\n", - uts.sysname, uts.release, uts.version, uts.machine); - fprintf(of, "<text class=\"t2\" x=\"20\" y=\"65\">CPU: %s</text>\n", cpu); - if (model) - fprintf(of, "<text class=\"t2\" x=\"20\" y=\"80\">Disk: %s</text>\n", model); - fprintf(of, "<text class=\"t2\" x=\"20\" y=\"95\">Boot options: %s</text>\n", cmdline); - fprintf(of, "<text class=\"t2\" x=\"20\" y=\"110\">Build: %s</text>\n", build); - fprintf(of, "<text class=\"t2\" x=\"20\" y=\"125\">Log start time: %.03fs</text>\n", log_start); - fprintf(of, "<text class=\"t2\" x=\"20\" y=\"140\">Idle time: "); - - if (idletime >= 0.0) - fprintf(of, "%.03fs", idletime); - else - fprintf(of, "Not detected"); - - fprintf(of, "</text>\n"); - 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", - arg_hz, arg_samples_len, overrun, pscount, pfiltered); - - return 0; -} - -static void svg_graph_box(FILE *of, struct list_sample_data *head, int height, double graph_start) { - double d = 0.0; - int i = 0; - double finalsample = 0.0; - struct list_sample_data *sampledata_last; - - sampledata_last = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - sampledata_last = sampledata; - } - - finalsample = sampledata_last->sampletime; - - /* outside box, fill */ - fprintf(of, "<rect class=\"box\" x=\"%.03f\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n", - time_to_graph(0.0), - time_to_graph(finalsample - graph_start), - ps_to_graph(height)); - - for (d = graph_start; d <= finalsample; - 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) - fprintf(of, " <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) - fprintf(of, " <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 - fprintf(of, " <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) - fprintf(of, " <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(FILE *of, - struct list_sample_data *head, - struct ps_struct *ps_first, - double graph_start) { - struct ps_struct *ps; - int i; - struct list_sample_data *sampledata_last; - - sampledata_last = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - sampledata_last = sampledata; - } - - - fprintf(of, "\n\n<!-- Pss memory size graph -->\n"); - - fprintf(of, "\n <text class=\"t2\" x=\"5\" y=\"-15\">Memory allocation - Pss</text>\n"); - - /* vsize 1000 == 1000mb */ - svg_graph_box(of, head, 100, graph_start); - /* draw some hlines for usable memory sizes */ - for (i = 100000; i < 1000000; i += 100000) { - fprintf(of, " <line class=\"sec01\" x1=\"%.03f\" y1=\"%.0f\" x2=\"%.03f\" y2=\"%.0f\"/>\n", - time_to_graph(.0), - kb_to_graph(i), - time_to_graph(sampledata_last->sampletime - graph_start), - kb_to_graph(i)); - fprintf(of, " <text class=\"sec\" x=\"%.03f\" y=\"%.0f\">%dM</text>\n", - time_to_graph(sampledata_last->sampletime - graph_start) + 5, - kb_to_graph(i), (1000000 - i) / 1000); - } - fprintf(of, "\n"); - - /* now plot the graph itself */ - i = 1; - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int bottom; - int top; - struct ps_sched_struct *cross_place; - - 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; - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - if (ps->sample->sampledata == sampledata) { - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - break; - } - } - while (ps->sample->cross) { - cross_place = ps->sample->cross; - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - } - - fprintf(of, " <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - "rgb(64,64,64)", - time_to_graph(prev_sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - top), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - 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; - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - /* don't draw anything smaller than 2mb */ - if (ps->sample->sampledata != sampledata) - continue; - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - fprintf(of, " <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - colorwheel[ps->pid % 12], - time_to_graph(prev_sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - top), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - kb_to_graph(top - bottom)); - bottom = top; - } - break; - } - - while ((cross_place = ps->sample->cross)) { - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - fprintf(of, " <rect class=\"clrw\" style=\"fill: %s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - colorwheel[ps->pid % 12], - time_to_graph(prev_sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - top), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - kb_to_graph(top - bottom)); - bottom = top; - } - } - - prev_sampledata = sampledata; - i++; - } - - /* overlay all the text labels */ - i = 1; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int bottom; - int top = 0; - struct ps_sched_struct *prev_sample; - struct ps_sched_struct *cross_place; - - /* put all the small pss blocks into the bottom */ - ps = ps_first->next_ps; - while (ps->next_ps) { - ps = ps->next_ps; - if (!ps) - continue; - - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - - if (ps->sample->sampledata == sampledata) { - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - - break; - } - } - - while ((cross_place = ps->sample->cross)) { - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - } - bottom = top; - - /* now plot the ones that are of significant size */ - ps = ps_first; - while (ps->next_ps) { - prev_sample = ps->sample; - ps = ps->next_ps; - if (!ps) - continue; - ps->sample = ps->first; - while (ps->sample->next) { - prev_sample = ps->sample; - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - /* don't draw anything smaller than 2mb */ - if (ps->sample->sampledata == sampledata) { - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - /* draw a label with the process / PID */ - if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) - fprintf(of, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n", - time_to_graph(sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), - ps->name, ps->pid); - bottom = top; - } - break; - } - } - while ((cross_place = ps->sample->cross)) { - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - prev_sample = ps->sample->prev; - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - /* draw a label with the process / PID */ - if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) - fprintf(of, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]</text>\n", - time_to_graph(sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), - ps->name, ps->pid); - bottom = top; - } - } - - i++; - } - - /* debug output - full data dump */ - fprintf(of, "\n\n<!-- PSS map - csv format -->\n"); - ps = ps_first; - while (ps->next_ps) { - _cleanup_free_ char *enc_name = NULL; - ps = ps->next_ps; - if (!ps) - continue; - - enc_name = xml_comment_encode(ps->name); - if (!enc_name) - continue; - - fprintf(of, "<!-- %s [%d] pss=", enc_name, ps->pid); - - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - fprintf(of, "%d," , ps->sample->pss); - } - - fprintf(of, " -->\n"); - } - -} - -static void svg_io_bi_bar(FILE *of, - struct list_sample_data *head, - int n_samples, - double graph_start, - double interval) { - - double max = 0.0; - double range; - int max_here = 0; - int i; - int k; - struct list_sample_data *start_sampledata; - struct list_sample_data *stop_sampledata; - - fprintf(of, "<!-- IO utilization graph - In -->\n"); - fprintf(of, "<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(of, head, 5, graph_start); - - /* find the max IO first */ - i = 1; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start; - int stop; - int diff; - double tot; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples - 1); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < ((range/2) - 1) && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; - - if (tot > max) { - max = tot; - max_here = i; - } - - tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; - - if (tot > max) - max = tot; - - i++; - } - - /* plot bi */ - i = 1; - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start; - int stop; - int diff; - double tot; - double pbi = 0; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; - - if (max > 0) - pbi = tot / max; - - if (pbi > 0.001) - fprintf(of, "<rect class=\"bi\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - (arg_scale_y * 5) - (pbi * (arg_scale_y * 5)), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - pbi * (arg_scale_y * 5)); - - /* labels around highest value */ - if (i == max_here) - fprintf(of, " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n", - time_to_graph(sampledata->sampletime - graph_start) + 5, - ((arg_scale_y * 5) - (pbi * (arg_scale_y * 5))) + 15, - max / 1024.0 / (interval / 1000000000.0)); - - i++; - prev_sampledata = sampledata; - } -} - -static void svg_io_bo_bar(FILE *of, - struct list_sample_data *head, - int n_samples, - double graph_start, - double interval) { - double max = 0.0; - double range; - int max_here = 0; - int i; - int k; - struct list_sample_data *start_sampledata; - struct list_sample_data *stop_sampledata; - - fprintf(of, "<!-- IO utilization graph - out -->\n"); - fprintf(of, "<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(of, head, 5, graph_start); - - /* find the max IO first */ - i = 0; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start; - int stop; - int diff; - double tot; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples - 1); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < (range/2) - 1 && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; - if (tot > max) - max = tot; - - tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; - if (tot > max) { - max = tot; - max_here = i; - } - - i++; - } - - /* plot bo */ - prev_sampledata = head; - i = 1; - - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start, stop, diff; - double tot, pbo; - - pbo = 0; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) - / diff; - - if (max > 0) - pbo = tot / max; - - if (pbo > 0.001) - fprintf(of, "<rect class=\"bo\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - (arg_scale_y * 5) - (pbo * (arg_scale_y * 5)), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - pbo * (arg_scale_y * 5)); - - /* labels around highest bo value */ - if (i == max_here) - fprintf(of, " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\">%0.2fmb/sec</text>\n", - time_to_graph(sampledata->sampletime - graph_start) + 5, - ((arg_scale_y * 5) - (pbo * (arg_scale_y * 5))), - max / 1024.0 / (interval / 1000000000.0)); - - i++; - prev_sampledata = sampledata; - } -} - -static void svg_cpu_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { - - fprintf(of, "<!-- CPU utilization graph -->\n"); - - if (cpu_num < 0) - fprintf(of, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] utilization</text>\n"); - else - fprintf(of, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] utilization</text>\n", cpu_num); - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* bars for each sample, proportional to the CPU util. */ - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int c; - double trt; - double ptrt; - - ptrt = trt = 0.0; - - if (cpu_num < 0) - for (c = 0; c < n_cpus; c++) - trt += sampledata->runtime[c] - prev_sampledata->runtime[c]; - else - trt = sampledata->runtime[cpu_num] - prev_sampledata->runtime[cpu_num]; - - trt = trt / 1000000000.0; - - if (cpu_num < 0) - trt = trt / (double)n_cpus; - - if (trt > 0.0) - ptrt = trt / (sampledata->sampletime - prev_sampledata->sampletime); - - if (ptrt > 1.0) - ptrt = 1.0; - - if (ptrt > 0.001) - fprintf(of, "<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - (arg_scale_y * 5) - (ptrt * (arg_scale_y * 5)), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - ptrt * (arg_scale_y * 5)); - - prev_sampledata = sampledata; - } -} - -static void svg_wait_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { - - fprintf(of, "<!-- Wait time aggregation box -->\n"); - - if (cpu_num < 0) - fprintf(of, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[overall] wait</text>\n"); - else - fprintf(of, "<text class=\"t2\" x=\"5\" y=\"-15\">CPU[%d] wait</text>\n", cpu_num); - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* bars for each sample, proportional to the CPU util. */ - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int c; - double twt; - double ptwt; - - ptwt = twt = 0.0; - - if (cpu_num < 0) - for (c = 0; c < n_cpus; c++) - twt += sampledata->waittime[c] - prev_sampledata->waittime[c]; - else - twt = sampledata->waittime[cpu_num] - prev_sampledata->waittime[cpu_num]; - - twt = twt / 1000000000.0; - - if (cpu_num < 0) - twt = twt / (double)n_cpus; - - if (twt > 0.0) - ptwt = twt / (sampledata->sampletime - prev_sampledata->sampletime); - - if (ptwt > 1.0) - ptwt = 1.0; - - if (ptwt > 0.001) - fprintf(of, "<rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - ((arg_scale_y * 5) - (ptwt * (arg_scale_y * 5))), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - ptwt * (arg_scale_y * 5)); - - prev_sampledata = sampledata; - } -} - -static void svg_entropy_bar(FILE *of, struct list_sample_data *head, double graph_start) { - - fprintf(of, "<!-- entropy pool graph -->\n"); - - fprintf(of, "<text class=\"t2\" x=\"5\" y=\"-15\">Entropy pool size</text>\n"); - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* bars for each sample, scale 0-4096 */ - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - fprintf(of, "<rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - ((arg_scale_y * 5) - ((sampledata->entropy_avail / 4096.) * (arg_scale_y * 5))), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - (sampledata->entropy_avail / 4096.) * (arg_scale_y * 5)); - prev_sampledata = sampledata; - } -} - -static struct ps_struct *get_next_ps(struct ps_struct *ps, struct ps_struct *ps_first) { - /* - * 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 */ - for (;;) { - if (ps->parent && ps->parent->next) - return ps->parent->next; - - ps = ps->parent; - if (!ps) - return ps; - } - - return NULL; -} - -static bool ps_filter(struct ps_struct *ps) { - if (!arg_filter) - return false; - - /* can't draw data when there is only 1 sample (need start + stop) */ - if (ps->first == ps->last) - return true; - - /* don't filter kthreadd */ - if (ps->pid == 2) - return false; - - /* drop stuff that doesn't use any real CPU time */ - if (ps->total <= 0.001) - return true; - - return 0; -} - -static void svg_do_initcall(FILE *of, struct list_sample_data *head, int count_only, double graph_start) { - _cleanup_pclose_ FILE *f = NULL; - double t; - char func[256]; - int ret; - int usecs; - - /* can't plot initcall when disabled or in relative mode */ - if (!arg_initcall || arg_relative) { - kcount = 0; - return; - } - - if (!count_only) { - fprintf(of, "<!-- initcall -->\n"); - fprintf(of, "<text class=\"t2\" x=\"5\" y=\"-15\">Kernel init threads</text>\n"); - /* surrounding box */ - svg_graph_box(of, head, kcount, graph_start); - } - - 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; - } - - fprintf(of, "<!-- thread=\"%s\" time=\"%.3f\" elapsed=\"%d\" result=\"%d\" -->\n", - func, t, usecs, ret); - - if (usecs < 1000) - continue; - - /* rect */ - fprintf(of, " <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 */ - fprintf(of, " <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(FILE *of, - struct list_sample_data *head, - int n_samples, - int n_cpus, - struct ps_struct *ps_first, - double graph_start, - double interval) { - - struct ps_struct *ps; - int i = 0; - int j = 0; - int pid; - double w = 0.0; - - fprintf(of, "<!-- Process graph -->\n"); - fprintf(of, "<text class=\"t2\" x=\"5\" y=\"-15\">Processes</text>\n"); - - /* surrounding box */ - svg_graph_box(of, head, pcount, graph_start); - - /* pass 2 - ps boxes */ - ps = ps_first; - while ((ps = get_next_ps(ps, ps_first))) { - _cleanup_free_ char *enc_name = NULL, *escaped = NULL; - double endtime; - double starttime; - int t; - - if (!utf8_is_printable(ps->name, strlen(ps->name))) - escaped = utf8_escape_non_printable(ps->name); - - enc_name = xml_comment_encode(escaped ? escaped : ps->name); - if (!enc_name) - continue; - - /* leave some trace of what we actually filtered etc. */ - fprintf(of, "<!-- %s [%i] ppid=%i runtime=%.03fs -->\n", enc_name, ps->pid, - ps->ppid, ps->total); - - starttime = ps->first->sampledata->sampletime; - - 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 if (ps->parent){ - /* 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)) - fprintf(of, " <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; - } - - endtime = ps->last->sampledata->sampletime; - fprintf(of, " <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(ps->last->sampledata->sampletime - starttime), - ps_to_graph(1)); - - /* paint cpu load over these */ - ps->sample = ps->first; - t = 1; - while (ps->sample->next) { - double rt, prt; - double wt, wrt; - struct ps_sched_struct *prev; - - prev = ps->sample; - ps->sample = ps->sample->next; - - /* calculate over interval */ - rt = ps->sample->runtime - prev->runtime; - wt = ps->sample->waittime - prev->waittime; - - prt = (rt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); - wrt = (wt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); - - /* 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; - - fprintf(of, " <rect class=\"wait\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - time_to_graph(prev->sampledata->sampletime - graph_start), - ps_to_graph(j), - time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), - ps_to_graph(wrt)); - - /* draw cpu over wait - TODO figure out how/why run + wait > interval */ - fprintf(of, " <rect class=\"cpu\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", - time_to_graph(prev->sampledata->sampletime - graph_start), - ps_to_graph(j + (1.0 - prt)), - time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), - ps_to_graph(prt)); - t++; - } - - /* determine where to display the process name */ - if ((endtime - starttime) < 1.5) - /* too small to fit label inside the box */ - w = endtime; - else - w = starttime; - - /* text label of process name */ - fprintf(of, " <text x=\"%.03f\" y=\"%.03f\"><![CDATA[%s]]> [%i]<tspan class=\"run\">%.03fs</tspan> %s</text>\n", - time_to_graph(w - graph_start) + 5.0, - ps_to_graph(j) + 14.0, - escaped ? escaped : ps->name, - ps->pid, - (ps->last->runtime - ps->first->runtime) / 1000000000.0, - arg_show_cgroup ? ps->cgroup : ""); - /* paint lines to the parent process */ - if (ps->parent) { - /* horizontal part */ - fprintf(of, " <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) - fprintf(of, " <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 */ - - fprintf(of, "\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; - } - - /* need to know last node first */ - ps->sample = ps->first; - i = ps->sample->next->sampledata->counter; - - while (ps->sample->next && i<(n_samples-(arg_hz/2))) { - double crt; - double brt; - int c; - int ii; - struct ps_sched_struct *sample_hz; - - ps->sample = ps->sample->next; - sample_hz = ps->sample; - for (ii = 0; (ii < (int)arg_hz/2) && sample_hz->next; ii++) - sample_hz = sample_hz->next; - - /* subtract bootchart cpu utilization from total */ - crt = 0.0; - for (c = 0; c < n_cpus; c++) - crt += sample_hz->sampledata->runtime[c] - ps->sample->sampledata->runtime[c]; - - brt = sample_hz->runtime - ps->sample->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 = ps->sample->sampledata->sampletime - graph_start; - fprintf(of, "\n<!-- idle detected at %.03f seconds -->\n", idletime); - fprintf(of, "<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); - fprintf(of, "<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; - } - - i++; - } -} - -static void svg_top_ten_cpu(FILE *of, struct ps_struct *ps_first) { - struct ps_struct *top[10]; - struct ps_struct emptyps = {}; - struct ps_struct *ps; - int n, m; - - for (n = 0; n < (int) ELEMENTSOF(top); n++) - top[n] = &emptyps; - - /* walk all ps's and setup ptrs */ - ps = ps_first; - while ((ps = get_next_ps(ps, ps_first))) { - 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; - } - } - - fprintf(of, "<text class=\"t2\" x=\"20\" y=\"0\">Top CPU consumers:</text>\n"); - for (n = 0; n < 10; n++) - fprintf(of, "<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(FILE *of, struct ps_struct *ps_first) { - struct ps_struct *top[10]; - struct ps_struct emptyps = {}; - struct ps_struct *ps; - int n, m; - - for (n = 0; n < (int) ELEMENTSOF(top); n++) - top[n] = &emptyps; - - /* walk all ps's and setup ptrs */ - ps = ps_first; - while ((ps = get_next_ps(ps, ps_first))) { - 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; - } - } - - fprintf(of, "<text class=\"t2\" x=\"20\" y=\"0\">Top PSS consumers:</text>\n"); - for (n = 0; n < 10; n++) - fprintf(of, "<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); -} - -int svg_do(FILE *of, - const char *build, - struct list_sample_data *head, - struct ps_struct *ps_first, - int n_samples, - int pscount, - int n_cpus, - double graph_start, - double log_start, - double interval, - int overrun) { - - struct ps_struct *ps; - double offset = 7; - int r, c; - - sampledata = head; - LIST_FIND_TAIL(link, sampledata, head); - ps = ps_first; - - /* count initcall thread count first */ - svg_do_initcall(of, head, 1, graph_start); - ksize = kcount ? ps_to_graph(kcount) + (arg_scale_y * 2) : 0; - - /* then count processes */ - while ((ps = get_next_ps(ps, ps_first))) { - 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(of, head, graph_start, arg_percpu ? n_cpus : 0); - fprintf(of, "<rect class=\"bg\" width=\"100%%\" height=\"100%%\" />\n\n"); - - fprintf(of, "<g transform=\"translate(10,400)\">\n"); - svg_io_bi_bar(of, head, n_samples, graph_start, interval); - fprintf(of, "</g>\n\n"); - - fprintf(of, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset)); - svg_io_bo_bar(of, head, n_samples, graph_start, interval); - fprintf(of, "</g>\n\n"); - - for (c = -1; c < (arg_percpu ? n_cpus : 0); c++) { - offset += 7; - fprintf(of, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset)); - svg_cpu_bar(of, head, n_cpus, c, graph_start); - fprintf(of, "</g>\n\n"); - - offset += 7; - fprintf(of, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset)); - svg_wait_bar(of, head, n_cpus, c, graph_start); - fprintf(of, "</g>\n\n"); - } - - if (kcount) { - offset += 7; - fprintf(of, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset)); - svg_do_initcall(of, head, 0, graph_start); - fprintf(of, "</g>\n\n"); - } - - offset += 7; - fprintf(of, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset) + ksize); - svg_ps_bars(of, head, n_samples, n_cpus, ps_first, graph_start, interval); - fprintf(of, "</g>\n\n"); - - fprintf(of, "<g transform=\"translate(10, 0)\">\n"); - r = svg_title(of, build, pscount, log_start, overrun); - fprintf(of, "</g>\n\n"); - - if (r < 0) - return r; - - fprintf(of, "<g transform=\"translate(10,200)\">\n"); - svg_top_ten_cpu(of, ps_first); - fprintf(of, "</g>\n\n"); - - if (arg_entropy) { - fprintf(of, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset) + ksize + psize); - svg_entropy_bar(of, head, graph_start); - fprintf(of, "</g>\n\n"); - } - - if (arg_pss) { - fprintf(of, "<g transform=\"translate(10,%.03f)\">\n", 400.0 + (arg_scale_y * offset) + ksize + psize + esize); - svg_pss_graph(of, head, ps_first, graph_start); - fprintf(of, "</g>\n\n"); - - fprintf(of, "<g transform=\"translate(410,200)\">\n"); - svg_top_ten_pss(of, ps_first); - fprintf(of, "</g>\n\n"); - } - - /* fprintf footer */ - fprintf(of, "\n</svg>\n"); - - return 0; -} |