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
36 #include <sys/resource.h>
48 #include "systemd/sd-journal.h"
53 #include "conf-parser.h"
55 #include "path-util.h"
58 #include "bootchart.h"
63 struct ps_struct *ps_first;
69 static int exiting = 0;
71 #define DEFAULT_SAMPLES_LEN 500
72 #define DEFAULT_HZ 25.0
73 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
74 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
75 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
76 #define DEFAULT_OUTPUT "/run/log"
79 bool arg_entropy = false;
81 bool arg_relative = false;
82 bool arg_filter = true;
83 bool arg_show_cmdline = false;
84 bool arg_show_cgroup = false;
86 bool arg_percpu = false;
88 int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */
89 double arg_hz = DEFAULT_HZ;
90 double arg_scale_x = DEFAULT_SCALE_X;
91 double arg_scale_y = DEFAULT_SCALE_Y;
92 static struct list_sample_data *sampledata;
93 struct list_sample_data *head;
95 char arg_init_path[PATH_MAX] = DEFAULT_INIT;
96 char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT;
98 static void signal_handler(int sig) {
104 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
106 #define BOOTCHART_MAX (16*1024*1024)
108 static void parse_conf(void) {
109 char *init = NULL, *output = NULL;
110 const ConfigTableItem items[] = {
111 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
112 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
113 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
114 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
115 { "Bootchart", "Output", config_parse_path, 0, &output },
116 { "Bootchart", "Init", config_parse_path, 0, &init },
117 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
118 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
119 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
120 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
121 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
122 { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu },
123 { NULL, NULL, NULL, 0, NULL }
126 config_parse_many(BOOTCHART_CONF,
127 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
128 NULL, config_item_table_lookup, items, true, NULL);
131 strscpy(arg_init_path, sizeof(arg_init_path), init);
133 strscpy(arg_output_path, sizeof(arg_output_path), output);
136 static void help(void) {
137 printf("Usage: %s [OPTIONS]\n\n"
139 " -r --rel Record time relative to recording\n"
140 " -f --freq=FREQ Sample frequency [%g]\n"
141 " -n --samples=N Stop sampling at [%d] samples\n"
142 " -x --scale-x=N Scale the graph horizontally [%g] \n"
143 " -y --scale-y=N Scale the graph vertically [%g] \n"
144 " -p --pss Enable PSS graph (CPU intensive)\n"
145 " -e --entropy Enable the entropy_avail graph\n"
146 " -o --output=PATH Path to output files [%s]\n"
147 " -i --init=PATH Path to init executable [%s]\n"
148 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
149 " -C --cmdline Display full command lines with arguments\n"
150 " -c --control-group Display process control group\n"
151 " --per-cpu Draw each CPU utilization and wait bar also\n"
152 " -h --help Display this message\n\n"
153 "See bootchart.conf for more information.\n",
154 program_invocation_short_name,
163 static int parse_argv(int argc, char *argv[]) {
169 static const struct option options[] = {
170 {"rel", no_argument, NULL, 'r' },
171 {"freq", required_argument, NULL, 'f' },
172 {"samples", required_argument, NULL, 'n' },
173 {"pss", no_argument, NULL, 'p' },
174 {"output", required_argument, NULL, 'o' },
175 {"init", required_argument, NULL, 'i' },
176 {"no-filter", no_argument, NULL, 'F' },
177 {"cmdline", no_argument, NULL, 'C' },
178 {"control-group", no_argument, NULL, 'c' },
179 {"help", no_argument, NULL, 'h' },
180 {"scale-x", required_argument, NULL, 'x' },
181 {"scale-y", required_argument, NULL, 'y' },
182 {"entropy", no_argument, NULL, 'e' },
183 {"per-cpu", no_argument, NULL, ARG_PERCPU},
191 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
198 r = safe_atod(optarg, &arg_hz);
200 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
207 arg_show_cmdline = true;
210 arg_show_cgroup = true;
213 r = safe_atoi(optarg, &arg_samples_len);
215 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
219 path_kill_slashes(optarg);
220 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
223 path_kill_slashes(optarg);
224 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
230 r = safe_atod(optarg, &arg_scale_x);
232 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
236 r = safe_atod(optarg, &arg_scale_y);
238 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
256 assert_not_reached("Unhandled option code.");
260 log_error("Frequency needs to be > 0");
267 static void do_journal_append(char *file) {
268 struct iovec iovec[5];
271 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
273 _cleanup_close_ int fd = -1;
275 bootchart_file = strappend("BOOTCHART_FILE=", file);
277 IOVEC_SET_STRING(iovec[j++], bootchart_file);
279 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
280 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
281 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
282 if (bootchart_message)
283 IOVEC_SET_STRING(iovec[j++], bootchart_message);
285 p = malloc(9 + BOOTCHART_MAX);
291 memcpy(p, "BOOTCHART=", 10);
293 fd = open(file, O_RDONLY|O_CLOEXEC);
295 log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file);
299 n = loop_read(fd, p + 10, BOOTCHART_MAX, false);
301 log_error_errno(n, "Failed to read bootchart data: %m");
305 iovec[j].iov_base = p;
306 iovec[j].iov_len = 10 + n;
309 r = sd_journal_sendv(iovec, j);
311 log_error_errno(r, "Failed to send bootchart: %m");
314 int main(int argc, char *argv[]) {
315 _cleanup_free_ char *build = NULL;
316 _cleanup_close_ int sysfd = -1;
317 _cleanup_closedir_ DIR *proc = NULL;
318 struct sigaction sig = {
319 .sa_handler = signal_handler,
321 struct ps_struct *ps;
322 char output_file[PATH_MAX];
330 r = parse_argv(argc, argv);
338 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
340 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
346 execl(arg_init_path, arg_init_path, NULL);
351 rlim.rlim_cur = 4096;
352 rlim.rlim_max = 4096;
353 (void) setrlimit(RLIMIT_NOFILE, &rlim);
355 /* start with empty ps LL */
356 ps_first = new0(struct ps_struct, 1);
362 /* handle TERM/INT nicely */
363 sigaction(SIGHUP, &sig, NULL);
365 interval = (1.0 / arg_hz) * 1000000000.0;
369 if (graph_start < 0.0) {
370 log_error("Failed to setup graph start time.\n\n"
371 "The system uptime probably includes time that the system was suspended. "
372 "Use --rel to bypass this issue.");
376 LIST_HEAD_INIT(head);
378 /* main program loop */
379 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
388 sampledata = new0(struct list_sample_data, 1);
389 if (sampledata == NULL) {
394 sampledata->sampletime = gettime_ns();
395 sampledata->counter = samples;
398 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
401 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
402 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
408 proc = opendir("/proc");
410 /* wait for /proc to become available, discarding samples */
412 r = log_sample(proc, samples, &sampledata);
417 sample_stop = gettime_ns();
419 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
420 timeleft = interval - elapsed;
422 newint_s = (time_t)(timeleft / 1000000000.0);
423 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
426 * check if we have not consumed our entire timeslice. If we
427 * do, don't sleep and take a new sample right away.
428 * we'll lose all the missed samples and overrun our total
431 if (newint_ns > 0 || newint_s > 0) {
432 req.tv_sec = newint_s;
433 req.tv_nsec = newint_ns;
435 res = nanosleep(&req, NULL);
437 if (errno == EINTR) {
438 /* caught signal, probably HUP! */
441 log_error_errno(errno, "nanosleep() failed: %m");
446 /* calculate how many samples we lost and scrap them */
447 arg_samples_len -= (int)(newint_ns / interval);
449 LIST_PREPEND(link, head, sampledata);
452 /* do some cleanup, close fd's */
454 while (ps->next_ps) {
456 if (ps->schedstat >= 0)
457 close(ps->schedstat);
466 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
469 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
470 of = fopen(output_file, "we");
474 log_error("Error opening output file '%s': %m\n", output_file);
478 r = svg_do(strna(build));
480 log_error_errno(r, "Error generating svg file: %m\n");
484 log_info("systemd-bootchart wrote %s\n", output_file);
486 do_journal_append(output_file);
491 /* nitpic cleanups */
492 ps = ps_first->next_ps;
493 while (ps->next_ps) {
494 struct ps_struct *old;
497 old->sample = ps->first;
499 while (old->sample->next) {
500 struct ps_sched_struct *oldsample = old->sample;
502 old->sample = old->sample->next;
514 while (sampledata->link_prev) {
515 struct list_sample_data *old_sampledata = sampledata;
516 sampledata = sampledata->link_prev;
517 free(old_sampledata);
520 /* don't complain when overrun once, happens most commonly on 1st sample */
522 log_warning("systemd-boochart: sample time overrun %i times\n", overrun);