1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2009-2013 Intel Coproration
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/>.
27 Many thanks to those who contributed ideas and code:
28 - Ziga Mahkovec - Original bootchart author
29 - Anders Norgaard - PyBootchartgui
30 - Michael Meeks - bootchart2
31 - Scott James Remnant - Ubuntu C-based logger
32 - Arjan van der Ven - for the idea to merge bootgraph.pl functionality
37 #include <sys/types.h>
38 #include <sys/resource.h>
51 #include <systemd/sd-journal.h>
56 #include "conf-parser.h"
58 #include "path-util.h"
61 #include "bootchart.h"
65 double sampletime[MAXSAMPLES];
66 struct ps_struct *ps_first;
67 struct block_stat_struct blockstat[MAXSAMPLES];
68 int entropy_avail[MAXSAMPLES];
69 struct cpu_stat_struct cpustat[MAXCPUS];
75 static int exiting = 0;
79 bool arg_entropy = false;
81 bool arg_relative = false;
82 bool arg_filter = true;
83 bool arg_show_cmdline = false;
86 int arg_samples_len = 500; /* we record len+1 (1 start sample) */
87 double arg_hz = 25.0; /* 20 seconds log time */
88 double arg_scale_x = 100.0; /* 100px = 1sec */
89 double arg_scale_y = 20.0; /* 16px = 1 process bar */
91 char arg_init_path[PATH_MAX] = "/sbin/init";
92 char arg_output_path[PATH_MAX] = "/run/log";
94 static void signal_handler(int sig) {
100 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
102 #define BOOTCHART_MAX (16*1024*1024)
104 static void parse_conf(void) {
105 char *init = NULL, *output = NULL;
106 const ConfigTableItem items[] = {
107 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
108 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
109 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
110 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
111 { "Bootchart", "Output", config_parse_path, 0, &output },
112 { "Bootchart", "Init", config_parse_path, 0, &init },
113 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
114 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
115 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
116 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
117 { NULL, NULL, NULL, 0, NULL }
119 _cleanup_fclose_ FILE *f;
122 f = fopen(BOOTCHART_CONF, "re");
126 r = config_parse(NULL, BOOTCHART_CONF, f,
127 NULL, config_item_table_lookup, (void*) items, true, NULL);
129 log_warning("Failed to parse configuration file: %s", strerror(-r));
132 strscpy(arg_init_path, sizeof(arg_init_path), init);
134 strscpy(arg_output_path, sizeof(arg_output_path), output);
137 static int parse_args(int argc, char *argv[]) {
138 static struct option options[] = {
139 {"rel", no_argument, NULL, 'r'},
140 {"freq", required_argument, NULL, 'f'},
141 {"samples", required_argument, NULL, 'n'},
142 {"pss", no_argument, NULL, 'p'},
143 {"output", required_argument, NULL, 'o'},
144 {"init", required_argument, NULL, 'i'},
145 {"no-filter", no_argument, NULL, 'F'},
146 {"cmdline", no_argument, NULL, 'C'},
147 {"help", no_argument, NULL, 'h'},
148 {"scale-x", required_argument, NULL, 'x'},
149 {"scale-y", required_argument, NULL, 'y'},
150 {"entropy", no_argument, NULL, 'e'},
155 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FChx:y:", options, NULL)) >= 0) {
163 r = safe_atod(optarg, &arg_hz);
165 log_warning("failed to parse --freq/-f argument '%s': %s",
166 optarg, strerror(-r));
172 arg_show_cmdline = true;
175 r = safe_atoi(optarg, &arg_samples_len);
177 log_warning("failed to parse --samples/-n argument '%s': %s",
178 optarg, strerror(-r));
181 path_kill_slashes(optarg);
182 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
185 path_kill_slashes(optarg);
186 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
192 r = safe_atod(optarg, &arg_scale_x);
194 log_warning("failed to parse --scale-x/-x argument '%s': %s",
195 optarg, strerror(-r));
198 r = safe_atod(optarg, &arg_scale_y);
200 log_warning("failed to parse --scale-y/-y argument '%s': %s",
201 optarg, strerror(-r));
207 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
208 fprintf(stderr, " --rel, -r Record time relative to recording\n");
209 fprintf(stderr, " --freq, -f f Sample frequency [%f]\n", arg_hz);
210 fprintf(stderr, " --samples, -n N Stop sampling at [%d] samples\n", arg_samples_len);
211 fprintf(stderr, " --scale-x, -x N Scale the graph horizontally [%f] \n", arg_scale_x);
212 fprintf(stderr, " --scale-y, -y N Scale the graph vertically [%f] \n", arg_scale_y);
213 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
214 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
215 fprintf(stderr, " --output, -o [PATH] Path to output files [%s]\n", arg_output_path);
216 fprintf(stderr, " --init, -i [PATH] Path to init executable [%s]\n", arg_init_path);
217 fprintf(stderr, " --no-filter, -F Disable filtering of processes from the graph\n");
218 fprintf(stderr, " that are of less importance or short-lived\n");
219 fprintf(stderr, " --cmdline, -C Display the full command line with arguments\n");
220 fprintf(stderr, " of processes, instead of only the process name\n");
221 fprintf(stderr, " --help, -h Display this message\n");
222 fprintf(stderr, "See bootchart.conf for more information.\n");
230 if (arg_samples_len > MAXSAMPLES) {
231 fprintf(stderr, "Error: samples exceeds maximum\n");
236 fprintf(stderr, "Error: Frequency needs to be > 0\n");
243 static void do_journal_append(char *file)
245 struct iovec iovec[5];
248 char _cleanup_free_ *bootchart_file = NULL, *bootchart_message = NULL,
251 bootchart_file = strappend("BOOTCHART_FILE=", file);
253 IOVEC_SET_STRING(iovec[j++], bootchart_file);
255 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
256 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
257 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
258 if (bootchart_message)
259 IOVEC_SET_STRING(iovec[j++], bootchart_message);
261 p = malloc(9 + BOOTCHART_MAX);
267 memcpy(p, "BOOTCHART=", 10);
269 f = open(file, O_RDONLY);
271 log_error("Failed to read bootchart data: %s\n", strerror(-errno));
274 n = loop_read(f, p + 10, BOOTCHART_MAX, false);
276 log_error("Failed to read bootchart data: %s\n", strerror(-n));
282 iovec[j].iov_base = p;
283 iovec[j].iov_len = 10 + n;
286 r = sd_journal_sendv(iovec, j);
288 log_error("Failed to send bootchart: %s", strerror(-r));
291 int main(int argc, char *argv[]) {
292 _cleanup_free_ char *build = NULL;
293 struct sigaction sig = {
294 .sa_handler = signal_handler,
296 struct ps_struct *ps;
297 char output_file[PATH_MAX];
305 r = parse_args(argc, argv);
310 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
312 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
318 execl(arg_init_path, arg_init_path, NULL);
323 rlim.rlim_cur = 4096;
324 rlim.rlim_max = 4096;
325 (void) setrlimit(RLIMIT_NOFILE, &rlim);
327 /* start with empty ps LL */
328 ps_first = calloc(1, sizeof(struct ps_struct));
330 perror("calloc(ps_struct)");
334 /* handle TERM/INT nicely */
335 sigaction(SIGHUP, &sig, NULL);
337 interval = (1.0 / arg_hz) * 1000000000.0;
341 /* main program loop */
342 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
351 sampletime[samples] = gettime_ns();
353 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
355 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
356 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
357 of = fopen(output_file, "w");
361 sysfd = open("/sys", O_RDONLY);
364 parse_env_file("/etc/os-release", NEWLINE,
365 "PRETTY_NAME", &build,
368 /* wait for /proc to become available, discarding samples */
369 if (graph_start <= 0.0)
374 sample_stop = gettime_ns();
376 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
377 timeleft = interval - elapsed;
379 newint_s = (time_t)(timeleft / 1000000000.0);
380 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
383 * check if we have not consumed our entire timeslice. If we
384 * do, don't sleep and take a new sample right away.
385 * we'll lose all the missed samples and overrun our total
388 if (newint_ns > 0 || newint_s > 0) {
389 req.tv_sec = newint_s;
390 req.tv_nsec = newint_ns;
392 res = nanosleep(&req, NULL);
394 if (errno == EINTR) {
395 /* caught signal, probably HUP! */
398 perror("nanosleep()");
403 /* calculate how many samples we lost and scrap them */
404 arg_samples_len -= (int)(newint_ns / interval);
408 /* do some cleanup, close fd's */
410 while (ps->next_ps) {
413 close(ps->schedstat);
422 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
423 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
424 of = fopen(output_file, "w");
428 fprintf(stderr, "opening output file '%s': %m\n", output_file);
434 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
436 do_journal_append(output_file);
445 /* nitpic cleanups */
447 while (ps->next_ps) {
448 struct ps_struct *old = ps;
456 /* don't complain when overrun once, happens most commonly on 1st sample */
458 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);