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
) {
91 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
93 #define BOOTCHART_MAX (16*1024*1024)
95 static void parse_conf(void) {
96 char *init
= NULL
, *output
= NULL
;
97 const ConfigTableItem items
[] = {
98 { "Bootchart", "Samples", config_parse_int
, 0, &arg_samples_len
},
99 { "Bootchart", "Frequency", config_parse_double
, 0, &arg_hz
},
100 { "Bootchart", "Relative", config_parse_bool
, 0, &arg_relative
},
101 { "Bootchart", "Filter", config_parse_bool
, 0, &arg_filter
},
102 { "Bootchart", "Output", config_parse_path
, 0, &output
},
103 { "Bootchart", "Init", config_parse_path
, 0, &init
},
104 { "Bootchart", "PlotMemoryUsage", config_parse_bool
, 0, &arg_pss
},
105 { "Bootchart", "PlotEntropyGraph", config_parse_bool
, 0, &arg_entropy
},
106 { "Bootchart", "ScaleX", config_parse_double
, 0, &arg_scale_x
},
107 { "Bootchart", "ScaleY", config_parse_double
, 0, &arg_scale_y
},
108 { "Bootchart", "ControlGroup", config_parse_bool
, 0, &arg_show_cgroup
},
109 { "Bootchart", "PerCPU", config_parse_bool
, 0, &arg_percpu
},
110 { NULL
, NULL
, NULL
, 0, NULL
}
113 config_parse_many(BOOTCHART_CONF
,
114 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
115 NULL
, config_item_table_lookup
, items
, true, NULL
);
118 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
120 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
123 static void help(void) {
124 printf("Usage: %s [OPTIONS]\n\n"
126 " -r --rel Record time relative to recording\n"
127 " -f --freq=FREQ Sample frequency [%g]\n"
128 " -n --samples=N Stop sampling at [%d] samples\n"
129 " -x --scale-x=N Scale the graph horizontally [%g] \n"
130 " -y --scale-y=N Scale the graph vertically [%g] \n"
131 " -p --pss Enable PSS graph (CPU intensive)\n"
132 " -e --entropy Enable the entropy_avail graph\n"
133 " -o --output=PATH Path to output files [%s]\n"
134 " -i --init=PATH Path to init executable [%s]\n"
135 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
136 " -C --cmdline Display full command lines with arguments\n"
137 " -c --control-group Display process control group\n"
138 " --per-cpu Draw each CPU utilization and wait bar also\n"
139 " -h --help Display this message\n\n"
140 "See bootchart.conf for more information.\n",
141 program_invocation_short_name
,
150 static int parse_argv(int argc
, char *argv
[]) {
156 static const struct option options
[] = {
157 {"rel", no_argument
, NULL
, 'r' },
158 {"freq", required_argument
, NULL
, 'f' },
159 {"samples", required_argument
, NULL
, 'n' },
160 {"pss", no_argument
, NULL
, 'p' },
161 {"output", required_argument
, NULL
, 'o' },
162 {"init", required_argument
, NULL
, 'i' },
163 {"no-filter", no_argument
, NULL
, 'F' },
164 {"cmdline", no_argument
, NULL
, 'C' },
165 {"control-group", no_argument
, NULL
, 'c' },
166 {"help", no_argument
, NULL
, 'h' },
167 {"scale-x", required_argument
, NULL
, 'x' },
168 {"scale-y", required_argument
, NULL
, 'y' },
169 {"entropy", no_argument
, NULL
, 'e' },
170 {"per-cpu", no_argument
, NULL
, ARG_PERCPU
},
178 while ((c
= getopt_long(argc
, argv
, "erpf:n:o:i:FCchx:y:", options
, NULL
)) >= 0)
185 r
= safe_atod(optarg
, &arg_hz
);
187 log_warning_errno(r
, "failed to parse --freq/-f argument '%s': %m",
194 arg_show_cmdline
= true;
197 arg_show_cgroup
= true;
200 r
= safe_atoi(optarg
, &arg_samples_len
);
202 log_warning_errno(r
, "failed to parse --samples/-n argument '%s': %m",
206 path_kill_slashes(optarg
);
207 strscpy(arg_output_path
, sizeof(arg_output_path
), optarg
);
210 path_kill_slashes(optarg
);
211 strscpy(arg_init_path
, sizeof(arg_init_path
), optarg
);
217 r
= safe_atod(optarg
, &arg_scale_x
);
219 log_warning_errno(r
, "failed to parse --scale-x/-x argument '%s': %m",
223 r
= safe_atod(optarg
, &arg_scale_y
);
225 log_warning_errno(r
, "failed to parse --scale-y/-y argument '%s': %m",
243 assert_not_reached("Unhandled option code.");
247 log_error("Frequency needs to be > 0");
254 static int do_journal_append(char *file
) {
255 _cleanup_free_
char *bootchart_message
= NULL
;
256 _cleanup_free_
char *bootchart_file
= NULL
;
257 _cleanup_free_
char *p
= NULL
;
258 _cleanup_close_
int fd
= -1;
259 struct iovec iovec
[5];
263 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
267 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
)
274 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
276 p
= malloc(10 + BOOTCHART_MAX
);
280 memcpy(p
, "BOOTCHART=", 10);
282 fd
= open(file
, O_RDONLY
|O_CLOEXEC
);
284 return log_error_errno(errno
, "Failed to open bootchart data \"%s\": %m", file
);
286 n
= loop_read(fd
, p
+ 10, BOOTCHART_MAX
, false);
288 return log_error_errno(n
, "Failed to read bootchart data: %m");
290 iovec
[j
].iov_base
= p
;
291 iovec
[j
].iov_len
= 10 + n
;
294 r
= sd_journal_sendv(iovec
, j
);
296 log_error_errno(r
, "Failed to send bootchart: %m");
301 int main(int argc
, char *argv
[]) {
302 static struct list_sample_data
*sampledata
;
303 _cleanup_closedir_
DIR *proc
= NULL
;
304 _cleanup_free_
char *build
= NULL
;
305 _cleanup_fclose_
FILE *of
= NULL
;
306 _cleanup_close_
int sysfd
= -1;
307 struct ps_struct
*ps_first
;
311 char output_file
[PATH_MAX
];
318 struct ps_struct
*ps
;
320 struct list_sample_data
*head
;
321 struct sigaction sig
= {
322 .sa_handler
= signal_handler
,
327 r
= parse_argv(argc
, argv
);
335 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
337 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
343 execl(arg_init_path
, arg_init_path
, NULL
);
348 rlim
.rlim_cur
= 4096;
349 rlim
.rlim_max
= 4096;
350 (void) setrlimit(RLIMIT_NOFILE
, &rlim
);
352 /* start with empty ps LL */
353 ps_first
= new0(struct ps_struct
, 1);
359 /* handle TERM/INT nicely */
360 sigaction(SIGHUP
, &sig
, NULL
);
362 interval
= (1.0 / arg_hz
) * 1000000000.0;
365 graph_start
= log_start
= gettime_ns();
370 clock_gettime(CLOCK_BOOTTIME
, &n
);
371 uptime
= (n
.tv_sec
+ (n
.tv_nsec
/ (double) NSEC_PER_SEC
));
373 log_start
= gettime_ns();
374 graph_start
= log_start
- uptime
;
377 if (graph_start
< 0.0) {
378 log_error("Failed to setup graph start time.\n\n"
379 "The system uptime probably includes time that the system was suspended. "
380 "Use --rel to bypass this issue.");
384 LIST_HEAD_INIT(head
);
386 /* main program loop */
387 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
;
430 newint_s
= (time_t)(timeleft
/ 1000000000.0);
431 newint_ns
= (long)(timeleft
- (newint_s
* 1000000000.0));
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
439 if (newint_ns
> 0 || newint_s
> 0) {
440 req
.tv_sec
= newint_s
;
441 req
.tv_nsec
= newint_ns
;
443 res
= nanosleep(&req
, NULL
);
445 if (errno
== EINTR
) {
446 /* caught signal, probably HUP! */
449 log_error_errno(errno
, "nanosleep() failed: %m");
454 /* calculate how many samples we lost and scrap them */
455 arg_samples_len
-= (int)(newint_ns
/ interval
);
457 LIST_PREPEND(link
, head
, sampledata
);
460 /* do some cleanup, close fd's */
462 while (ps
->next_ps
) {
464 ps
->schedstat
= safe_close(ps
->schedstat
);
465 ps
->sched
= safe_close(ps
->sched
);
474 r
= strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
477 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
478 of
= fopen(output_file
, "we");
482 log_error("Error opening output file '%s': %m\n", output_file
);
486 r
= svg_do(of
, strna(build
), head
, ps_first
,
487 samples
, pscount
, n_cpus
, graph_start
,
488 log_start
, interval
, overrun
);
491 log_error_errno(r
, "Error generating svg file: %m");
495 log_info("systemd-bootchart wrote %s\n", output_file
);
497 r
= do_journal_append(output_file
);
501 /* nitpic cleanups */
502 ps
= ps_first
->next_ps
;
503 while (ps
->next_ps
) {
504 struct ps_struct
*old
;
507 old
->sample
= ps
->first
;
509 while (old
->sample
->next
) {
510 struct ps_sched_struct
*oldsample
= old
->sample
;
512 old
->sample
= old
->sample
->next
;
525 while (sampledata
->link_prev
) {
526 struct list_sample_data
*old_sampledata
= sampledata
;
527 sampledata
= sampledata
->link_prev
;
528 free(old_sampledata
);
532 /* don't complain when overrun once, happens most commonly on 1st sample */
534 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun
);