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
37 #include <sys/types.h>
38 #include <sys/resource.h>
51 #include "systemd/sd-journal.h"
56 #include "conf-parser.h"
58 #include "path-util.h"
61 #include "bootchart.h"
66 struct ps_struct
*ps_first
;
72 static int exiting
= 0;
75 #define DEFAULT_SAMPLES_LEN 500
76 #define DEFAULT_HZ 25.0
77 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
78 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
79 #define DEFAULT_INIT ROOTLIBDIR "/systemd/systemd"
80 #define DEFAULT_OUTPUT "/run/log"
83 bool arg_entropy
= false;
85 bool arg_relative
= false;
86 bool arg_filter
= true;
87 bool arg_show_cmdline
= false;
88 bool arg_show_cgroup
= false;
90 bool arg_percpu
= false;
92 int arg_samples_len
= DEFAULT_SAMPLES_LEN
; /* we record len+1 (1 start sample) */
93 double arg_hz
= DEFAULT_HZ
;
94 double arg_scale_x
= DEFAULT_SCALE_X
;
95 double arg_scale_y
= DEFAULT_SCALE_Y
;
96 static struct list_sample_data
*sampledata
;
97 struct list_sample_data
*head
;
99 char arg_init_path
[PATH_MAX
] = DEFAULT_INIT
;
100 char arg_output_path
[PATH_MAX
] = DEFAULT_OUTPUT
;
102 static void signal_handler(int sig
) {
108 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
110 #define BOOTCHART_MAX (16*1024*1024)
112 static void parse_conf(void) {
113 char *init
= NULL
, *output
= NULL
;
114 const ConfigTableItem items
[] = {
115 { "Bootchart", "Samples", config_parse_int
, 0, &arg_samples_len
},
116 { "Bootchart", "Frequency", config_parse_double
, 0, &arg_hz
},
117 { "Bootchart", "Relative", config_parse_bool
, 0, &arg_relative
},
118 { "Bootchart", "Filter", config_parse_bool
, 0, &arg_filter
},
119 { "Bootchart", "Output", config_parse_path
, 0, &output
},
120 { "Bootchart", "Init", config_parse_path
, 0, &init
},
121 { "Bootchart", "PlotMemoryUsage", config_parse_bool
, 0, &arg_pss
},
122 { "Bootchart", "PlotEntropyGraph", config_parse_bool
, 0, &arg_entropy
},
123 { "Bootchart", "ScaleX", config_parse_double
, 0, &arg_scale_x
},
124 { "Bootchart", "ScaleY", config_parse_double
, 0, &arg_scale_y
},
125 { "Bootchart", "ControlGroup", config_parse_bool
, 0, &arg_show_cgroup
},
126 { "Bootchart", "PerCPU", config_parse_bool
, 0, &arg_percpu
},
127 { NULL
, NULL
, NULL
, 0, NULL
}
130 config_parse_many(BOOTCHART_CONF
,
131 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
132 NULL
, config_item_table_lookup
, items
, true, NULL
);
135 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
137 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
140 static void help(void) {
142 "Usage: %s [OPTIONS]\n\n"
144 " -r, --rel Record time relative to recording\n"
145 " -f, --freq=FREQ Sample frequency [%g]\n"
146 " -n, --samples=N Stop sampling at [%d] samples\n"
147 " -x, --scale-x=N Scale the graph horizontally [%g] \n"
148 " -y, --scale-y=N Scale the graph vertically [%g] \n"
149 " -p, --pss Enable PSS graph (CPU intensive)\n"
150 " -e, --entropy Enable the entropy_avail graph\n"
151 " -o, --output=PATH Path to output files [%s]\n"
152 " -i, --init=PATH Path to init executable [%s]\n"
153 " -F, --no-filter Disable filtering of unimportant or ephemeral processes\n"
154 " -C, --cmdline Display full command lines with arguments\n"
155 " -c, --control-group Display process control group\n"
156 " --per-cpu Draw each CPU utilization and wait bar also\n"
157 " -h, --help Display this message\n\n"
158 "See bootchart.conf for more information.\n",
159 program_invocation_short_name
,
168 static int parse_argv(int argc
, char *argv
[]) {
174 static const struct option options
[] = {
175 {"rel", no_argument
, NULL
, 'r' },
176 {"freq", required_argument
, NULL
, 'f' },
177 {"samples", required_argument
, NULL
, 'n' },
178 {"pss", no_argument
, NULL
, 'p' },
179 {"output", required_argument
, NULL
, 'o' },
180 {"init", required_argument
, NULL
, 'i' },
181 {"no-filter", no_argument
, NULL
, 'F' },
182 {"cmdline", no_argument
, NULL
, 'C' },
183 {"control-group", no_argument
, NULL
, 'c' },
184 {"help", no_argument
, NULL
, 'h' },
185 {"scale-x", required_argument
, NULL
, 'x' },
186 {"scale-y", required_argument
, NULL
, 'y' },
187 {"entropy", no_argument
, NULL
, 'e' },
188 {"per-cpu", no_argument
, NULL
, ARG_PERCPU
},
196 while ((c
= getopt_long(argc
, argv
, "erpf:n:o:i:FCchx:y:", options
, NULL
)) >= 0)
203 r
= safe_atod(optarg
, &arg_hz
);
205 log_warning_errno(r
, "failed to parse --freq/-f argument '%s': %m",
212 arg_show_cmdline
= true;
215 arg_show_cgroup
= true;
218 r
= safe_atoi(optarg
, &arg_samples_len
);
220 log_warning_errno(r
, "failed to parse --samples/-n argument '%s': %m",
224 path_kill_slashes(optarg
);
225 strscpy(arg_output_path
, sizeof(arg_output_path
), optarg
);
228 path_kill_slashes(optarg
);
229 strscpy(arg_init_path
, sizeof(arg_init_path
), optarg
);
235 r
= safe_atod(optarg
, &arg_scale_x
);
237 log_warning_errno(r
, "failed to parse --scale-x/-x argument '%s': %m",
241 r
= safe_atod(optarg
, &arg_scale_y
);
243 log_warning_errno(r
, "failed to parse --scale-y/-y argument '%s': %m",
261 assert_not_reached("Unhandled option code.");
265 log_error("Frequency needs to be > 0");
272 static void do_journal_append(char *file
) {
273 struct iovec iovec
[5];
276 _cleanup_free_
char *bootchart_file
= NULL
, *bootchart_message
= NULL
,
279 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
281 IOVEC_SET_STRING(iovec
[j
++], bootchart_file
);
283 IOVEC_SET_STRING(iovec
[j
++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
284 IOVEC_SET_STRING(iovec
[j
++], "PRIORITY=7");
285 bootchart_message
= strjoin("MESSAGE=Bootchart created: ", file
, NULL
);
286 if (bootchart_message
)
287 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
289 p
= malloc(9 + BOOTCHART_MAX
);
295 memcpy(p
, "BOOTCHART=", 10);
297 f
= open(file
, O_RDONLY
|O_CLOEXEC
);
299 log_error_errno(errno
, "Failed to read bootchart data: %m");
302 n
= loop_read(f
, p
+ 10, BOOTCHART_MAX
, false);
304 log_error_errno(n
, "Failed to read bootchart data: %m");
310 iovec
[j
].iov_base
= p
;
311 iovec
[j
].iov_len
= 10 + n
;
314 r
= sd_journal_sendv(iovec
, j
);
316 log_error_errno(r
, "Failed to send bootchart: %m");
319 int main(int argc
, char *argv
[]) {
320 _cleanup_free_
char *build
= NULL
;
321 struct sigaction sig
= {
322 .sa_handler
= signal_handler
,
324 struct ps_struct
*ps
;
325 char output_file
[PATH_MAX
];
330 bool has_procfs
= false;
334 r
= parse_argv(argc
, argv
);
336 return r
== 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;
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
);
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;
370 if (graph_start
< 0.0) {
372 "Failed to setup graph start time.\n\nThe system uptime "
373 "probably includes time that the system was suspended. "
374 "Use --rel to bypass this issue.\n");
378 has_procfs
= access("/proc/vmstat", F_OK
) == 0;
380 LIST_HEAD_INIT(head
);
382 /* main program loop */
383 for (samples
= 0; !exiting
&& samples
< arg_samples_len
; samples
++) {
392 sampledata
= new0(struct list_sample_data
, 1);
393 if (sampledata
== NULL
) {
398 sampledata
->sampletime
= gettime_ns();
399 sampledata
->counter
= samples
;
401 if (!of
&& (access(arg_output_path
, R_OK
|W_OK
|X_OK
) == 0)) {
403 r
= strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
406 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
407 of
= fopen(output_file
, "we");
411 sysfd
= open("/sys", O_RDONLY
|O_CLOEXEC
);
414 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
) == -ENOENT
)
415 parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
);
419 log_sample(samples
, &sampledata
);
421 /* wait for /proc to become available, discarding samples */
422 has_procfs
= access("/proc/vmstat", F_OK
) == 0;
424 sample_stop
= gettime_ns();
426 elapsed
= (sample_stop
- sampledata
->sampletime
) * 1000000000.0;
427 timeleft
= interval
- elapsed
;
429 newint_s
= (time_t)(timeleft
/ 1000000000.0);
430 newint_ns
= (long)(timeleft
- (newint_s
* 1000000000.0));
433 * check if we have not consumed our entire timeslice. If we
434 * do, don't sleep and take a new sample right away.
435 * we'll lose all the missed samples and overrun our total
438 if (newint_ns
> 0 || newint_s
> 0) {
439 req
.tv_sec
= newint_s
;
440 req
.tv_nsec
= newint_ns
;
442 res
= nanosleep(&req
, NULL
);
444 if (errno
== EINTR
) {
445 /* 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)(newint_ns
/ interval
);
456 LIST_PREPEND(link
, head
, sampledata
);
459 /* do some cleanup, close fd's */
461 while (ps
->next_ps
) {
464 close(ps
->schedstat
);
473 r
= strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
476 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
477 of
= fopen(output_file
, "we");
481 fprintf(stderr
, "opening output file '%s': %m\n", output_file
);
485 svg_do(strna(build
));
487 fprintf(stderr
, "systemd-bootchart wrote %s\n", output_file
);
489 do_journal_append(output_file
);
498 /* nitpic cleanups */
499 ps
= ps_first
->next_ps
;
500 while (ps
->next_ps
) {
501 struct ps_struct
*old
;
504 old
->sample
= ps
->first
;
506 while (old
->sample
->next
) {
507 struct ps_sched_struct
*oldsample
= old
->sample
;
509 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
);
527 /* don't complain when overrun once, happens most commonly on 1st sample */
529 fprintf(stderr
, "systemd-boochart: Warning: sample time overrun %i times\n", overrun
);