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 "path-util.h"
60 #include "string-util.h"
65 static int exiting
= 0;
67 #define DEFAULT_SAMPLES_LEN 500
68 #define DEFAULT_HZ 25.0
69 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
70 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
71 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
72 #define DEFAULT_OUTPUT "/run/log"
75 bool arg_entropy
= false;
76 bool arg_initcall
= true;
77 bool arg_relative
= false;
78 bool arg_filter
= true;
79 bool arg_show_cmdline
= false;
80 bool arg_show_cgroup
= false;
82 bool arg_percpu
= false;
83 int arg_samples_len
= DEFAULT_SAMPLES_LEN
; /* we record len+1 (1 start sample) */
84 double arg_hz
= DEFAULT_HZ
;
85 double arg_scale_x
= DEFAULT_SCALE_X
;
86 double arg_scale_y
= DEFAULT_SCALE_Y
;
88 char arg_init_path
[PATH_MAX
] = DEFAULT_INIT
;
89 char arg_output_path
[PATH_MAX
] = DEFAULT_OUTPUT
;
91 static void signal_handler(int sig
) {
95 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
97 #define BOOTCHART_MAX (16*1024*1024)
99 static void parse_conf(void) {
100 char *init
= NULL
, *output
= NULL
;
101 const ConfigTableItem items
[] = {
102 { "Bootchart", "Samples", config_parse_int
, 0, &arg_samples_len
},
103 { "Bootchart", "Frequency", config_parse_double
, 0, &arg_hz
},
104 { "Bootchart", "Relative", config_parse_bool
, 0, &arg_relative
},
105 { "Bootchart", "Filter", config_parse_bool
, 0, &arg_filter
},
106 { "Bootchart", "Output", config_parse_path
, 0, &output
},
107 { "Bootchart", "Init", config_parse_path
, 0, &init
},
108 { "Bootchart", "PlotMemoryUsage", config_parse_bool
, 0, &arg_pss
},
109 { "Bootchart", "PlotEntropyGraph", config_parse_bool
, 0, &arg_entropy
},
110 { "Bootchart", "ScaleX", config_parse_double
, 0, &arg_scale_x
},
111 { "Bootchart", "ScaleY", config_parse_double
, 0, &arg_scale_y
},
112 { "Bootchart", "ControlGroup", config_parse_bool
, 0, &arg_show_cgroup
},
113 { "Bootchart", "PerCPU", config_parse_bool
, 0, &arg_percpu
},
114 { NULL
, NULL
, NULL
, 0, NULL
}
117 config_parse_many(BOOTCHART_CONF
,
118 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
119 NULL
, config_item_table_lookup
, items
, true, NULL
);
122 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
124 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
127 static void help(void) {
128 printf("Usage: %s [OPTIONS]\n\n"
130 " -r --rel Record time relative to recording\n"
131 " -f --freq=FREQ Sample frequency [%g]\n"
132 " -n --samples=N Stop sampling at [%d] samples\n"
133 " -x --scale-x=N Scale the graph horizontally [%g] \n"
134 " -y --scale-y=N Scale the graph vertically [%g] \n"
135 " -p --pss Enable PSS graph (CPU intensive)\n"
136 " -e --entropy Enable the entropy_avail graph\n"
137 " -o --output=PATH Path to output files [%s]\n"
138 " -i --init=PATH Path to init executable [%s]\n"
139 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
140 " -C --cmdline Display full command lines with arguments\n"
141 " -c --control-group Display process control group\n"
142 " --per-cpu Draw each CPU utilization and wait bar also\n"
143 " -h --help Display this message\n\n"
144 "See bootchart.conf for more information.\n",
145 program_invocation_short_name
,
154 static int parse_argv(int argc
, char *argv
[]) {
160 static const struct option options
[] = {
161 {"rel", no_argument
, NULL
, 'r' },
162 {"freq", required_argument
, NULL
, 'f' },
163 {"samples", required_argument
, NULL
, 'n' },
164 {"pss", no_argument
, NULL
, 'p' },
165 {"output", required_argument
, NULL
, 'o' },
166 {"init", required_argument
, NULL
, 'i' },
167 {"no-filter", no_argument
, NULL
, 'F' },
168 {"cmdline", no_argument
, NULL
, 'C' },
169 {"control-group", no_argument
, NULL
, 'c' },
170 {"help", no_argument
, NULL
, 'h' },
171 {"scale-x", required_argument
, NULL
, 'x' },
172 {"scale-y", required_argument
, NULL
, 'y' },
173 {"entropy", no_argument
, NULL
, 'e' },
174 {"per-cpu", no_argument
, NULL
, ARG_PERCPU
},
182 while ((c
= getopt_long(argc
, argv
, "erpf:n:o:i:FCchx:y:", options
, NULL
)) >= 0)
189 r
= safe_atod(optarg
, &arg_hz
);
191 log_warning_errno(r
, "failed to parse --freq/-f argument '%s': %m",
198 arg_show_cmdline
= true;
201 arg_show_cgroup
= true;
204 r
= safe_atoi(optarg
, &arg_samples_len
);
206 log_warning_errno(r
, "failed to parse --samples/-n argument '%s': %m",
210 path_kill_slashes(optarg
);
211 strscpy(arg_output_path
, sizeof(arg_output_path
), optarg
);
214 path_kill_slashes(optarg
);
215 strscpy(arg_init_path
, sizeof(arg_init_path
), optarg
);
221 r
= safe_atod(optarg
, &arg_scale_x
);
223 log_warning_errno(r
, "failed to parse --scale-x/-x argument '%s': %m",
227 r
= safe_atod(optarg
, &arg_scale_y
);
229 log_warning_errno(r
, "failed to parse --scale-y/-y argument '%s': %m",
247 assert_not_reached("Unhandled option code.");
251 log_error("Frequency needs to be > 0");
258 static int do_journal_append(char *file
) {
259 _cleanup_free_
char *bootchart_message
= NULL
;
260 _cleanup_free_
char *bootchart_file
= NULL
;
261 _cleanup_free_
char *p
= NULL
;
262 _cleanup_close_
int fd
= -1;
263 struct iovec iovec
[5];
267 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
271 IOVEC_SET_STRING(iovec
[j
++], bootchart_file
);
272 IOVEC_SET_STRING(iovec
[j
++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
273 IOVEC_SET_STRING(iovec
[j
++], "PRIORITY=7");
274 bootchart_message
= strjoin("MESSAGE=Bootchart created: ", file
, NULL
);
275 if (!bootchart_message
)
278 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
280 p
= malloc(10 + BOOTCHART_MAX
);
284 memcpy(p
, "BOOTCHART=", 10);
286 fd
= open(file
, O_RDONLY
|O_CLOEXEC
);
288 return log_error_errno(errno
, "Failed to open bootchart data \"%s\": %m", file
);
290 n
= loop_read(fd
, p
+ 10, BOOTCHART_MAX
, false);
292 return 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");
305 int main(int argc
, char *argv
[]) {
306 static struct list_sample_data
*sampledata
;
307 _cleanup_closedir_
DIR *proc
= NULL
;
308 _cleanup_free_
char *build
= NULL
;
309 _cleanup_fclose_
FILE *of
= NULL
;
310 _cleanup_close_
int sysfd
= -1;
311 struct ps_struct
*ps_first
;
315 char output_file
[PATH_MAX
];
322 struct ps_struct
*ps
;
324 struct list_sample_data
*head
;
325 struct sigaction sig
= {
326 .sa_handler
= signal_handler
,
331 r
= parse_argv(argc
, argv
);
339 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
341 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
347 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_or_monotonic(), &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
++) {
396 sampledata
= new0(struct list_sample_data
, 1);
397 if (sampledata
== NULL
) {
402 sampledata
->sampletime
= gettime_ns();
403 sampledata
->counter
= samples
;
406 sysfd
= open("/sys", O_RDONLY
|O_CLOEXEC
);
409 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
) == -ENOENT
)
410 parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
);
416 proc
= opendir("/proc");
418 /* wait for /proc to become available, discarding samples */
420 r
= log_sample(proc
, samples
, ps_first
, &sampledata
, &pscount
, &n_cpus
);
425 sample_stop
= gettime_ns();
427 elapsed
= (sample_stop
- sampledata
->sampletime
) * 1000000000.0;
428 timeleft
= interval
- elapsed
;
431 * check if we have not consumed our entire timeslice. If we
432 * do, don't sleep and take a new sample right away.
433 * we'll lose all the missed samples and overrun our total
439 req
.tv_sec
= (time_t)(timeleft
/ 1000000000.0);
440 req
.tv_nsec
= (long)(timeleft
- (req
.tv_sec
* 1000000000.0));
442 res
= nanosleep(&req
, NULL
);
445 /* caught signal, probably HUP! */
447 log_error_errno(errno
, "nanosleep() failed: %m");
452 /* calculate how many samples we lost and scrap them */
453 arg_samples_len
-= (int)(-timeleft
/ interval
);
455 LIST_PREPEND(link
, head
, sampledata
);
458 /* do some cleanup, close fd's */
460 while (ps
->next_ps
) {
462 ps
->schedstat
= safe_close(ps
->schedstat
);
463 ps
->sched
= safe_close(ps
->sched
);
464 ps
->smaps
= safe_fclose(ps
->smaps
);
469 r
= strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
472 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
473 of
= fopen(output_file
, "we");
477 log_error("Error opening output file '%s': %m\n", output_file
);
481 r
= svg_do(of
, strna(build
), head
, ps_first
,
482 samples
, pscount
, n_cpus
, graph_start
,
483 log_start
, interval
, overrun
);
486 log_error_errno(r
, "Error generating svg file: %m");
490 log_info("systemd-bootchart wrote %s\n", output_file
);
492 r
= do_journal_append(output_file
);
496 /* nitpic cleanups */
497 ps
= ps_first
->next_ps
;
498 while (ps
->next_ps
) {
499 struct ps_struct
*old
;
502 old
->sample
= ps
->first
;
504 while (old
->sample
->next
) {
505 struct ps_sched_struct
*oldsample
= old
->sample
;
507 old
->sample
= old
->sample
->next
;
520 while (sampledata
->link_prev
) {
521 struct list_sample_data
*old_sampledata
= sampledata
;
522 sampledata
= sampledata
->link_prev
;
523 free(old_sampledata
);
527 /* don't complain when overrun once, happens most commonly on 1st sample */
529 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun
);