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;
75 #define DEFAULT_SAMPLES_LEN 500
76 #define DEFAULT_HZ 25.0
77 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
78 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
79 #define DEFAULT_INIT "/sbin/init"
80 #define DEFAULT_OUTPUT "/run/log"
83 bool arg_entropy = false;
85 bool arg_relative = false;
86 bool arg_filter = true;
87 bool arg_show_cmdline = false;
88 bool arg_show_cgroup = false;
91 int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */
92 double arg_hz = DEFAULT_HZ;
93 double arg_scale_x = DEFAULT_SCALE_X;
94 double arg_scale_y = DEFAULT_SCALE_Y;
95 static struct list_sample_data *sampledata;
96 struct list_sample_data *head;
98 char arg_init_path[PATH_MAX] = DEFAULT_INIT;
99 char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT;
101 static void signal_handler(int sig) {
107 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
109 #define BOOTCHART_MAX (16*1024*1024)
111 static void parse_conf(void) {
112 char *init = NULL, *output = NULL;
113 const ConfigTableItem items[] = {
114 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
115 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
116 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
117 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
118 { "Bootchart", "Output", config_parse_path, 0, &output },
119 { "Bootchart", "Init", config_parse_path, 0, &init },
120 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
121 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
122 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
123 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
124 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
125 { NULL, NULL, NULL, 0, NULL }
128 config_parse_many(BOOTCHART_CONF,
129 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
130 NULL, config_item_table_lookup, items, true, NULL);
133 strscpy(arg_init_path, sizeof(arg_init_path), init);
135 strscpy(arg_output_path, sizeof(arg_output_path), output);
138 static void help(void) {
140 "Usage: %s [OPTIONS]\n\n"
142 " -r, --rel Record time relative to recording\n"
143 " -f, --freq=FREQ Sample frequency [%g]\n"
144 " -n, --samples=N Stop sampling at [%d] samples\n"
145 " -x, --scale-x=N Scale the graph horizontally [%g] \n"
146 " -y, --scale-y=N Scale the graph vertically [%g] \n"
147 " -p, --pss Enable PSS graph (CPU intensive)\n"
148 " -e, --entropy Enable the entropy_avail graph\n"
149 " -o, --output=PATH Path to output files [%s]\n"
150 " -i, --init=PATH Path to init executable [%s]\n"
151 " -F, --no-filter Disable filtering of unimportant or ephemeral processes\n"
152 " -C, --cmdline Display full command lines with arguments\n"
153 " -c, --control-group Display process control group\n"
154 " -h, --help Display this message\n\n"
155 "See bootchart.conf for more information.\n",
156 program_invocation_short_name,
165 static int parse_argv(int argc, char *argv[]) {
166 static const struct option options[] = {
167 {"rel", no_argument, NULL, 'r'},
168 {"freq", required_argument, NULL, 'f'},
169 {"samples", required_argument, NULL, 'n'},
170 {"pss", no_argument, NULL, 'p'},
171 {"output", required_argument, NULL, 'o'},
172 {"init", required_argument, NULL, 'i'},
173 {"no-filter", no_argument, NULL, 'F'},
174 {"cmdline", no_argument, NULL, 'C'},
175 {"control-group", no_argument, NULL, 'c'},
176 {"help", no_argument, NULL, 'h'},
177 {"scale-x", required_argument, NULL, 'x'},
178 {"scale-y", required_argument, NULL, 'y'},
179 {"entropy", no_argument, NULL, 'e'},
187 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
194 r = safe_atod(optarg, &arg_hz);
196 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
203 arg_show_cmdline = true;
206 arg_show_cgroup = true;
209 r = safe_atoi(optarg, &arg_samples_len);
211 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
215 path_kill_slashes(optarg);
216 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
219 path_kill_slashes(optarg);
220 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
226 r = safe_atod(optarg, &arg_scale_x);
228 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
232 r = safe_atod(optarg, &arg_scale_y);
234 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
249 assert_not_reached("Unhandled option code.");
253 log_error("Frequency needs to be > 0");
260 static void do_journal_append(char *file) {
261 struct iovec iovec[5];
264 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
267 bootchart_file = strappend("BOOTCHART_FILE=", file);
269 IOVEC_SET_STRING(iovec[j++], bootchart_file);
271 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
272 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
273 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
274 if (bootchart_message)
275 IOVEC_SET_STRING(iovec[j++], bootchart_message);
277 p = malloc(9 + BOOTCHART_MAX);
283 memcpy(p, "BOOTCHART=", 10);
285 f = open(file, O_RDONLY|O_CLOEXEC);
287 log_error_errno(errno, "Failed to read bootchart data: %m");
290 n = loop_read(f, p + 10, BOOTCHART_MAX, false);
292 log_error_errno(n, "Failed to read bootchart data: %m");
298 iovec[j].iov_base = p;
299 iovec[j].iov_len = 10 + n;
302 r = sd_journal_sendv(iovec, j);
304 log_error_errno(r, "Failed to send bootchart: %m");
307 int main(int argc, char *argv[]) {
308 _cleanup_free_ char *build = NULL;
309 struct sigaction sig = {
310 .sa_handler = signal_handler,
312 struct ps_struct *ps;
313 char output_file[PATH_MAX];
318 bool has_procfs = false;
322 r = parse_argv(argc, argv);
324 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
327 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
329 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
335 execl(arg_init_path, arg_init_path, NULL);
340 rlim.rlim_cur = 4096;
341 rlim.rlim_max = 4096;
342 (void) setrlimit(RLIMIT_NOFILE, &rlim);
344 /* start with empty ps LL */
345 ps_first = new0(struct ps_struct, 1);
351 /* handle TERM/INT nicely */
352 sigaction(SIGHUP, &sig, NULL);
354 interval = (1.0 / arg_hz) * 1000000000.0;
358 if (graph_start < 0.0) {
360 "Failed to setup graph start time.\n\nThe system uptime "
361 "probably includes time that the system was suspended. "
362 "Use --rel to bypass this issue.\n");
366 has_procfs = access("/proc/vmstat", F_OK) == 0;
368 LIST_HEAD_INIT(head);
370 /* main program loop */
371 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
380 sampledata = new0(struct list_sample_data, 1);
381 if (sampledata == NULL) {
386 sampledata->sampletime = gettime_ns();
387 sampledata->counter = samples;
389 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
391 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
394 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
395 of = fopen(output_file, "we");
399 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
402 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
403 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
407 log_sample(samples, &sampledata);
409 /* wait for /proc to become available, discarding samples */
410 has_procfs = access("/proc/vmstat", F_OK) == 0;
412 sample_stop = gettime_ns();
414 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
415 timeleft = interval - elapsed;
417 newint_s = (time_t)(timeleft / 1000000000.0);
418 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
421 * check if we have not consumed our entire timeslice. If we
422 * do, don't sleep and take a new sample right away.
423 * we'll lose all the missed samples and overrun our total
426 if (newint_ns > 0 || newint_s > 0) {
427 req.tv_sec = newint_s;
428 req.tv_nsec = newint_ns;
430 res = nanosleep(&req, NULL);
432 if (errno == EINTR) {
433 /* caught signal, probably HUP! */
436 log_error_errno(errno, "nanosleep() failed: %m");
441 /* calculate how many samples we lost and scrap them */
442 arg_samples_len -= (int)(newint_ns / interval);
444 LIST_PREPEND(link, head, sampledata);
447 /* do some cleanup, close fd's */
449 while (ps->next_ps) {
452 close(ps->schedstat);
461 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
464 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
465 of = fopen(output_file, "we");
469 fprintf(stderr, "opening output file '%s': %m\n", output_file);
473 svg_do(strna(build));
475 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
477 do_journal_append(output_file);
486 /* nitpic cleanups */
487 ps = ps_first->next_ps;
488 while (ps->next_ps) {
489 struct ps_struct *old;
492 old->sample = ps->first;
494 while (old->sample->next) {
495 struct ps_sched_struct *oldsample = old->sample;
497 old->sample = old->sample->next;
509 while (sampledata->link_prev) {
510 struct list_sample_data *old_sampledata = sampledata;
511 sampledata = sampledata->link_prev;
512 free(old_sampledata);
515 /* don't complain when overrun once, happens most commonly on 1st sample */
517 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);