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"
61 static int exiting = 0;
63 #define DEFAULT_SAMPLES_LEN 500
64 #define DEFAULT_HZ 25.0
65 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
66 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
67 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
68 #define DEFAULT_OUTPUT "/run/log"
71 bool arg_entropy = false;
72 bool arg_initcall = true;
73 bool arg_relative = false;
74 bool arg_filter = true;
75 bool arg_show_cmdline = false;
76 bool arg_show_cgroup = false;
78 bool arg_percpu = false;
79 int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */
80 double arg_hz = DEFAULT_HZ;
81 double arg_scale_x = DEFAULT_SCALE_X;
82 double arg_scale_y = DEFAULT_SCALE_Y;
84 char arg_init_path[PATH_MAX] = DEFAULT_INIT;
85 char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT;
87 static void signal_handler(int sig) {
93 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
95 #define BOOTCHART_MAX (16*1024*1024)
97 static void parse_conf(void) {
98 char *init = NULL, *output = NULL;
99 const ConfigTableItem items[] = {
100 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
101 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
102 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
103 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
104 { "Bootchart", "Output", config_parse_path, 0, &output },
105 { "Bootchart", "Init", config_parse_path, 0, &init },
106 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
107 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
108 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
109 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
110 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
111 { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu },
112 { NULL, NULL, NULL, 0, NULL }
115 config_parse_many(BOOTCHART_CONF,
116 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
117 NULL, config_item_table_lookup, items, true, NULL);
120 strscpy(arg_init_path, sizeof(arg_init_path), init);
122 strscpy(arg_output_path, sizeof(arg_output_path), output);
125 static void help(void) {
126 printf("Usage: %s [OPTIONS]\n\n"
128 " -r --rel Record time relative to recording\n"
129 " -f --freq=FREQ Sample frequency [%g]\n"
130 " -n --samples=N Stop sampling at [%d] samples\n"
131 " -x --scale-x=N Scale the graph horizontally [%g] \n"
132 " -y --scale-y=N Scale the graph vertically [%g] \n"
133 " -p --pss Enable PSS graph (CPU intensive)\n"
134 " -e --entropy Enable the entropy_avail graph\n"
135 " -o --output=PATH Path to output files [%s]\n"
136 " -i --init=PATH Path to init executable [%s]\n"
137 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
138 " -C --cmdline Display full command lines with arguments\n"
139 " -c --control-group Display process control group\n"
140 " --per-cpu Draw each CPU utilization and wait bar also\n"
141 " -h --help Display this message\n\n"
142 "See bootchart.conf for more information.\n",
143 program_invocation_short_name,
152 static int parse_argv(int argc, char *argv[]) {
158 static const struct option options[] = {
159 {"rel", no_argument, NULL, 'r' },
160 {"freq", required_argument, NULL, 'f' },
161 {"samples", required_argument, NULL, 'n' },
162 {"pss", no_argument, NULL, 'p' },
163 {"output", required_argument, NULL, 'o' },
164 {"init", required_argument, NULL, 'i' },
165 {"no-filter", no_argument, NULL, 'F' },
166 {"cmdline", no_argument, NULL, 'C' },
167 {"control-group", no_argument, NULL, 'c' },
168 {"help", no_argument, NULL, 'h' },
169 {"scale-x", required_argument, NULL, 'x' },
170 {"scale-y", required_argument, NULL, 'y' },
171 {"entropy", no_argument, NULL, 'e' },
172 {"per-cpu", no_argument, NULL, ARG_PERCPU},
180 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
187 r = safe_atod(optarg, &arg_hz);
189 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
196 arg_show_cmdline = true;
199 arg_show_cgroup = true;
202 r = safe_atoi(optarg, &arg_samples_len);
204 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
208 path_kill_slashes(optarg);
209 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
212 path_kill_slashes(optarg);
213 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
219 r = safe_atod(optarg, &arg_scale_x);
221 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
225 r = safe_atod(optarg, &arg_scale_y);
227 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
245 assert_not_reached("Unhandled option code.");
249 log_error("Frequency needs to be > 0");
256 static void do_journal_append(char *file) {
257 struct iovec iovec[5];
260 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
262 _cleanup_close_ int fd = -1;
264 bootchart_file = strappend("BOOTCHART_FILE=", file);
266 IOVEC_SET_STRING(iovec[j++], bootchart_file);
268 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
269 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
270 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
271 if (bootchart_message)
272 IOVEC_SET_STRING(iovec[j++], bootchart_message);
274 p = malloc(9 + BOOTCHART_MAX);
280 memcpy(p, "BOOTCHART=", 10);
282 fd = open(file, O_RDONLY|O_CLOEXEC);
284 log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file);
288 n = loop_read(fd, p + 10, BOOTCHART_MAX, false);
290 log_error_errno(n, "Failed to read bootchart data: %m");
294 iovec[j].iov_base = p;
295 iovec[j].iov_len = 10 + n;
298 r = sd_journal_sendv(iovec, j);
300 log_error_errno(r, "Failed to send bootchart: %m");
303 int main(int argc, char *argv[]) {
304 _cleanup_free_ char *build = NULL;
305 _cleanup_close_ int sysfd = -1;
306 _cleanup_closedir_ DIR *proc = NULL;
307 _cleanup_fclose_ FILE *of = NULL;
311 struct ps_struct *ps_first;
312 struct sigaction sig = {
313 .sa_handler = signal_handler,
315 struct ps_struct *ps;
316 char output_file[PATH_MAX];
325 struct list_sample_data *head;
326 static struct list_sample_data *sampledata;
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;
368 graph_start = log_start = gettime_ns();
373 clock_gettime(CLOCK_BOOTTIME, &n);
374 uptime = (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
376 log_start = gettime_ns();
377 graph_start = log_start - uptime;
380 if (graph_start < 0.0) {
381 log_error("Failed to setup graph start time.\n\n"
382 "The system uptime probably includes time that the system was suspended. "
383 "Use --rel to bypass this issue.");
387 LIST_HEAD_INIT(head);
389 /* main program loop */
390 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
399 sampledata = new0(struct list_sample_data, 1);
400 if (sampledata == NULL) {
405 sampledata->sampletime = gettime_ns();
406 sampledata->counter = samples;
409 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
412 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
413 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
419 proc = opendir("/proc");
421 /* wait for /proc to become available, discarding samples */
423 r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus);
428 sample_stop = gettime_ns();
430 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
431 timeleft = interval - elapsed;
433 newint_s = (time_t)(timeleft / 1000000000.0);
434 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
437 * check if we have not consumed our entire timeslice. If we
438 * do, don't sleep and take a new sample right away.
439 * we'll lose all the missed samples and overrun our total
442 if (newint_ns > 0 || newint_s > 0) {
443 req.tv_sec = newint_s;
444 req.tv_nsec = newint_ns;
446 res = nanosleep(&req, NULL);
448 if (errno == EINTR) {
449 /* caught signal, probably HUP! */
452 log_error_errno(errno, "nanosleep() failed: %m");
457 /* calculate how many samples we lost and scrap them */
458 arg_samples_len -= (int)(newint_ns / interval);
460 LIST_PREPEND(link, head, sampledata);
463 /* do some cleanup, close fd's */
465 while (ps->next_ps) {
467 if (ps->schedstat >= 0)
468 close(ps->schedstat);
477 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
480 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
481 of = fopen(output_file, "we");
485 log_error("Error opening output file '%s': %m\n", output_file);
502 log_error_errno(r, "Error generating svg file: %m\n");
506 log_info("systemd-bootchart wrote %s\n", output_file);
508 do_journal_append(output_file);
510 /* nitpic cleanups */
511 ps = ps_first->next_ps;
512 while (ps->next_ps) {
513 struct ps_struct *old;
516 old->sample = ps->first;
518 while (old->sample->next) {
519 struct ps_sched_struct *oldsample = old->sample;
521 old->sample = old->sample->next;
533 while (sampledata->link_prev) {
534 struct list_sample_data *old_sampledata = sampledata;
535 sampledata = sampledata->link_prev;
536 free(old_sampledata);
539 /* don't complain when overrun once, happens most commonly on 1st sample */
541 log_warning("systemd-boochart: sample time overrun %i times\n", overrun);