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
}
127 _cleanup_fclose_
FILE *f
;
130 f
= fopen(BOOTCHART_CONF
, "re");
134 r
= config_parse(NULL
, BOOTCHART_CONF
, f
,
135 NULL
, config_item_table_lookup
, (void*) items
, true, false, NULL
);
137 log_warning("Failed to parse configuration file: %s", strerror(-r
));
140 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
142 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
145 static void help(void) {
147 "Usage: %s [OPTIONS]\n\n"
149 " -r, --rel Record time relative to recording\n"
150 " -f, --freq=FREQ Sample frequency [%g]\n"
151 " -n, --samples=N Stop sampling at [%d] samples\n"
152 " -x, --scale-x=N Scale the graph horizontally [%g] \n"
153 " -y, --scale-y=N Scale the graph vertically [%g] \n"
154 " -p, --pss Enable PSS graph (CPU intensive)\n"
155 " -e, --entropy Enable the entropy_avail graph\n"
156 " -o, --output=PATH Path to output files [%s]\n"
157 " -i, --init=PATH Path to init executable [%s]\n"
158 " -F, --no-filter Disable filtering of unimportant or ephemeral processes\n"
159 " -C, --cmdline Display full command lines with arguments\n"
160 " -c, --control-group Display process control group\n"
161 " -h, --help Display this message\n\n"
162 "See bootchart.conf for more information.\n",
163 program_invocation_short_name
,
172 static int parse_args(int argc
, char *argv
[]) {
173 static struct option options
[] = {
174 {"rel", no_argument
, NULL
, 'r'},
175 {"freq", required_argument
, NULL
, 'f'},
176 {"samples", required_argument
, NULL
, 'n'},
177 {"pss", no_argument
, NULL
, 'p'},
178 {"output", required_argument
, NULL
, 'o'},
179 {"init", required_argument
, NULL
, 'i'},
180 {"no-filter", no_argument
, NULL
, 'F'},
181 {"cmdline", no_argument
, NULL
, 'C'},
182 {"control-group", no_argument
, NULL
, 'c'},
183 {"help", no_argument
, NULL
, 'h'},
184 {"scale-x", required_argument
, NULL
, 'x'},
185 {"scale-y", required_argument
, NULL
, 'y'},
186 {"entropy", no_argument
, NULL
, 'e'},
191 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("failed to parse --freq/-f argument '%s': %s",
202 optarg
, strerror(-r
));
208 arg_show_cmdline
= true;
211 arg_show_cgroup
= true;
214 r
= safe_atoi(optarg
, &arg_samples_len
);
216 log_warning("failed to parse --samples/-n argument '%s': %s",
217 optarg
, strerror(-r
));
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("failed to parse --scale-x/-x argument '%s': %s",
234 optarg
, strerror(-r
));
237 r
= safe_atod(optarg
, &arg_scale_y
);
239 log_warning("failed to parse --scale-y/-y argument '%s': %s",
240 optarg
, strerror(-r
));
254 fprintf(stderr
, "Error: Frequency needs to be > 0\n");
261 static void do_journal_append(char *file
) {
262 struct iovec iovec
[5];
265 _cleanup_free_
char *bootchart_file
= NULL
, *bootchart_message
= NULL
,
268 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
270 IOVEC_SET_STRING(iovec
[j
++], bootchart_file
);
272 IOVEC_SET_STRING(iovec
[j
++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
273 IOVEC_SET_STRING(iovec
[j
++], "PRIORITY=7");
274 bootchart_message
= strjoin("MESSAGE=Bootchart created: ", file
, NULL
);
275 if (bootchart_message
)
276 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
278 p
= malloc(9 + BOOTCHART_MAX
);
284 memcpy(p
, "BOOTCHART=", 10);
286 f
= open(file
, O_RDONLY
|O_CLOEXEC
);
288 log_error("Failed to read bootchart data: %m");
291 n
= loop_read(f
, p
+ 10, BOOTCHART_MAX
, false);
293 log_error("Failed to read bootchart data: %s", strerror(-n
));
299 iovec
[j
].iov_base
= p
;
300 iovec
[j
].iov_len
= 10 + n
;
303 r
= sd_journal_sendv(iovec
, j
);
305 log_error("Failed to send bootchart: %s", strerror(-r
));
308 int main(int argc
, char *argv
[]) {
309 _cleanup_free_
char *build
= NULL
;
310 struct sigaction sig
= {
311 .sa_handler
= signal_handler
,
313 struct ps_struct
*ps
;
314 char output_file
[PATH_MAX
];
322 r
= parse_args(argc
, argv
);
327 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
329 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
335 execl(arg_init_path
, arg_init_path
, NULL
);
340 rlim
.rlim_cur
= 4096;
341 rlim
.rlim_max
= 4096;
342 (void) setrlimit(RLIMIT_NOFILE
, &rlim
);
344 /* start with empty ps LL */
345 ps_first
= new0(struct ps_struct
, 1);
351 /* handle TERM/INT nicely */
352 sigaction(SIGHUP
, &sig
, NULL
);
354 interval
= (1.0 / arg_hz
) * 1000000000.0;
358 LIST_HEAD_INIT(head
);
360 /* main program loop */
361 for (samples
= 0; !exiting
&& samples
< arg_samples_len
; samples
++) {
370 sampledata
= new0(struct list_sample_data
, 1);
371 if (sampledata
== NULL
) {
372 log_error("Failed to allocate memory for a node: %m");
376 sampledata
->sampletime
= gettime_ns();
377 sampledata
->counter
= samples
;
379 if (!of
&& (access(arg_output_path
, R_OK
|W_OK
|X_OK
) == 0)) {
381 strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
382 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
383 of
= fopen(output_file
, "we");
387 sysfd
= open("/sys", O_RDONLY
|O_CLOEXEC
);
390 parse_env_file("/etc/os-release", NEWLINE
,
391 "PRETTY_NAME", &build
,
394 /* wait for /proc to become available, discarding samples */
395 if (graph_start
<= 0.0)
398 log_sample(samples
, &sampledata
);
400 sample_stop
= gettime_ns();
402 elapsed
= (sample_stop
- sampledata
->sampletime
) * 1000000000.0;
403 timeleft
= interval
- elapsed
;
405 newint_s
= (time_t)(timeleft
/ 1000000000.0);
406 newint_ns
= (long)(timeleft
- (newint_s
* 1000000000.0));
409 * check if we have not consumed our entire timeslice. If we
410 * do, don't sleep and take a new sample right away.
411 * we'll lose all the missed samples and overrun our total
414 if (newint_ns
> 0 || newint_s
> 0) {
415 req
.tv_sec
= newint_s
;
416 req
.tv_nsec
= newint_ns
;
418 res
= nanosleep(&req
, NULL
);
420 if (errno
== EINTR
) {
421 /* caught signal, probably HUP! */
424 log_error("nanosleep() failed: %m");
429 /* calculate how many samples we lost and scrap them */
430 arg_samples_len
-= (int)(newint_ns
/ interval
);
432 LIST_PREPEND(link
, head
, sampledata
);
435 /* do some cleanup, close fd's */
437 while (ps
->next_ps
) {
440 close(ps
->schedstat
);
449 strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
450 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
451 of
= fopen(output_file
, "we");
455 fprintf(stderr
, "opening output file '%s': %m\n", output_file
);
461 fprintf(stderr
, "systemd-bootchart wrote %s\n", output_file
);
463 do_journal_append(output_file
);
472 /* nitpic cleanups */
473 ps
= ps_first
->next_ps
;
474 while (ps
->next_ps
) {
475 struct ps_struct
*old
;
478 old
->sample
= ps
->first
;
480 while (old
->sample
->next
) {
481 struct ps_sched_struct
*oldsample
= old
->sample
;
483 old
->sample
= old
->sample
->next
;
495 while (sampledata
->link_prev
) {
496 struct list_sample_data
*old_sampledata
= sampledata
;
497 sampledata
= sampledata
->link_prev
;
498 free(old_sampledata
);
501 /* don't complain when overrun once, happens most commonly on 1st sample */
503 fprintf(stderr
, "systemd-boochart: Warning: sample time overrun %i times\n", overrun
);