]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
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;
76 bool arg_entropy
= false;
78 bool arg_relative
= false;
79 bool arg_filter
= true;
80 bool arg_show_cmdline
= false;
81 bool arg_show_cgroup
= false;
84 int arg_samples_len
= 500; /* we record len+1 (1 start sample) */
85 double arg_hz
= 25.0; /* 20 seconds log time */
86 double arg_scale_x
= 100.0; /* 100px = 1sec */
87 double arg_scale_y
= 20.0; /* 16px = 1 process bar */
88 static struct list_sample_data
*sampledata
;
89 struct list_sample_data
*head
;
91 char arg_init_path
[PATH_MAX
] = "/sbin/init";
92 char arg_output_path
[PATH_MAX
] = "/run/log";
94 static void signal_handler(int sig
) {
100 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
102 #define BOOTCHART_MAX (16*1024*1024)
104 static void parse_conf(void) {
105 char *init
= NULL
, *output
= NULL
;
106 const ConfigTableItem items
[] = {
107 { "Bootchart", "Samples", config_parse_int
, 0, &arg_samples_len
},
108 { "Bootchart", "Frequency", config_parse_double
, 0, &arg_hz
},
109 { "Bootchart", "Relative", config_parse_bool
, 0, &arg_relative
},
110 { "Bootchart", "Filter", config_parse_bool
, 0, &arg_filter
},
111 { "Bootchart", "Output", config_parse_path
, 0, &output
},
112 { "Bootchart", "Init", config_parse_path
, 0, &init
},
113 { "Bootchart", "PlotMemoryUsage", config_parse_bool
, 0, &arg_pss
},
114 { "Bootchart", "PlotEntropyGraph", config_parse_bool
, 0, &arg_entropy
},
115 { "Bootchart", "ScaleX", config_parse_double
, 0, &arg_scale_x
},
116 { "Bootchart", "ScaleY", config_parse_double
, 0, &arg_scale_y
},
117 { "Bootchart", "ControlGroup", config_parse_bool
, 0, &arg_show_cgroup
},
118 { NULL
, NULL
, NULL
, 0, NULL
}
120 _cleanup_fclose_
FILE *f
;
123 f
= fopen(BOOTCHART_CONF
, "re");
127 r
= config_parse(NULL
, BOOTCHART_CONF
, f
,
128 NULL
, config_item_table_lookup
, (void*) items
, true, false, NULL
);
130 log_warning("Failed to parse configuration file: %s", strerror(-r
));
133 strscpy(arg_init_path
, sizeof(arg_init_path
), init
);
135 strscpy(arg_output_path
, sizeof(arg_output_path
), output
);
138 static int parse_args(int argc
, char *argv
[]) {
139 static struct option options
[] = {
140 {"rel", no_argument
, NULL
, 'r'},
141 {"freq", required_argument
, NULL
, 'f'},
142 {"samples", required_argument
, NULL
, 'n'},
143 {"pss", no_argument
, NULL
, 'p'},
144 {"output", required_argument
, NULL
, 'o'},
145 {"init", required_argument
, NULL
, 'i'},
146 {"no-filter", no_argument
, NULL
, 'F'},
147 {"cmdline", no_argument
, NULL
, 'C'},
148 {"control-group", no_argument
, NULL
, 'c'},
149 {"help", no_argument
, NULL
, 'h'},
150 {"scale-x", required_argument
, NULL
, 'x'},
151 {"scale-y", required_argument
, NULL
, 'y'},
152 {"entropy", no_argument
, NULL
, 'e'},
157 while ((c
= getopt_long(argc
, argv
, "erpf:n:o:i:FCchx:y:", options
, NULL
)) >= 0) {
165 r
= safe_atod(optarg
, &arg_hz
);
167 log_warning("failed to parse --freq/-f argument '%s': %s",
168 optarg
, strerror(-r
));
174 arg_show_cmdline
= true;
177 arg_show_cgroup
= true;
180 r
= safe_atoi(optarg
, &arg_samples_len
);
182 log_warning("failed to parse --samples/-n argument '%s': %s",
183 optarg
, strerror(-r
));
186 path_kill_slashes(optarg
);
187 strscpy(arg_output_path
, sizeof(arg_output_path
), optarg
);
190 path_kill_slashes(optarg
);
191 strscpy(arg_init_path
, sizeof(arg_init_path
), optarg
);
197 r
= safe_atod(optarg
, &arg_scale_x
);
199 log_warning("failed to parse --scale-x/-x argument '%s': %s",
200 optarg
, strerror(-r
));
203 r
= safe_atod(optarg
, &arg_scale_y
);
205 log_warning("failed to parse --scale-y/-y argument '%s': %s",
206 optarg
, strerror(-r
));
212 fprintf(stderr
, "Usage: %s [OPTIONS]\n", argv
[0]);
213 fprintf(stderr
, " --rel, -r Record time relative to recording\n");
214 fprintf(stderr
, " --freq, -f f Sample frequency [%f]\n", arg_hz
);
215 fprintf(stderr
, " --samples, -n N Stop sampling at [%d] samples\n", arg_samples_len
);
216 fprintf(stderr
, " --scale-x, -x N Scale the graph horizontally [%f] \n", arg_scale_x
);
217 fprintf(stderr
, " --scale-y, -y N Scale the graph vertically [%f] \n", arg_scale_y
);
218 fprintf(stderr
, " --pss, -p Enable PSS graph (CPU intensive)\n");
219 fprintf(stderr
, " --entropy, -e Enable the entropy_avail graph\n");
220 fprintf(stderr
, " --output, -o [PATH] Path to output files [%s]\n", arg_output_path
);
221 fprintf(stderr
, " --init, -i [PATH] Path to init executable [%s]\n", arg_init_path
);
222 fprintf(stderr
, " --no-filter, -F Disable filtering of processes from the graph\n");
223 fprintf(stderr
, " that are of less importance or short-lived\n");
224 fprintf(stderr
, " --cmdline, -C Display the full command line with arguments\n");
225 fprintf(stderr
, " of processes, instead of only the process name\n");
226 fprintf(stderr
, " --control-group, -c Display process control group\n");
227 fprintf(stderr
, " --help, -h Display this message\n");
228 fprintf(stderr
, "See bootchart.conf for more information.\n");
236 fprintf(stderr
, "Error: Frequency needs to be > 0\n");
243 static void do_journal_append(char *file
) {
244 struct iovec iovec
[5];
247 _cleanup_free_
char *bootchart_file
= NULL
, *bootchart_message
= NULL
,
250 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
252 IOVEC_SET_STRING(iovec
[j
++], bootchart_file
);
254 IOVEC_SET_STRING(iovec
[j
++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
255 IOVEC_SET_STRING(iovec
[j
++], "PRIORITY=7");
256 bootchart_message
= strjoin("MESSAGE=Bootchart created: ", file
, NULL
);
257 if (bootchart_message
)
258 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
260 p
= malloc(9 + BOOTCHART_MAX
);
266 memcpy(p
, "BOOTCHART=", 10);
268 f
= open(file
, O_RDONLY
|O_CLOEXEC
);
270 log_error("Failed to read bootchart data: %m");
273 n
= loop_read(f
, p
+ 10, BOOTCHART_MAX
, false);
275 log_error("Failed to read bootchart data: %s", strerror(-n
));
281 iovec
[j
].iov_base
= p
;
282 iovec
[j
].iov_len
= 10 + n
;
285 r
= sd_journal_sendv(iovec
, j
);
287 log_error("Failed to send bootchart: %s", strerror(-r
));
290 int main(int argc
, char *argv
[]) {
291 _cleanup_free_
char *build
= NULL
;
292 struct sigaction sig
= {
293 .sa_handler
= signal_handler
,
295 struct ps_struct
*ps
;
296 char output_file
[PATH_MAX
];
304 r
= parse_args(argc
, argv
);
309 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
311 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
317 execl(arg_init_path
, arg_init_path
, NULL
);
322 rlim
.rlim_cur
= 4096;
323 rlim
.rlim_max
= 4096;
324 (void) setrlimit(RLIMIT_NOFILE
, &rlim
);
326 /* start with empty ps LL */
327 ps_first
= new0(struct ps_struct
, 1);
333 /* handle TERM/INT nicely */
334 sigaction(SIGHUP
, &sig
, NULL
);
336 interval
= (1.0 / arg_hz
) * 1000000000.0;
340 LIST_HEAD_INIT(head
);
342 /* main program loop */
343 for (samples
= 0; !exiting
&& samples
< arg_samples_len
; samples
++) {
352 sampledata
= new0(struct list_sample_data
, 1);
353 if (sampledata
== NULL
) {
354 log_error("Failed to allocate memory for a node: %m");
358 sampledata
->sampletime
= gettime_ns();
359 sampledata
->counter
= samples
;
361 if (!of
&& (access(arg_output_path
, R_OK
|W_OK
|X_OK
) == 0)) {
363 strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
364 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
365 of
= fopen(output_file
, "we");
369 sysfd
= open("/sys", O_RDONLY
|O_CLOEXEC
);
372 parse_env_file("/etc/os-release", NEWLINE
,
373 "PRETTY_NAME", &build
,
376 /* wait for /proc to become available, discarding samples */
377 if (graph_start
<= 0.0)
380 log_sample(samples
, &sampledata
);
382 sample_stop
= gettime_ns();
384 elapsed
= (sample_stop
- sampledata
->sampletime
) * 1000000000.0;
385 timeleft
= interval
- elapsed
;
387 newint_s
= (time_t)(timeleft
/ 1000000000.0);
388 newint_ns
= (long)(timeleft
- (newint_s
* 1000000000.0));
391 * check if we have not consumed our entire timeslice. If we
392 * do, don't sleep and take a new sample right away.
393 * we'll lose all the missed samples and overrun our total
396 if (newint_ns
> 0 || newint_s
> 0) {
397 req
.tv_sec
= newint_s
;
398 req
.tv_nsec
= newint_ns
;
400 res
= nanosleep(&req
, NULL
);
402 if (errno
== EINTR
) {
403 /* caught signal, probably HUP! */
406 log_error("nanosleep() failed: %m");
411 /* calculate how many samples we lost and scrap them */
412 arg_samples_len
-= (int)(newint_ns
/ interval
);
414 LIST_PREPEND(link
, head
, sampledata
);
417 /* do some cleanup, close fd's */
419 while (ps
->next_ps
) {
422 close(ps
->schedstat
);
431 strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
432 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
433 of
= fopen(output_file
, "we");
437 fprintf(stderr
, "opening output file '%s': %m\n", output_file
);
443 fprintf(stderr
, "systemd-bootchart wrote %s\n", output_file
);
445 do_journal_append(output_file
);
454 /* nitpic cleanups */
455 ps
= ps_first
->next_ps
;
456 while (ps
->next_ps
) {
457 struct ps_struct
*old
;
460 old
->sample
= ps
->first
;
462 while (old
->sample
->next
) {
463 struct ps_sched_struct
*oldsample
= old
->sample
;
465 old
->sample
= old
->sample
->next
;
477 while (sampledata
->link_prev
) {
478 struct list_sample_data
*old_sampledata
= sampledata
;
479 sampledata
= sampledata
->link_prev
;
480 free(old_sampledata
);
483 /* don't complain when overrun once, happens most commonly on 1st sample */
485 fprintf(stderr
, "systemd-boochart: Warning: sample time overrun %i times\n", overrun
);