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"
63 struct ps_struct
*ps_first
;
69 static int exiting
= 0;
72 #define DEFAULT_SAMPLES_LEN 500
73 #define DEFAULT_HZ 25.0
74 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
75 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
76 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
77 #define DEFAULT_OUTPUT "/run/log"
80 bool arg_entropy
= false;
82 bool arg_relative
= false;
83 bool arg_filter
= true;
84 bool arg_show_cmdline
= false;
85 bool arg_show_cgroup
= false;
87 bool arg_percpu
= false;
89 int arg_samples_len
= DEFAULT_SAMPLES_LEN
; /* we record len+1 (1 start sample) */
90 double arg_hz
= DEFAULT_HZ
;
91 double arg_scale_x
= DEFAULT_SCALE_X
;
92 double arg_scale_y
= DEFAULT_SCALE_Y
;
93 static struct list_sample_data
*sampledata
;
94 struct list_sample_data
*head
;
96 char arg_init_path
[PATH_MAX
] = DEFAULT_INIT
;
97 char arg_output_path
[PATH_MAX
] = DEFAULT_OUTPUT
;
99 static void signal_handler(int sig
) {
105 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
107 #define BOOTCHART_MAX (16*1024*1024)
109 static void parse_conf(void) {
110 char *init
= NULL
, *output
= NULL
;
111 const ConfigTableItem items
[] = {
112 { "Bootchart", "Samples", config_parse_int
, 0, &arg_samples_len
},
113 { "Bootchart", "Frequency", config_parse_double
, 0, &arg_hz
},
114 { "Bootchart", "Relative", config_parse_bool
, 0, &arg_relative
},
115 { "Bootchart", "Filter", config_parse_bool
, 0, &arg_filter
},
116 { "Bootchart", "Output", config_parse_path
, 0, &output
},
117 { "Bootchart", "Init", config_parse_path
, 0, &init
},
118 { "Bootchart", "PlotMemoryUsage", config_parse_bool
, 0, &arg_pss
},
119 { "Bootchart", "PlotEntropyGraph", config_parse_bool
, 0, &arg_entropy
},
120 { "Bootchart", "ScaleX", config_parse_double
, 0, &arg_scale_x
},
121 { "Bootchart", "ScaleY", config_parse_double
, 0, &arg_scale_y
},
122 { "Bootchart", "ControlGroup", config_parse_bool
, 0, &arg_show_cgroup
},
123 { "Bootchart", "PerCPU", config_parse_bool
, 0, &arg_percpu
},
124 { NULL
, NULL
, NULL
, 0, NULL
}
127 config_parse_many(BOOTCHART_CONF
,
128 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
129 NULL
, config_item_table_lookup
, items
, true, NULL
);
132 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
134 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
137 static void help(void) {
138 printf("Usage: %s [OPTIONS]\n\n"
140 " -r --rel Record time relative to recording\n"
141 " -f --freq=FREQ Sample frequency [%g]\n"
142 " -n --samples=N Stop sampling at [%d] samples\n"
143 " -x --scale-x=N Scale the graph horizontally [%g] \n"
144 " -y --scale-y=N Scale the graph vertically [%g] \n"
145 " -p --pss Enable PSS graph (CPU intensive)\n"
146 " -e --entropy Enable the entropy_avail graph\n"
147 " -o --output=PATH Path to output files [%s]\n"
148 " -i --init=PATH Path to init executable [%s]\n"
149 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
150 " -C --cmdline Display full command lines with arguments\n"
151 " -c --control-group Display process control group\n"
152 " --per-cpu Draw each CPU utilization and wait bar also\n"
153 " -h --help Display this message\n\n"
154 "See bootchart.conf for more information.\n",
155 program_invocation_short_name
,
164 static int parse_argv(int argc
, char *argv
[]) {
170 static const struct option options
[] = {
171 {"rel", no_argument
, NULL
, 'r' },
172 {"freq", required_argument
, NULL
, 'f' },
173 {"samples", required_argument
, NULL
, 'n' },
174 {"pss", no_argument
, NULL
, 'p' },
175 {"output", required_argument
, NULL
, 'o' },
176 {"init", required_argument
, NULL
, 'i' },
177 {"no-filter", no_argument
, NULL
, 'F' },
178 {"cmdline", no_argument
, NULL
, 'C' },
179 {"control-group", no_argument
, NULL
, 'c' },
180 {"help", no_argument
, NULL
, 'h' },
181 {"scale-x", required_argument
, NULL
, 'x' },
182 {"scale-y", required_argument
, NULL
, 'y' },
183 {"entropy", no_argument
, NULL
, 'e' },
184 {"per-cpu", no_argument
, NULL
, ARG_PERCPU
},
192 while ((c
= getopt_long(argc
, argv
, "erpf:n:o:i:FCchx:y:", options
, NULL
)) >= 0)
199 r
= safe_atod(optarg
, &arg_hz
);
201 log_warning_errno(r
, "failed to parse --freq/-f argument '%s': %m",
208 arg_show_cmdline
= true;
211 arg_show_cgroup
= true;
214 r
= safe_atoi(optarg
, &arg_samples_len
);
216 log_warning_errno(r
, "failed to parse --samples/-n argument '%s': %m",
220 path_kill_slashes(optarg
);
221 strscpy(arg_output_path
, sizeof(arg_output_path
), optarg
);
224 path_kill_slashes(optarg
);
225 strscpy(arg_init_path
, sizeof(arg_init_path
), optarg
);
231 r
= safe_atod(optarg
, &arg_scale_x
);
233 log_warning_errno(r
, "failed to parse --scale-x/-x argument '%s': %m",
237 r
= safe_atod(optarg
, &arg_scale_y
);
239 log_warning_errno(r
, "failed to parse --scale-y/-y argument '%s': %m",
257 assert_not_reached("Unhandled option code.");
261 log_error("Frequency needs to be > 0");
268 static void do_journal_append(char *file
) {
269 struct iovec iovec
[5];
272 _cleanup_free_
char *bootchart_file
= NULL
, *bootchart_message
= NULL
,
274 _cleanup_close_
int fd
= -1;
276 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
278 IOVEC_SET_STRING(iovec
[j
++], bootchart_file
);
280 IOVEC_SET_STRING(iovec
[j
++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
281 IOVEC_SET_STRING(iovec
[j
++], "PRIORITY=7");
282 bootchart_message
= strjoin("MESSAGE=Bootchart created: ", file
, NULL
);
283 if (bootchart_message
)
284 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
286 p
= malloc(9 + BOOTCHART_MAX
);
292 memcpy(p
, "BOOTCHART=", 10);
294 fd
= open(file
, O_RDONLY
|O_CLOEXEC
);
296 log_error_errno(errno
, "Failed to open bootchart data \"%s\": %m", file
);
300 n
= loop_read(fd
, p
+ 10, BOOTCHART_MAX
, false);
302 log_error_errno(n
, "Failed to read bootchart data: %m");
306 iovec
[j
].iov_base
= p
;
307 iovec
[j
].iov_len
= 10 + n
;
310 r
= sd_journal_sendv(iovec
, j
);
312 log_error_errno(r
, "Failed to send bootchart: %m");
315 int main(int argc
, char *argv
[]) {
316 _cleanup_free_
char *build
= NULL
;
317 struct sigaction sig
= {
318 .sa_handler
= signal_handler
,
320 struct ps_struct
*ps
;
321 char output_file
[PATH_MAX
];
326 bool has_procfs
= false;
330 r
= parse_argv(argc
, argv
);
332 return r
== 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;
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;
366 if (graph_start
< 0.0) {
367 log_error("Failed to setup graph start time.\n\n"
368 "The system uptime probably includes time that the system was suspended. "
369 "Use --rel to bypass this issue.");
373 has_procfs
= access("/proc/vmstat", F_OK
) == 0;
375 LIST_HEAD_INIT(head
);
377 /* main program loop */
378 for (samples
= 0; !exiting
&& samples
< arg_samples_len
; samples
++) {
387 sampledata
= new0(struct list_sample_data
, 1);
388 if (sampledata
== NULL
) {
393 sampledata
->sampletime
= gettime_ns();
394 sampledata
->counter
= samples
;
397 sysfd
= open("/sys", O_RDONLY
|O_CLOEXEC
);
400 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
) == -ENOENT
)
401 parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
);
405 log_sample(samples
, &sampledata
);
407 /* wait for /proc to become available, discarding samples */
408 has_procfs
= access("/proc/vmstat", F_OK
) == 0;
410 sample_stop
= gettime_ns();
412 elapsed
= (sample_stop
- sampledata
->sampletime
) * 1000000000.0;
413 timeleft
= interval
- elapsed
;
415 newint_s
= (time_t)(timeleft
/ 1000000000.0);
416 newint_ns
= (long)(timeleft
- (newint_s
* 1000000000.0));
419 * check if we have not consumed our entire timeslice. If we
420 * do, don't sleep and take a new sample right away.
421 * we'll lose all the missed samples and overrun our total
424 if (newint_ns
> 0 || newint_s
> 0) {
425 req
.tv_sec
= newint_s
;
426 req
.tv_nsec
= newint_ns
;
428 res
= nanosleep(&req
, NULL
);
430 if (errno
== EINTR
) {
431 /* caught signal, probably HUP! */
434 log_error_errno(errno
, "nanosleep() failed: %m");
439 /* calculate how many samples we lost and scrap them */
440 arg_samples_len
-= (int)(newint_ns
/ interval
);
442 LIST_PREPEND(link
, head
, sampledata
);
445 /* do some cleanup, close fd's */
447 while (ps
->next_ps
) {
449 if (ps
->schedstat
>= 0)
450 close(ps
->schedstat
);
459 r
= strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
462 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
463 of
= fopen(output_file
, "we");
467 fprintf(stderr
, "opening output file '%s': %m\n", output_file
);
471 svg_do(strna(build
));
473 fprintf(stderr
, "systemd-bootchart wrote %s\n", output_file
);
475 do_journal_append(output_file
);
484 /* nitpic cleanups */
485 ps
= ps_first
->next_ps
;
486 while (ps
->next_ps
) {
487 struct ps_struct
*old
;
490 old
->sample
= ps
->first
;
492 while (old
->sample
->next
) {
493 struct ps_sched_struct
*oldsample
= old
->sample
;
495 old
->sample
= old
->sample
->next
;
507 while (sampledata
->link_prev
) {
508 struct list_sample_data
*old_sampledata
= sampledata
;
509 sampledata
= sampledata
->link_prev
;
510 free(old_sampledata
);
513 /* don't complain when overrun once, happens most commonly on 1st sample */
515 log_warning("systemd-boochart: sample time overrun %i times\n", overrun
);