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 "alloc-util.h"
52 #include "bootchart.h"
53 #include "conf-parser.h"
60 #include "parse-util.h"
61 #include "path-util.h"
63 #include "string-util.h"
68 static int exiting
= 0;
70 #define DEFAULT_SAMPLES_LEN 500
71 #define DEFAULT_HZ 25.0
72 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
73 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
74 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
75 #define DEFAULT_OUTPUT "/run/log"
78 bool arg_entropy
= false;
79 bool arg_initcall
= true;
80 bool arg_relative
= false;
81 bool arg_filter
= true;
82 bool arg_show_cmdline
= false;
83 bool arg_show_cgroup
= false;
85 bool arg_percpu
= false;
86 int arg_samples_len
= DEFAULT_SAMPLES_LEN
; /* we record len+1 (1 start sample) */
87 double arg_hz
= DEFAULT_HZ
;
88 double arg_scale_x
= DEFAULT_SCALE_X
;
89 double arg_scale_y
= DEFAULT_SCALE_Y
;
91 char arg_init_path
[PATH_MAX
] = DEFAULT_INIT
;
92 char arg_output_path
[PATH_MAX
] = DEFAULT_OUTPUT
;
94 static void signal_handler(int sig
) {
98 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
100 #define BOOTCHART_MAX (16*1024*1024)
102 static void parse_conf(void) {
103 char *init
= NULL
, *output
= NULL
;
104 const ConfigTableItem items
[] = {
105 { "Bootchart", "Samples", config_parse_int
, 0, &arg_samples_len
},
106 { "Bootchart", "Frequency", config_parse_double
, 0, &arg_hz
},
107 { "Bootchart", "Relative", config_parse_bool
, 0, &arg_relative
},
108 { "Bootchart", "Filter", config_parse_bool
, 0, &arg_filter
},
109 { "Bootchart", "Output", config_parse_path
, 0, &output
},
110 { "Bootchart", "Init", config_parse_path
, 0, &init
},
111 { "Bootchart", "PlotMemoryUsage", config_parse_bool
, 0, &arg_pss
},
112 { "Bootchart", "PlotEntropyGraph", config_parse_bool
, 0, &arg_entropy
},
113 { "Bootchart", "ScaleX", config_parse_double
, 0, &arg_scale_x
},
114 { "Bootchart", "ScaleY", config_parse_double
, 0, &arg_scale_y
},
115 { "Bootchart", "ControlGroup", config_parse_bool
, 0, &arg_show_cgroup
},
116 { "Bootchart", "PerCPU", config_parse_bool
, 0, &arg_percpu
},
117 { NULL
, NULL
, NULL
, 0, NULL
}
120 config_parse_many(BOOTCHART_CONF
,
121 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
122 NULL
, config_item_table_lookup
, items
, true, NULL
);
125 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
127 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
130 static void help(void) {
131 printf("Usage: %s [OPTIONS]\n\n"
133 " -r --rel Record time relative to recording\n"
134 " -f --freq=FREQ Sample frequency [%g]\n"
135 " -n --samples=N Stop sampling at [%d] samples\n"
136 " -x --scale-x=N Scale the graph horizontally [%g] \n"
137 " -y --scale-y=N Scale the graph vertically [%g] \n"
138 " -p --pss Enable PSS graph (CPU intensive)\n"
139 " -e --entropy Enable the entropy_avail graph\n"
140 " -o --output=PATH Path to output files [%s]\n"
141 " -i --init=PATH Path to init executable [%s]\n"
142 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
143 " -C --cmdline Display full command lines with arguments\n"
144 " -c --control-group Display process control group\n"
145 " --per-cpu Draw each CPU utilization and wait bar also\n"
146 " -h --help Display this message\n\n"
147 "See bootchart.conf for more information.\n",
148 program_invocation_short_name
,
157 static int parse_argv(int argc
, char *argv
[]) {
163 static const struct option options
[] = {
164 {"rel", no_argument
, NULL
, 'r' },
165 {"freq", required_argument
, NULL
, 'f' },
166 {"samples", required_argument
, NULL
, 'n' },
167 {"pss", no_argument
, NULL
, 'p' },
168 {"output", required_argument
, NULL
, 'o' },
169 {"init", required_argument
, NULL
, 'i' },
170 {"no-filter", no_argument
, NULL
, 'F' },
171 {"cmdline", no_argument
, NULL
, 'C' },
172 {"control-group", no_argument
, NULL
, 'c' },
173 {"help", no_argument
, NULL
, 'h' },
174 {"scale-x", required_argument
, NULL
, 'x' },
175 {"scale-y", required_argument
, NULL
, 'y' },
176 {"entropy", no_argument
, NULL
, 'e' },
177 {"per-cpu", no_argument
, NULL
, ARG_PERCPU
},
185 while ((c
= getopt_long(argc
, argv
, "erpf:n:o:i:FCchx:y:", options
, NULL
)) >= 0)
192 r
= safe_atod(optarg
, &arg_hz
);
194 log_warning_errno(r
, "failed to parse --freq/-f argument '%s': %m",
201 arg_show_cmdline
= true;
204 arg_show_cgroup
= true;
207 r
= safe_atoi(optarg
, &arg_samples_len
);
209 log_warning_errno(r
, "failed to parse --samples/-n argument '%s': %m",
213 path_kill_slashes(optarg
);
214 strscpy(arg_output_path
, sizeof(arg_output_path
), optarg
);
217 path_kill_slashes(optarg
);
218 strscpy(arg_init_path
, sizeof(arg_init_path
), optarg
);
224 r
= safe_atod(optarg
, &arg_scale_x
);
226 log_warning_errno(r
, "failed to parse --scale-x/-x argument '%s': %m",
230 r
= safe_atod(optarg
, &arg_scale_y
);
232 log_warning_errno(r
, "failed to parse --scale-y/-y argument '%s': %m",
250 assert_not_reached("Unhandled option code.");
254 log_error("Frequency needs to be > 0");
261 static int do_journal_append(char *file
) {
262 _cleanup_free_
char *bootchart_message
= NULL
;
263 _cleanup_free_
char *bootchart_file
= NULL
;
264 _cleanup_free_
char *p
= NULL
;
265 _cleanup_close_
int fd
= -1;
266 struct iovec iovec
[5];
270 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
274 IOVEC_SET_STRING(iovec
[j
++], bootchart_file
);
275 IOVEC_SET_STRING(iovec
[j
++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
276 IOVEC_SET_STRING(iovec
[j
++], "PRIORITY=7");
277 bootchart_message
= strjoin("MESSAGE=Bootchart created: ", file
, NULL
);
278 if (!bootchart_message
)
281 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
283 p
= malloc(10 + BOOTCHART_MAX
);
287 memcpy(p
, "BOOTCHART=", 10);
289 fd
= open(file
, O_RDONLY
|O_CLOEXEC
);
291 return log_error_errno(errno
, "Failed to open bootchart data \"%s\": %m", file
);
293 n
= loop_read(fd
, p
+ 10, BOOTCHART_MAX
, false);
295 return log_error_errno(n
, "Failed to read bootchart data: %m");
297 iovec
[j
].iov_base
= p
;
298 iovec
[j
].iov_len
= 10 + n
;
301 r
= sd_journal_sendv(iovec
, j
);
303 log_error_errno(r
, "Failed to send bootchart: %m");
308 int main(int argc
, char *argv
[]) {
309 static struct list_sample_data
*sampledata
;
310 _cleanup_closedir_
DIR *proc
= NULL
;
311 _cleanup_free_
char *build
= NULL
;
312 _cleanup_fclose_
FILE *of
= NULL
;
313 _cleanup_close_
int sysfd
= -1;
314 struct ps_struct
*ps_first
;
318 char output_file
[PATH_MAX
];
325 struct ps_struct
*ps
;
327 struct list_sample_data
*head
;
328 struct sigaction sig
= {
329 .sa_handler
= signal_handler
,
334 r
= parse_argv(argc
, argv
);
342 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
344 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
350 execl(arg_init_path
, arg_init_path
, NULL
);
354 rlim
.rlim_cur
= 4096;
355 rlim
.rlim_max
= 4096;
356 (void) setrlimit(RLIMIT_NOFILE
, &rlim
);
358 /* start with empty ps LL */
359 ps_first
= new0(struct ps_struct
, 1);
365 /* handle TERM/INT nicely */
366 sigaction(SIGHUP
, &sig
, NULL
);
368 interval
= (1.0 / arg_hz
) * 1000000000.0;
371 graph_start
= log_start
= gettime_ns();
376 clock_gettime(clock_boottime_or_monotonic(), &n
);
377 uptime
= (n
.tv_sec
+ (n
.tv_nsec
/ (double) NSEC_PER_SEC
));
379 log_start
= gettime_ns();
380 graph_start
= log_start
- uptime
;
383 if (graph_start
< 0.0) {
384 log_error("Failed to setup graph start time.\n\n"
385 "The system uptime probably includes time that the system was suspended. "
386 "Use --rel to bypass this issue.");
390 LIST_HEAD_INIT(head
);
392 /* main program loop */
393 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
;
434 * check if we have not consumed our entire timeslice. If we
435 * do, don't sleep and take a new sample right away.
436 * we'll lose all the missed samples and overrun our total
442 req
.tv_sec
= (time_t)(timeleft
/ 1000000000.0);
443 req
.tv_nsec
= (long)(timeleft
- (req
.tv_sec
* 1000000000.0));
445 res
= nanosleep(&req
, NULL
);
448 /* caught signal, probably HUP! */
450 log_error_errno(errno
, "nanosleep() failed: %m");
455 /* calculate how many samples we lost and scrap them */
456 arg_samples_len
-= (int)(-timeleft
/ interval
);
458 LIST_PREPEND(link
, head
, sampledata
);
461 /* do some cleanup, close fd's */
463 while (ps
->next_ps
) {
465 ps
->schedstat
= safe_close(ps
->schedstat
);
466 ps
->sched
= safe_close(ps
->sched
);
467 ps
->smaps
= safe_fclose(ps
->smaps
);
472 r
= strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
475 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
476 of
= fopen(output_file
, "we");
480 log_error("Error opening output file '%s': %m\n", output_file
);
484 r
= svg_do(of
, strna(build
), head
, ps_first
,
485 samples
, pscount
, n_cpus
, graph_start
,
486 log_start
, interval
, overrun
);
489 log_error_errno(r
, "Error generating svg file: %m");
493 log_info("systemd-bootchart wrote %s\n", output_file
);
495 r
= do_journal_append(output_file
);
499 /* nitpic cleanups */
500 ps
= ps_first
->next_ps
;
501 while (ps
->next_ps
) {
502 struct ps_struct
*old
;
505 old
->sample
= ps
->first
;
507 while (old
->sample
->next
) {
508 struct ps_sched_struct
*oldsample
= old
->sample
;
510 old
->sample
= old
->sample
->next
;
523 while (sampledata
->link_prev
) {
524 struct list_sample_data
*old_sampledata
= sampledata
;
525 sampledata
= sampledata
->link_prev
;
526 free(old_sampledata
);
530 /* don't complain when overrun once, happens most commonly on 1st sample */
532 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun
);