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
45 #include <sys/resource.h>
49 #include "sd-journal.h"
51 #include "bootchart.h"
52 #include "conf-parser.h"
58 #include "parse-util.h"
59 #include "path-util.h"
61 #include "string-util.h"
66 static int exiting
= 0;
68 #define DEFAULT_SAMPLES_LEN 500
69 #define DEFAULT_HZ 25.0
70 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
71 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
72 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
73 #define DEFAULT_OUTPUT "/run/log"
76 bool arg_entropy
= false;
77 bool arg_initcall
= true;
78 bool arg_relative
= false;
79 bool arg_filter
= true;
80 bool arg_show_cmdline
= false;
81 bool arg_show_cgroup
= false;
83 bool arg_percpu
= false;
84 int arg_samples_len
= DEFAULT_SAMPLES_LEN
; /* we record len+1 (1 start sample) */
85 double arg_hz
= DEFAULT_HZ
;
86 double arg_scale_x
= DEFAULT_SCALE_X
;
87 double arg_scale_y
= DEFAULT_SCALE_Y
;
89 char arg_init_path
[PATH_MAX
] = DEFAULT_INIT
;
90 char arg_output_path
[PATH_MAX
] = DEFAULT_OUTPUT
;
92 static void signal_handler(int sig
) {
96 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
98 #define BOOTCHART_MAX (16*1024*1024)
100 static void parse_conf(void) {
101 char *init
= NULL
, *output
= NULL
;
102 const ConfigTableItem items
[] = {
103 { "Bootchart", "Samples", config_parse_int
, 0, &arg_samples_len
},
104 { "Bootchart", "Frequency", config_parse_double
, 0, &arg_hz
},
105 { "Bootchart", "Relative", config_parse_bool
, 0, &arg_relative
},
106 { "Bootchart", "Filter", config_parse_bool
, 0, &arg_filter
},
107 { "Bootchart", "Output", config_parse_path
, 0, &output
},
108 { "Bootchart", "Init", config_parse_path
, 0, &init
},
109 { "Bootchart", "PlotMemoryUsage", config_parse_bool
, 0, &arg_pss
},
110 { "Bootchart", "PlotEntropyGraph", config_parse_bool
, 0, &arg_entropy
},
111 { "Bootchart", "ScaleX", config_parse_double
, 0, &arg_scale_x
},
112 { "Bootchart", "ScaleY", config_parse_double
, 0, &arg_scale_y
},
113 { "Bootchart", "ControlGroup", config_parse_bool
, 0, &arg_show_cgroup
},
114 { "Bootchart", "PerCPU", config_parse_bool
, 0, &arg_percpu
},
115 { NULL
, NULL
, NULL
, 0, NULL
}
118 config_parse_many(BOOTCHART_CONF
,
119 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
120 NULL
, config_item_table_lookup
, items
, true, NULL
);
123 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
125 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
128 static void help(void) {
129 printf("Usage: %s [OPTIONS]\n\n"
131 " -r --rel Record time relative to recording\n"
132 " -f --freq=FREQ Sample frequency [%g]\n"
133 " -n --samples=N Stop sampling at [%d] samples\n"
134 " -x --scale-x=N Scale the graph horizontally [%g] \n"
135 " -y --scale-y=N Scale the graph vertically [%g] \n"
136 " -p --pss Enable PSS graph (CPU intensive)\n"
137 " -e --entropy Enable the entropy_avail graph\n"
138 " -o --output=PATH Path to output files [%s]\n"
139 " -i --init=PATH Path to init executable [%s]\n"
140 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
141 " -C --cmdline Display full command lines with arguments\n"
142 " -c --control-group Display process control group\n"
143 " --per-cpu Draw each CPU utilization and wait bar also\n"
144 " -h --help Display this message\n\n"
145 "See bootchart.conf for more information.\n",
146 program_invocation_short_name
,
155 static int parse_argv(int argc
, char *argv
[]) {
161 static const struct option options
[] = {
162 {"rel", no_argument
, NULL
, 'r' },
163 {"freq", required_argument
, NULL
, 'f' },
164 {"samples", required_argument
, NULL
, 'n' },
165 {"pss", no_argument
, NULL
, 'p' },
166 {"output", required_argument
, NULL
, 'o' },
167 {"init", required_argument
, NULL
, 'i' },
168 {"no-filter", no_argument
, NULL
, 'F' },
169 {"cmdline", no_argument
, NULL
, 'C' },
170 {"control-group", no_argument
, NULL
, 'c' },
171 {"help", no_argument
, NULL
, 'h' },
172 {"scale-x", required_argument
, NULL
, 'x' },
173 {"scale-y", required_argument
, NULL
, 'y' },
174 {"entropy", no_argument
, NULL
, 'e' },
175 {"per-cpu", no_argument
, NULL
, ARG_PERCPU
},
183 while ((c
= getopt_long(argc
, argv
, "erpf:n:o:i:FCchx:y:", options
, NULL
)) >= 0)
190 r
= safe_atod(optarg
, &arg_hz
);
192 log_warning_errno(r
, "failed to parse --freq/-f argument '%s': %m",
199 arg_show_cmdline
= true;
202 arg_show_cgroup
= true;
205 r
= safe_atoi(optarg
, &arg_samples_len
);
207 log_warning_errno(r
, "failed to parse --samples/-n argument '%s': %m",
211 path_kill_slashes(optarg
);
212 strscpy(arg_output_path
, sizeof(arg_output_path
), optarg
);
215 path_kill_slashes(optarg
);
216 strscpy(arg_init_path
, sizeof(arg_init_path
), optarg
);
222 r
= safe_atod(optarg
, &arg_scale_x
);
224 log_warning_errno(r
, "failed to parse --scale-x/-x argument '%s': %m",
228 r
= safe_atod(optarg
, &arg_scale_y
);
230 log_warning_errno(r
, "failed to parse --scale-y/-y argument '%s': %m",
248 assert_not_reached("Unhandled option code.");
252 log_error("Frequency needs to be > 0");
259 static int do_journal_append(char *file
) {
260 _cleanup_free_
char *bootchart_message
= NULL
;
261 _cleanup_free_
char *bootchart_file
= NULL
;
262 _cleanup_free_
char *p
= NULL
;
263 _cleanup_close_
int fd
= -1;
264 struct iovec iovec
[5];
268 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
272 IOVEC_SET_STRING(iovec
[j
++], bootchart_file
);
273 IOVEC_SET_STRING(iovec
[j
++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
274 IOVEC_SET_STRING(iovec
[j
++], "PRIORITY=7");
275 bootchart_message
= strjoin("MESSAGE=Bootchart created: ", file
, NULL
);
276 if (!bootchart_message
)
279 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
281 p
= malloc(10 + BOOTCHART_MAX
);
285 memcpy(p
, "BOOTCHART=", 10);
287 fd
= open(file
, O_RDONLY
|O_CLOEXEC
);
289 return log_error_errno(errno
, "Failed to open bootchart data \"%s\": %m", file
);
291 n
= loop_read(fd
, p
+ 10, BOOTCHART_MAX
, false);
293 return log_error_errno(n
, "Failed to read bootchart data: %m");
295 iovec
[j
].iov_base
= p
;
296 iovec
[j
].iov_len
= 10 + n
;
299 r
= sd_journal_sendv(iovec
, j
);
301 log_error_errno(r
, "Failed to send bootchart: %m");
306 int main(int argc
, char *argv
[]) {
307 static struct list_sample_data
*sampledata
;
308 _cleanup_closedir_
DIR *proc
= NULL
;
309 _cleanup_free_
char *build
= NULL
;
310 _cleanup_fclose_
FILE *of
= NULL
;
311 _cleanup_close_
int sysfd
= -1;
312 struct ps_struct
*ps_first
;
316 char output_file
[PATH_MAX
];
323 struct ps_struct
*ps
;
325 struct list_sample_data
*head
;
326 struct sigaction sig
= {
327 .sa_handler
= signal_handler
,
332 r
= parse_argv(argc
, argv
);
340 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
342 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
348 execl(arg_init_path
, arg_init_path
, NULL
);
352 rlim
.rlim_cur
= 4096;
353 rlim
.rlim_max
= 4096;
354 (void) setrlimit(RLIMIT_NOFILE
, &rlim
);
356 /* start with empty ps LL */
357 ps_first
= new0(struct ps_struct
, 1);
363 /* handle TERM/INT nicely */
364 sigaction(SIGHUP
, &sig
, NULL
);
366 interval
= (1.0 / arg_hz
) * 1000000000.0;
369 graph_start
= log_start
= gettime_ns();
374 clock_gettime(clock_boottime_or_monotonic(), &n
);
375 uptime
= (n
.tv_sec
+ (n
.tv_nsec
/ (double) NSEC_PER_SEC
));
377 log_start
= gettime_ns();
378 graph_start
= log_start
- uptime
;
381 if (graph_start
< 0.0) {
382 log_error("Failed to setup graph start time.\n\n"
383 "The system uptime probably includes time that the system was suspended. "
384 "Use --rel to bypass this issue.");
388 LIST_HEAD_INIT(head
);
390 /* main program loop */
391 for (samples
= 0; !exiting
&& samples
< arg_samples_len
; samples
++) {
397 sampledata
= new0(struct list_sample_data
, 1);
398 if (sampledata
== NULL
) {
403 sampledata
->sampletime
= gettime_ns();
404 sampledata
->counter
= samples
;
407 sysfd
= open("/sys", O_RDONLY
|O_CLOEXEC
);
410 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
) == -ENOENT
)
411 parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
);
417 proc
= opendir("/proc");
419 /* wait for /proc to become available, discarding samples */
421 r
= log_sample(proc
, samples
, ps_first
, &sampledata
, &pscount
, &n_cpus
);
426 sample_stop
= gettime_ns();
428 elapsed
= (sample_stop
- sampledata
->sampletime
) * 1000000000.0;
429 timeleft
= interval
- elapsed
;
432 * check if we have not consumed our entire timeslice. If we
433 * do, don't sleep and take a new sample right away.
434 * we'll lose all the missed samples and overrun our total
440 req
.tv_sec
= (time_t)(timeleft
/ 1000000000.0);
441 req
.tv_nsec
= (long)(timeleft
- (req
.tv_sec
* 1000000000.0));
443 res
= nanosleep(&req
, NULL
);
446 /* caught signal, probably HUP! */
448 log_error_errno(errno
, "nanosleep() failed: %m");
453 /* calculate how many samples we lost and scrap them */
454 arg_samples_len
-= (int)(-timeleft
/ interval
);
456 LIST_PREPEND(link
, head
, sampledata
);
459 /* do some cleanup, close fd's */
461 while (ps
->next_ps
) {
463 ps
->schedstat
= safe_close(ps
->schedstat
);
464 ps
->sched
= safe_close(ps
->sched
);
465 ps
->smaps
= safe_fclose(ps
->smaps
);
470 r
= strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
473 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
474 of
= fopen(output_file
, "we");
478 log_error("Error opening output file '%s': %m\n", output_file
);
482 r
= svg_do(of
, strna(build
), head
, ps_first
,
483 samples
, pscount
, n_cpus
, graph_start
,
484 log_start
, interval
, overrun
);
487 log_error_errno(r
, "Error generating svg file: %m");
491 log_info("systemd-bootchart wrote %s\n", output_file
);
493 r
= do_journal_append(output_file
);
497 /* nitpic cleanups */
498 ps
= ps_first
->next_ps
;
499 while (ps
->next_ps
) {
500 struct ps_struct
*old
;
503 old
->sample
= ps
->first
;
505 while (old
->sample
->next
) {
506 struct ps_sched_struct
*oldsample
= old
->sample
;
508 old
->sample
= old
->sample
->next
;
521 while (sampledata
->link_prev
) {
522 struct list_sample_data
*old_sampledata
= sampledata
;
523 sampledata
= sampledata
->link_prev
;
524 free(old_sampledata
);
528 /* don't complain when overrun once, happens most commonly on 1st sample */
530 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun
);