]>
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\n", argv
[0]);
213 fprintf(stderr
, "Options:\n");
214 fprintf(stderr
, " -r, --rel Record time relative to recording\n");
215 fprintf(stderr
, " -f, --freq=FREQ Sample frequency [%f]\n", arg_hz
);
216 fprintf(stderr
, " -n, --samples=N Stop sampling at [%d] samples\n", arg_samples_len
);
217 fprintf(stderr
, " -x, --scale-x=N Scale the graph horizontally [%f] \n", arg_scale_x
);
218 fprintf(stderr
, " -y, --scale-y=N Scale the graph vertically [%f] \n", arg_scale_y
);
219 fprintf(stderr
, " -p, --pss Enable PSS graph (CPU intensive)\n");
220 fprintf(stderr
, " -e, --entropy Enable the entropy_avail graph\n");
221 fprintf(stderr
, " -o, --output=PATH Path to output files [%s]\n", arg_output_path
);
222 fprintf(stderr
, " -i, --init=PATH Path to init executable [%s]\n", arg_init_path
);
223 fprintf(stderr
, " -F, --no-filter Disable filtering of processes from the graph\n");
224 fprintf(stderr
, " that are of less importance or short-lived\n");
225 fprintf(stderr
, " -C, --cmdline Display the full command line with arguments\n");
226 fprintf(stderr
, " of processes, instead of only the process name\n");
227 fprintf(stderr
, " -c, --control-group Display process control group\n");
228 fprintf(stderr
, " -h, --help Display this message\n\n");
229 fprintf(stderr
, "See bootchart.conf for more information.\n");
237 fprintf(stderr
, "Error: Frequency needs to be > 0\n");
244 static void do_journal_append(char *file
) {
245 struct iovec iovec
[5];
248 _cleanup_free_
char *bootchart_file
= NULL
, *bootchart_message
= NULL
,
251 bootchart_file
= strappend("BOOTCHART_FILE=", file
);
253 IOVEC_SET_STRING(iovec
[j
++], bootchart_file
);
255 IOVEC_SET_STRING(iovec
[j
++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
256 IOVEC_SET_STRING(iovec
[j
++], "PRIORITY=7");
257 bootchart_message
= strjoin("MESSAGE=Bootchart created: ", file
, NULL
);
258 if (bootchart_message
)
259 IOVEC_SET_STRING(iovec
[j
++], bootchart_message
);
261 p
= malloc(9 + BOOTCHART_MAX
);
267 memcpy(p
, "BOOTCHART=", 10);
269 f
= open(file
, O_RDONLY
|O_CLOEXEC
);
271 log_error("Failed to read bootchart data: %m");
274 n
= loop_read(f
, p
+ 10, BOOTCHART_MAX
, false);
276 log_error("Failed to read bootchart data: %s", strerror(-n
));
282 iovec
[j
].iov_base
= p
;
283 iovec
[j
].iov_len
= 10 + n
;
286 r
= sd_journal_sendv(iovec
, j
);
288 log_error("Failed to send bootchart: %s", strerror(-r
));
291 int main(int argc
, char *argv
[]) {
292 _cleanup_free_
char *build
= NULL
;
293 struct sigaction sig
= {
294 .sa_handler
= signal_handler
,
296 struct ps_struct
*ps
;
297 char output_file
[PATH_MAX
];
305 r
= parse_args(argc
, argv
);
310 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
312 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
318 execl(arg_init_path
, arg_init_path
, NULL
);
323 rlim
.rlim_cur
= 4096;
324 rlim
.rlim_max
= 4096;
325 (void) setrlimit(RLIMIT_NOFILE
, &rlim
);
327 /* start with empty ps LL */
328 ps_first
= new0(struct ps_struct
, 1);
334 /* handle TERM/INT nicely */
335 sigaction(SIGHUP
, &sig
, NULL
);
337 interval
= (1.0 / arg_hz
) * 1000000000.0;
341 LIST_HEAD_INIT(head
);
343 /* main program loop */
344 for (samples
= 0; !exiting
&& samples
< arg_samples_len
; samples
++) {
353 sampledata
= new0(struct list_sample_data
, 1);
354 if (sampledata
== NULL
) {
355 log_error("Failed to allocate memory for a node: %m");
359 sampledata
->sampletime
= gettime_ns();
360 sampledata
->counter
= samples
;
362 if (!of
&& (access(arg_output_path
, R_OK
|W_OK
|X_OK
) == 0)) {
364 strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
365 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
366 of
= fopen(output_file
, "we");
370 sysfd
= open("/sys", O_RDONLY
|O_CLOEXEC
);
373 parse_env_file("/etc/os-release", NEWLINE
,
374 "PRETTY_NAME", &build
,
377 /* wait for /proc to become available, discarding samples */
378 if (graph_start
<= 0.0)
381 log_sample(samples
, &sampledata
);
383 sample_stop
= gettime_ns();
385 elapsed
= (sample_stop
- sampledata
->sampletime
) * 1000000000.0;
386 timeleft
= interval
- elapsed
;
388 newint_s
= (time_t)(timeleft
/ 1000000000.0);
389 newint_ns
= (long)(timeleft
- (newint_s
* 1000000000.0));
392 * check if we have not consumed our entire timeslice. If we
393 * do, don't sleep and take a new sample right away.
394 * we'll lose all the missed samples and overrun our total
397 if (newint_ns
> 0 || newint_s
> 0) {
398 req
.tv_sec
= newint_s
;
399 req
.tv_nsec
= newint_ns
;
401 res
= nanosleep(&req
, NULL
);
403 if (errno
== EINTR
) {
404 /* caught signal, probably HUP! */
407 log_error("nanosleep() failed: %m");
412 /* calculate how many samples we lost and scrap them */
413 arg_samples_len
-= (int)(newint_ns
/ interval
);
415 LIST_PREPEND(link
, head
, sampledata
);
418 /* do some cleanup, close fd's */
420 while (ps
->next_ps
) {
423 close(ps
->schedstat
);
432 strftime(datestr
, sizeof(datestr
), "%Y%m%d-%H%M", localtime(&t
));
433 snprintf(output_file
, PATH_MAX
, "%s/bootchart-%s.svg", arg_output_path
, datestr
);
434 of
= fopen(output_file
, "we");
438 fprintf(stderr
, "opening output file '%s': %m\n", output_file
);
444 fprintf(stderr
, "systemd-bootchart wrote %s\n", output_file
);
446 do_journal_append(output_file
);
455 /* nitpic cleanups */
456 ps
= ps_first
->next_ps
;
457 while (ps
->next_ps
) {
458 struct ps_struct
*old
;
461 old
->sample
= ps
->first
;
463 while (old
->sample
->next
) {
464 struct ps_sched_struct
*oldsample
= old
->sample
;
466 old
->sample
= old
->sample
->next
;
478 while (sampledata
->link_prev
) {
479 struct list_sample_data
*old_sampledata
= sampledata
;
480 sampledata
= sampledata
->link_prev
;
481 free(old_sampledata
);
484 /* don't complain when overrun once, happens most commonly on 1st sample */
486 fprintf(stderr
, "systemd-boochart: Warning: sample time overrun %i times\n", overrun
);