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 "/sbin/init"
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;
91 int arg_samples_len
= DEFAULT_SAMPLES_LEN
; /* we record len+1 (1 start sample) */
92 double arg_hz
= DEFAULT_HZ
;
93 double arg_scale_x
= DEFAULT_SCALE_X
;
94 double arg_scale_y
= DEFAULT_SCALE_Y
;
95 static struct list_sample_data
*sampledata
;
96 struct list_sample_data
*head
;
98 char arg_init_path
[PATH_MAX
] = DEFAULT_INIT
;
99 char arg_output_path
[PATH_MAX
] = DEFAULT_OUTPUT
;
101 static void signal_handler(int sig
) {
107 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
109 #define BOOTCHART_MAX (16*1024*1024)
111 static void parse_conf(void) {
112 char *init
= NULL
, *output
= NULL
;
113 const ConfigTableItem items
[] = {
114 { "Bootchart", "Samples", config_parse_int
, 0, &arg_samples_len
},
115 { "Bootchart", "Frequency", config_parse_double
, 0, &arg_hz
},
116 { "Bootchart", "Relative", config_parse_bool
, 0, &arg_relative
},
117 { "Bootchart", "Filter", config_parse_bool
, 0, &arg_filter
},
118 { "Bootchart", "Output", config_parse_path
, 0, &output
},
119 { "Bootchart", "Init", config_parse_path
, 0, &init
},
120 { "Bootchart", "PlotMemoryUsage", config_parse_bool
, 0, &arg_pss
},
121 { "Bootchart", "PlotEntropyGraph", config_parse_bool
, 0, &arg_entropy
},
122 { "Bootchart", "ScaleX", config_parse_double
, 0, &arg_scale_x
},
123 { "Bootchart", "ScaleY", config_parse_double
, 0, &arg_scale_y
},
124 { "Bootchart", "ControlGroup", config_parse_bool
, 0, &arg_show_cgroup
},
125 { NULL
, NULL
, NULL
, 0, NULL
}
128 config_parse(NULL
, BOOTCHART_CONF
, NULL
,
130 config_item_table_lookup
, items
,
131 true, false, true, NULL
);
134 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
136 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
139 static void help(void) {
141 "Usage: %s [OPTIONS]\n\n"
143 " -r, --rel Record time relative to recording\n"
144 " -f, --freq=FREQ Sample frequency [%g]\n"
145 " -n, --samples=N Stop sampling at [%d] samples\n"
146 " -x, --scale-x=N Scale the graph horizontally [%g] \n"
147 " -y, --scale-y=N Scale the graph vertically [%g] \n"
148 " -p, --pss Enable PSS graph (CPU intensive)\n"
149 " -e, --entropy Enable the entropy_avail graph\n"
150 " -o, --output=PATH Path to output files [%s]\n"
151 " -i, --init=PATH Path to init executable [%s]\n"
152 " -F, --no-filter Disable filtering of unimportant or ephemeral processes\n"
153 " -C, --cmdline Display full command lines with arguments\n"
154 " -c, --control-group Display process control group\n"
155 " -h, --help Display this message\n\n"
156 "See bootchart.conf for more information.\n",
157 program_invocation_short_name
,
166 static int parse_args(int argc
, char *argv
[]) {
167 static const struct option options
[] = {
168 {"rel", no_argument
, NULL
, 'r'},
169 {"freq", required_argument
, NULL
, 'f'},
170 {"samples", required_argument
, NULL
, 'n'},
171 {"pss", no_argument
, NULL
, 'p'},
172 {"output", required_argument
, NULL
, 'o'},
173 {"init", required_argument
, NULL
, 'i'},
174 {"no-filter", no_argument
, NULL
, 'F'},
175 {"cmdline", no_argument
, NULL
, 'C'},
176 {"control-group", no_argument
, NULL
, 'c'},
177 {"help", no_argument
, NULL
, 'h'},
178 {"scale-x", required_argument
, NULL
, 'x'},
179 {"scale-y", required_argument
, NULL
, 'y'},
180 {"entropy", no_argument
, NULL
, 'e'},
185 while ((c
= getopt_long(argc
, argv
, "erpf:n:o:i:FCchx:y:", options
, NULL
)) >= 0) {
193 r
= safe_atod(optarg
, &arg_hz
);
195 log_warning("failed to parse --freq/-f argument '%s': %s",
196 optarg
, strerror(-r
));
202 arg_show_cmdline
= true;
205 arg_show_cgroup
= true;
208 r
= safe_atoi(optarg
, &arg_samples_len
);
210 log_warning("failed to parse --samples/-n argument '%s': %s",
211 optarg
, strerror(-r
));
214 path_kill_slashes(optarg
);
215 strscpy(arg_output_path
, sizeof(arg_output_path
), optarg
);
218 path_kill_slashes(optarg
);
219 strscpy(arg_init_path
, sizeof(arg_init_path
), optarg
);
225 r
= safe_atod(optarg
, &arg_scale_x
);
227 log_warning("failed to parse --scale-x/-x argument '%s': %s",
228 optarg
, strerror(-r
));
231 r
= safe_atod(optarg
, &arg_scale_y
);
233 log_warning("failed to parse --scale-y/-y argument '%s': %s",
234 optarg
, strerror(-r
));
248 fprintf(stderr
, "Error: Frequency needs to be > 0\n");
255 static void do_journal_append(char *file
) {
256 struct iovec iovec
[5];
259 _cleanup_free_
char *bootchart_file
= NULL
, *bootchart_message
= NULL
,
262 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
264 IOVEC_SET_STRING(iovec
[j
++], bootchart_file
);
266 IOVEC_SET_STRING(iovec
[j
++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
267 IOVEC_SET_STRING(iovec
[j
++], "PRIORITY=7");
268 bootchart_message
= strjoin("MESSAGE=Bootchart created: ", file
, NULL
);
269 if (bootchart_message
)
270 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
272 p
= malloc(9 + BOOTCHART_MAX
);
278 memcpy(p
, "BOOTCHART=", 10);
280 f
= open(file
, O_RDONLY
|O_CLOEXEC
);
282 log_error("Failed to read bootchart data: %m");
285 n
= loop_read(f
, p
+ 10, BOOTCHART_MAX
, false);
287 log_error("Failed to read bootchart data: %s", strerror(-n
));
293 iovec
[j
].iov_base
= p
;
294 iovec
[j
].iov_len
= 10 + n
;
297 r
= sd_journal_sendv(iovec
, j
);
299 log_error("Failed to send bootchart: %s", strerror(-r
));
302 int main(int argc
, char *argv
[]) {
303 _cleanup_free_
char *build
= NULL
;
304 struct sigaction sig
= {
305 .sa_handler
= signal_handler
,
307 struct ps_struct
*ps
;
308 char output_file
[PATH_MAX
];
316 r
= parse_args(argc
, argv
);
321 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
323 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
329 execl(arg_init_path
, arg_init_path
, NULL
);
334 rlim
.rlim_cur
= 4096;
335 rlim
.rlim_max
= 4096;
336 (void) setrlimit(RLIMIT_NOFILE
, &rlim
);
338 /* start with empty ps LL */
339 ps_first
= new0(struct ps_struct
, 1);
345 /* handle TERM/INT nicely */
346 sigaction(SIGHUP
, &sig
, NULL
);
348 interval
= (1.0 / arg_hz
) * 1000000000.0;
352 LIST_HEAD_INIT(head
);
354 /* main program loop */
355 for (samples
= 0; !exiting
&& samples
< arg_samples_len
; samples
++) {
364 sampledata
= new0(struct list_sample_data
, 1);
365 if (sampledata
== NULL
) {
366 log_error("Failed to allocate memory for a node: %m");
370 sampledata
->sampletime
= gettime_ns();
371 sampledata
->counter
= samples
;
373 if (!of
&& (access(arg_output_path
, R_OK
|W_OK
|X_OK
) == 0)) {
375 strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
376 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
377 of
= fopen(output_file
, "we");
381 sysfd
= open("/sys", O_RDONLY
|O_CLOEXEC
);
384 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
) == -ENOENT
)
385 parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &build
, NULL
);
388 /* wait for /proc to become available, discarding samples */
389 if (graph_start
<= 0.0)
392 log_sample(samples
, &sampledata
);
394 sample_stop
= gettime_ns();
396 elapsed
= (sample_stop
- sampledata
->sampletime
) * 1000000000.0;
397 timeleft
= interval
- elapsed
;
399 newint_s
= (time_t)(timeleft
/ 1000000000.0);
400 newint_ns
= (long)(timeleft
- (newint_s
* 1000000000.0));
403 * check if we have not consumed our entire timeslice. If we
404 * do, don't sleep and take a new sample right away.
405 * we'll lose all the missed samples and overrun our total
408 if (newint_ns
> 0 || newint_s
> 0) {
409 req
.tv_sec
= newint_s
;
410 req
.tv_nsec
= newint_ns
;
412 res
= nanosleep(&req
, NULL
);
414 if (errno
== EINTR
) {
415 /* caught signal, probably HUP! */
418 log_error("nanosleep() failed: %m");
423 /* calculate how many samples we lost and scrap them */
424 arg_samples_len
-= (int)(newint_ns
/ interval
);
426 LIST_PREPEND(link
, head
, sampledata
);
429 /* do some cleanup, close fd's */
431 while (ps
->next_ps
) {
434 close(ps
->schedstat
);
443 strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
444 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
445 of
= fopen(output_file
, "we");
449 fprintf(stderr
, "opening output file '%s': %m\n", output_file
);
455 fprintf(stderr
, "systemd-bootchart wrote %s\n", output_file
);
457 do_journal_append(output_file
);
466 /* nitpic cleanups */
467 ps
= ps_first
->next_ps
;
468 while (ps
->next_ps
) {
469 struct ps_struct
*old
;
472 old
->sample
= ps
->first
;
474 while (old
->sample
->next
) {
475 struct ps_sched_struct
*oldsample
= old
->sample
;
477 old
->sample
= old
->sample
->next
;
489 while (sampledata
->link_prev
) {
490 struct list_sample_data
*old_sampledata
= sampledata
;
491 sampledata
= sampledata
->link_prev
;
492 free(old_sampledata
);
495 /* don't complain when overrun once, happens most commonly on 1st sample */
497 fprintf(stderr
, "systemd-boochart: Warning: sample time overrun %i times\n", overrun
);