2 This file is part of systemd.
4 Copyright (C) 2009-2013 Intel Corporation
7 Auke Kok <auke-jan.h.kok@intel.com>
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 Many thanks to those who contributed ideas and code:
26 - Ziga Mahkovec - Original bootchart author
27 - Anders Norgaard - PyBootchartgui
28 - Michael Meeks - bootchart2
29 - Scott James Remnant - Ubuntu C-based logger
30 - Arjan van der Ven - for the idea to merge bootgraph.pl functionality
43 #include <sys/resource.h>
47 #include "sd-journal.h"
49 #include "alloc-util.h"
50 #include "bootchart.h"
51 #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_MAX (16*1024*1024)
98 static void parse_conf(void) {
99 char *init
= NULL
, *output
= NULL
;
100 const ConfigTableItem items
[] = {
101 { "Bootchart", "Samples", config_parse_int
, 0, &arg_samples_len
},
102 { "Bootchart", "Frequency", config_parse_double
, 0, &arg_hz
},
103 { "Bootchart", "Relative", config_parse_bool
, 0, &arg_relative
},
104 { "Bootchart", "Filter", config_parse_bool
, 0, &arg_filter
},
105 { "Bootchart", "Output", config_parse_path
, 0, &output
},
106 { "Bootchart", "Init", config_parse_path
, 0, &init
},
107 { "Bootchart", "PlotMemoryUsage", config_parse_bool
, 0, &arg_pss
},
108 { "Bootchart", "PlotEntropyGraph", config_parse_bool
, 0, &arg_entropy
},
109 { "Bootchart", "ScaleX", config_parse_double
, 0, &arg_scale_x
},
110 { "Bootchart", "ScaleY", config_parse_double
, 0, &arg_scale_y
},
111 { "Bootchart", "ControlGroup", config_parse_bool
, 0, &arg_show_cgroup
},
112 { "Bootchart", "PerCPU", config_parse_bool
, 0, &arg_percpu
},
113 { NULL
, NULL
, NULL
, 0, NULL
}
116 config_parse_many(PKGSYSCONFDIR
"/bootchart.conf",
117 CONF_PATHS_NULSTR("systemd/bootchart.conf.d"),
118 NULL
, config_item_table_lookup
, items
, true, NULL
);
121 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
123 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
126 static void help(void) {
127 printf("Usage: %s [OPTIONS]\n\n"
129 " -r --rel Record time relative to recording\n"
130 " -f --freq=FREQ Sample frequency [%g]\n"
131 " -n --samples=N Stop sampling at [%d] samples\n"
132 " -x --scale-x=N Scale the graph horizontally [%g] \n"
133 " -y --scale-y=N Scale the graph vertically [%g] \n"
134 " -p --pss Enable PSS graph (CPU intensive)\n"
135 " -e --entropy Enable the entropy_avail graph\n"
136 " -o --output=PATH Path to output files [%s]\n"
137 " -i --init=PATH Path to init executable [%s]\n"
138 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
139 " -C --cmdline Display full command lines with arguments\n"
140 " -c --control-group Display process control group\n"
141 " --per-cpu Draw each CPU utilization and wait bar also\n"
142 " -h --help Display this message\n\n"
143 "See bootchart.conf for more information.\n",
144 program_invocation_short_name
,
153 static int parse_argv(int argc
, char *argv
[]) {
159 static const struct option options
[] = {
160 {"rel", no_argument
, NULL
, 'r' },
161 {"freq", required_argument
, NULL
, 'f' },
162 {"samples", required_argument
, NULL
, 'n' },
163 {"pss", no_argument
, NULL
, 'p' },
164 {"output", required_argument
, NULL
, 'o' },
165 {"init", required_argument
, NULL
, 'i' },
166 {"no-filter", no_argument
, NULL
, 'F' },
167 {"cmdline", no_argument
, NULL
, 'C' },
168 {"control-group", no_argument
, NULL
, 'c' },
169 {"help", no_argument
, NULL
, 'h' },
170 {"scale-x", required_argument
, NULL
, 'x' },
171 {"scale-y", required_argument
, NULL
, 'y' },
172 {"entropy", no_argument
, NULL
, 'e' },
173 {"per-cpu", no_argument
, NULL
, ARG_PERCPU
},
181 while ((c
= getopt_long(argc
, argv
, "erpf:n:o:i:FCchx:y:", options
, NULL
)) >= 0)
188 r
= safe_atod(optarg
, &arg_hz
);
190 log_warning_errno(r
, "failed to parse --freq/-f argument '%s': %m",
197 arg_show_cmdline
= true;
200 arg_show_cgroup
= true;
203 r
= safe_atoi(optarg
, &arg_samples_len
);
205 log_warning_errno(r
, "failed to parse --samples/-n argument '%s': %m",
209 path_kill_slashes(optarg
);
210 strscpy(arg_output_path
, sizeof(arg_output_path
), optarg
);
213 path_kill_slashes(optarg
);
214 strscpy(arg_init_path
, sizeof(arg_init_path
), optarg
);
220 r
= safe_atod(optarg
, &arg_scale_x
);
222 log_warning_errno(r
, "failed to parse --scale-x/-x argument '%s': %m",
226 r
= safe_atod(optarg
, &arg_scale_y
);
228 log_warning_errno(r
, "failed to parse --scale-y/-y argument '%s': %m",
246 assert_not_reached("Unhandled option code.");
250 log_error("Frequency needs to be > 0");
257 static int do_journal_append(char *file
) {
258 _cleanup_free_
char *bootchart_message
= NULL
;
259 _cleanup_free_
char *bootchart_file
= NULL
;
260 _cleanup_free_
char *p
= NULL
;
261 _cleanup_close_
int fd
= -1;
262 struct iovec iovec
[5];
266 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
270 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
)
277 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
279 p
= malloc(10 + BOOTCHART_MAX
);
283 memcpy(p
, "BOOTCHART=", 10);
285 fd
= open(file
, O_RDONLY
|O_CLOEXEC
);
287 return log_error_errno(errno
, "Failed to open bootchart data \"%s\": %m", file
);
289 n
= loop_read(fd
, p
+ 10, BOOTCHART_MAX
, false);
291 return log_error_errno(n
, "Failed to read bootchart data: %m");
293 iovec
[j
].iov_base
= p
;
294 iovec
[j
].iov_len
= 10 + n
;
297 r
= sd_journal_sendv(iovec
, j
);
299 log_error_errno(r
, "Failed to send bootchart: %m");
304 int main(int argc
, char *argv
[]) {
305 static struct list_sample_data
*sampledata
;
306 _cleanup_closedir_
DIR *proc
= NULL
;
307 _cleanup_free_
char *build
= NULL
;
308 _cleanup_fclose_
FILE *of
= NULL
;
309 _cleanup_close_
int sysfd
= -1;
310 struct ps_struct
*ps_first
;
314 char output_file
[PATH_MAX
];
321 struct ps_struct
*ps
;
323 struct list_sample_data
*head
;
324 struct sigaction sig
= {
325 .sa_handler
= signal_handler
,
330 r
= parse_argv(argc
, argv
);
338 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
340 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
346 execl(arg_init_path
, arg_init_path
, NULL
);
350 rlim
.rlim_cur
= 4096;
351 rlim
.rlim_max
= 4096;
352 (void) setrlimit(RLIMIT_NOFILE
, &rlim
);
354 /* start with empty ps LL */
355 ps_first
= new0(struct ps_struct
, 1);
361 /* handle TERM/INT nicely */
362 sigaction(SIGHUP
, &sig
, NULL
);
364 interval
= (1.0 / arg_hz
) * 1000000000.0;
367 graph_start
= log_start
= gettime_ns();
372 clock_gettime(clock_boottime_or_monotonic(), &n
);
373 uptime
= (n
.tv_sec
+ (n
.tv_nsec
/ (double) NSEC_PER_SEC
));
375 log_start
= gettime_ns();
376 graph_start
= log_start
- uptime
;
379 if (graph_start
< 0.0) {
380 log_error("Failed to setup graph start time.\n\n"
381 "The system uptime probably includes time that the system was suspended. "
382 "Use --rel to bypass this issue.");
386 LIST_HEAD_INIT(head
);
388 /* main program loop */
389 for (samples
= 0; !exiting
&& samples
< arg_samples_len
; samples
++) {
395 sampledata
= new0(struct list_sample_data
, 1);
396 if (sampledata
== NULL
) {
401 sampledata
->sampletime
= gettime_ns();
402 sampledata
->counter
= samples
;
405 sysfd
= open("/sys", O_RDONLY
|O_CLOEXEC
);
408 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
) == -ENOENT
)
409 parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
);
415 proc
= opendir("/proc");
417 /* wait for /proc to become available, discarding samples */
419 r
= log_sample(proc
, samples
, ps_first
, &sampledata
, &pscount
, &n_cpus
);
424 sample_stop
= gettime_ns();
426 elapsed
= (sample_stop
- sampledata
->sampletime
) * 1000000000.0;
427 timeleft
= interval
- elapsed
;
430 * check if we have not consumed our entire timeslice. If we
431 * do, don't sleep and take a new sample right away.
432 * we'll lose all the missed samples and overrun our total
438 req
.tv_sec
= (time_t)(timeleft
/ 1000000000.0);
439 req
.tv_nsec
= (long)(timeleft
- (req
.tv_sec
* 1000000000.0));
441 res
= nanosleep(&req
, NULL
);
444 /* caught signal, probably HUP! */
446 log_error_errno(errno
, "nanosleep() failed: %m");
451 /* calculate how many samples we lost and scrap them */
452 arg_samples_len
-= (int)(-timeleft
/ interval
);
454 LIST_PREPEND(link
, head
, sampledata
);
457 /* do some cleanup, close fd's */
459 while (ps
->next_ps
) {
461 ps
->schedstat
= safe_close(ps
->schedstat
);
462 ps
->sched
= safe_close(ps
->sched
);
463 ps
->smaps
= safe_fclose(ps
->smaps
);
468 r
= strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
471 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
472 of
= fopen(output_file
, "we");
476 log_error("Error opening output file '%s': %m\n", output_file
);
480 r
= svg_do(of
, strna(build
), head
, ps_first
,
481 samples
, pscount
, n_cpus
, graph_start
,
482 log_start
, interval
, overrun
);
485 log_error_errno(r
, "Error generating svg file: %m");
489 log_info("systemd-bootchart wrote %s\n", output_file
);
491 r
= do_journal_append(output_file
);
495 /* nitpic cleanups */
496 ps
= ps_first
->next_ps
;
497 while (ps
->next_ps
) {
498 struct ps_struct
*old
;
501 old
->sample
= ps
->first
;
503 while (old
->sample
->next
) {
504 struct ps_sched_struct
*oldsample
= old
->sample
;
506 old
->sample
= old
->sample
->next
;
519 while (sampledata
->link_prev
) {
520 struct list_sample_data
*old_sampledata
= sampledata
;
521 sampledata
= sampledata
->link_prev
;
522 free(old_sampledata
);
526 /* don't complain when overrun once, happens most commonly on 1st sample */
528 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun
);