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/>.
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"
66 struct ps_struct *ps_first;
72 static int exiting = 0;
76 bool arg_entropy = false;
78 bool arg_relative = false;
79 bool arg_filter = true;
80 bool arg_show_cmdline = false;
83 int arg_samples_len = 500; /* we record len+1 (1 start sample) */
84 double arg_hz = 25.0; /* 20 seconds log time */
85 double arg_scale_x = 100.0; /* 100px = 1sec */
86 double arg_scale_y = 20.0; /* 16px = 1 process bar */
87 static struct list_sample_data *sampledata;
88 struct list_sample_data *head;
90 char arg_init_path[PATH_MAX] = "/sbin/init";
91 char arg_output_path[PATH_MAX] = "/run/log";
93 static void signal_handler(int sig) {
99 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
101 #define BOOTCHART_MAX (16*1024*1024)
103 static void parse_conf(void) {
104 char *init = NULL, *output = NULL;
105 const ConfigTableItem items[] = {
106 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
107 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
108 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
109 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
110 { "Bootchart", "Output", config_parse_path, 0, &output },
111 { "Bootchart", "Init", config_parse_path, 0, &init },
112 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
113 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
114 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
115 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
116 { NULL, NULL, NULL, 0, NULL }
118 _cleanup_fclose_ FILE *f;
121 f = fopen(BOOTCHART_CONF, "re");
125 r = config_parse(NULL, BOOTCHART_CONF, f,
126 NULL, config_item_table_lookup, (void*) items, true, false, NULL);
128 log_warning("Failed to parse configuration file: %s", strerror(-r));
131 strscpy(arg_init_path, sizeof(arg_init_path), init);
133 strscpy(arg_output_path, sizeof(arg_output_path), output);
136 static int parse_args(int argc, char *argv[]) {
137 static struct option options[] = {
138 {"rel", no_argument, NULL, 'r'},
139 {"freq", required_argument, NULL, 'f'},
140 {"samples", required_argument, NULL, 'n'},
141 {"pss", no_argument, NULL, 'p'},
142 {"output", required_argument, NULL, 'o'},
143 {"init", required_argument, NULL, 'i'},
144 {"no-filter", no_argument, NULL, 'F'},
145 {"cmdline", no_argument, NULL, 'C'},
146 {"help", no_argument, NULL, 'h'},
147 {"scale-x", required_argument, NULL, 'x'},
148 {"scale-y", required_argument, NULL, 'y'},
149 {"entropy", no_argument, NULL, 'e'},
154 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FChx:y:", options, NULL)) >= 0) {
162 r = safe_atod(optarg, &arg_hz);
164 log_warning("failed to parse --freq/-f argument '%s': %s",
165 optarg, strerror(-r));
171 arg_show_cmdline = true;
174 r = safe_atoi(optarg, &arg_samples_len);
176 log_warning("failed to parse --samples/-n argument '%s': %s",
177 optarg, strerror(-r));
180 path_kill_slashes(optarg);
181 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
184 path_kill_slashes(optarg);
185 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
191 r = safe_atod(optarg, &arg_scale_x);
193 log_warning("failed to parse --scale-x/-x argument '%s': %s",
194 optarg, strerror(-r));
197 r = safe_atod(optarg, &arg_scale_y);
199 log_warning("failed to parse --scale-y/-y argument '%s': %s",
200 optarg, strerror(-r));
206 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
207 fprintf(stderr, " --rel, -r Record time relative to recording\n");
208 fprintf(stderr, " --freq, -f f Sample frequency [%f]\n", arg_hz);
209 fprintf(stderr, " --samples, -n N Stop sampling at [%d] samples\n", arg_samples_len);
210 fprintf(stderr, " --scale-x, -x N Scale the graph horizontally [%f] \n", arg_scale_x);
211 fprintf(stderr, " --scale-y, -y N Scale the graph vertically [%f] \n", arg_scale_y);
212 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
213 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
214 fprintf(stderr, " --output, -o [PATH] Path to output files [%s]\n", arg_output_path);
215 fprintf(stderr, " --init, -i [PATH] Path to init executable [%s]\n", arg_init_path);
216 fprintf(stderr, " --no-filter, -F Disable filtering of processes from the graph\n");
217 fprintf(stderr, " that are of less importance or short-lived\n");
218 fprintf(stderr, " --cmdline, -C Display the full command line with arguments\n");
219 fprintf(stderr, " of processes, instead of only the process name\n");
220 fprintf(stderr, " --help, -h Display this message\n");
221 fprintf(stderr, "See bootchart.conf for more information.\n");
229 fprintf(stderr, "Error: Frequency needs to be > 0\n");
236 static void do_journal_append(char *file) {
237 struct iovec iovec[5];
240 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
243 bootchart_file = strappend("BOOTCHART_FILE=", file);
245 IOVEC_SET_STRING(iovec[j++], bootchart_file);
247 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
248 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
249 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
250 if (bootchart_message)
251 IOVEC_SET_STRING(iovec[j++], bootchart_message);
253 p = malloc(9 + BOOTCHART_MAX);
259 memcpy(p, "BOOTCHART=", 10);
261 f = open(file, O_RDONLY|O_CLOEXEC);
263 log_error("Failed to read bootchart data: %m");
266 n = loop_read(f, p + 10, BOOTCHART_MAX, false);
268 log_error("Failed to read bootchart data: %s", strerror(-n));
274 iovec[j].iov_base = p;
275 iovec[j].iov_len = 10 + n;
278 r = sd_journal_sendv(iovec, j);
280 log_error("Failed to send bootchart: %s", strerror(-r));
283 int main(int argc, char *argv[]) {
284 _cleanup_free_ char *build = NULL;
285 struct sigaction sig = {
286 .sa_handler = signal_handler,
288 struct ps_struct *ps;
289 char output_file[PATH_MAX];
297 r = parse_args(argc, argv);
302 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
304 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
310 execl(arg_init_path, arg_init_path, NULL);
315 rlim.rlim_cur = 4096;
316 rlim.rlim_max = 4096;
317 (void) setrlimit(RLIMIT_NOFILE, &rlim);
319 /* start with empty ps LL */
320 ps_first = new0(struct ps_struct, 1);
326 /* handle TERM/INT nicely */
327 sigaction(SIGHUP, &sig, NULL);
329 interval = (1.0 / arg_hz) * 1000000000.0;
333 LIST_HEAD_INIT(head);
335 /* main program loop */
336 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
345 sampledata = new0(struct list_sample_data, 1);
346 if (sampledata == NULL) {
347 log_error("Failed to allocate memory for a node: %m");
351 sampledata->sampletime = gettime_ns();
352 sampledata->counter = samples;
354 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
356 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
357 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
358 of = fopen(output_file, "we");
362 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
365 parse_env_file("/etc/os-release", NEWLINE,
366 "PRETTY_NAME", &build,
369 /* wait for /proc to become available, discarding samples */
370 if (graph_start <= 0.0)
373 log_sample(samples, &sampledata);
375 sample_stop = gettime_ns();
377 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
378 timeleft = interval - elapsed;
380 newint_s = (time_t)(timeleft / 1000000000.0);
381 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
384 * check if we have not consumed our entire timeslice. If we
385 * do, don't sleep and take a new sample right away.
386 * we'll lose all the missed samples and overrun our total
389 if (newint_ns > 0 || newint_s > 0) {
390 req.tv_sec = newint_s;
391 req.tv_nsec = newint_ns;
393 res = nanosleep(&req, NULL);
395 if (errno == EINTR) {
396 /* caught signal, probably HUP! */
399 log_error("nanosleep() failed: %m");
404 /* calculate how many samples we lost and scrap them */
405 arg_samples_len -= (int)(newint_ns / interval);
407 LIST_PREPEND(link, head, sampledata);
410 /* do some cleanup, close fd's */
412 while (ps->next_ps) {
415 close(ps->schedstat);
424 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
425 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
426 of = fopen(output_file, "we");
430 fprintf(stderr, "opening output file '%s': %m\n", output_file);
436 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
438 do_journal_append(output_file);
447 /* nitpic cleanups */
448 ps = ps_first->next_ps;
449 while (ps->next_ps) {
450 struct ps_struct *old;
453 old->sample = ps->first;
455 while (old->sample->next) {
456 struct ps_sched_struct *oldsample = old->sample;
458 old->sample = old->sample->next;
468 while (sampledata->link_prev) {
469 struct list_sample_data *old_sampledata = sampledata;
470 sampledata = sampledata->link_prev;
471 free(old_sampledata);
474 /* don't complain when overrun once, happens most commonly on 1st sample */
476 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);