]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
util-lib: move CONF_DIRS_NULSTR definition to def.h
[thirdparty/systemd.git] / src / bootchart / bootchart.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright (C) 2009-2013 Intel Corporation
7
8 Authors:
9 Auke Kok <auke-jan.h.kok@intel.com>
10
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.
15
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.
20
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/>.
23 ***/
24
25 /***
26
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
33
34 ***/
35
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <getopt.h>
39 #include <limits.h>
40 #include <signal.h>
41 #include <stdbool.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <sys/resource.h>
46 #include <time.h>
47 #include <unistd.h>
48
49 #include "sd-journal.h"
50
51 #include "alloc-util.h"
52 #include "bootchart.h"
53 #include "conf-parser.h"
54 #include "def.h"
55 #include "fd-util.h"
56 #include "fileio.h"
57 #include "io-util.h"
58 #include "list.h"
59 #include "macro.h"
60 #include "parse-util.h"
61 #include "path-util.h"
62 #include "store.h"
63 #include "string-util.h"
64 #include "strxcpyx.h"
65 #include "svg.h"
66 #include "util.h"
67
68 static int exiting = 0;
69
70 #define DEFAULT_SAMPLES_LEN 500
71 #define DEFAULT_HZ 25.0
72 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
73 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
74 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
75 #define DEFAULT_OUTPUT "/run/log"
76
77 /* graph defaults */
78 bool arg_entropy = false;
79 bool arg_initcall = true;
80 bool arg_relative = false;
81 bool arg_filter = true;
82 bool arg_show_cmdline = false;
83 bool arg_show_cgroup = false;
84 bool arg_pss = false;
85 bool arg_percpu = false;
86 int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */
87 double arg_hz = DEFAULT_HZ;
88 double arg_scale_x = DEFAULT_SCALE_X;
89 double arg_scale_y = DEFAULT_SCALE_Y;
90
91 char arg_init_path[PATH_MAX] = DEFAULT_INIT;
92 char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT;
93
94 static void signal_handler(int sig) {
95 exiting = 1;
96 }
97
98 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
99
100 #define BOOTCHART_MAX (16*1024*1024)
101
102 static void parse_conf(void) {
103 char *init = NULL, *output = NULL;
104 const ConfigTableItem items[] = {
105 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
106 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
107 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
108 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
109 { "Bootchart", "Output", config_parse_path, 0, &output },
110 { "Bootchart", "Init", config_parse_path, 0, &init },
111 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
112 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
113 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
114 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
115 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
116 { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu },
117 { NULL, NULL, NULL, 0, NULL }
118 };
119
120 config_parse_many(BOOTCHART_CONF,
121 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
122 NULL, config_item_table_lookup, items, true, NULL);
123
124 if (init != NULL)
125 strscpy(arg_init_path, sizeof(arg_init_path), init);
126 if (output != NULL)
127 strscpy(arg_output_path, sizeof(arg_output_path), output);
128 }
129
130 static void help(void) {
131 printf("Usage: %s [OPTIONS]\n\n"
132 "Options:\n"
133 " -r --rel Record time relative to recording\n"
134 " -f --freq=FREQ Sample frequency [%g]\n"
135 " -n --samples=N Stop sampling at [%d] samples\n"
136 " -x --scale-x=N Scale the graph horizontally [%g] \n"
137 " -y --scale-y=N Scale the graph vertically [%g] \n"
138 " -p --pss Enable PSS graph (CPU intensive)\n"
139 " -e --entropy Enable the entropy_avail graph\n"
140 " -o --output=PATH Path to output files [%s]\n"
141 " -i --init=PATH Path to init executable [%s]\n"
142 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
143 " -C --cmdline Display full command lines with arguments\n"
144 " -c --control-group Display process control group\n"
145 " --per-cpu Draw each CPU utilization and wait bar also\n"
146 " -h --help Display this message\n\n"
147 "See bootchart.conf for more information.\n",
148 program_invocation_short_name,
149 DEFAULT_HZ,
150 DEFAULT_SAMPLES_LEN,
151 DEFAULT_SCALE_X,
152 DEFAULT_SCALE_Y,
153 DEFAULT_OUTPUT,
154 DEFAULT_INIT);
155 }
156
157 static int parse_argv(int argc, char *argv[]) {
158
159 enum {
160 ARG_PERCPU = 0x100,
161 };
162
163 static const struct option options[] = {
164 {"rel", no_argument, NULL, 'r' },
165 {"freq", required_argument, NULL, 'f' },
166 {"samples", required_argument, NULL, 'n' },
167 {"pss", no_argument, NULL, 'p' },
168 {"output", required_argument, NULL, 'o' },
169 {"init", required_argument, NULL, 'i' },
170 {"no-filter", no_argument, NULL, 'F' },
171 {"cmdline", no_argument, NULL, 'C' },
172 {"control-group", no_argument, NULL, 'c' },
173 {"help", no_argument, NULL, 'h' },
174 {"scale-x", required_argument, NULL, 'x' },
175 {"scale-y", required_argument, NULL, 'y' },
176 {"entropy", no_argument, NULL, 'e' },
177 {"per-cpu", no_argument, NULL, ARG_PERCPU},
178 {}
179 };
180 int c, r;
181
182 if (getpid() == 1)
183 opterr = 0;
184
185 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
186 switch (c) {
187
188 case 'r':
189 arg_relative = true;
190 break;
191 case 'f':
192 r = safe_atod(optarg, &arg_hz);
193 if (r < 0)
194 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
195 optarg);
196 break;
197 case 'F':
198 arg_filter = false;
199 break;
200 case 'C':
201 arg_show_cmdline = true;
202 break;
203 case 'c':
204 arg_show_cgroup = true;
205 break;
206 case 'n':
207 r = safe_atoi(optarg, &arg_samples_len);
208 if (r < 0)
209 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
210 optarg);
211 break;
212 case 'o':
213 path_kill_slashes(optarg);
214 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
215 break;
216 case 'i':
217 path_kill_slashes(optarg);
218 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
219 break;
220 case 'p':
221 arg_pss = true;
222 break;
223 case 'x':
224 r = safe_atod(optarg, &arg_scale_x);
225 if (r < 0)
226 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
227 optarg);
228 break;
229 case 'y':
230 r = safe_atod(optarg, &arg_scale_y);
231 if (r < 0)
232 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
233 optarg);
234 break;
235 case 'e':
236 arg_entropy = true;
237 break;
238 case ARG_PERCPU:
239 arg_percpu = true;
240 break;
241 case 'h':
242 help();
243 return 0;
244 case '?':
245 if (getpid() != 1)
246 return -EINVAL;
247 else
248 return 0;
249 default:
250 assert_not_reached("Unhandled option code.");
251 }
252
253 if (arg_hz <= 0) {
254 log_error("Frequency needs to be > 0");
255 return -EINVAL;
256 }
257
258 return 1;
259 }
260
261 static int do_journal_append(char *file) {
262 _cleanup_free_ char *bootchart_message = NULL;
263 _cleanup_free_ char *bootchart_file = NULL;
264 _cleanup_free_ char *p = NULL;
265 _cleanup_close_ int fd = -1;
266 struct iovec iovec[5];
267 int r, j = 0;
268 ssize_t n;
269
270 bootchart_file = strappend("BOOTCHART_FILE=", file);
271 if (!bootchart_file)
272 return log_oom();
273
274 IOVEC_SET_STRING(iovec[j++], bootchart_file);
275 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
276 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
277 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
278 if (!bootchart_message)
279 return log_oom();
280
281 IOVEC_SET_STRING(iovec[j++], bootchart_message);
282
283 p = malloc(10 + BOOTCHART_MAX);
284 if (!p)
285 return log_oom();
286
287 memcpy(p, "BOOTCHART=", 10);
288
289 fd = open(file, O_RDONLY|O_CLOEXEC);
290 if (fd < 0)
291 return log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file);
292
293 n = loop_read(fd, p + 10, BOOTCHART_MAX, false);
294 if (n < 0)
295 return log_error_errno(n, "Failed to read bootchart data: %m");
296
297 iovec[j].iov_base = p;
298 iovec[j].iov_len = 10 + n;
299 j++;
300
301 r = sd_journal_sendv(iovec, j);
302 if (r < 0)
303 log_error_errno(r, "Failed to send bootchart: %m");
304
305 return 0;
306 }
307
308 int main(int argc, char *argv[]) {
309 static struct list_sample_data *sampledata;
310 _cleanup_closedir_ DIR *proc = NULL;
311 _cleanup_free_ char *build = NULL;
312 _cleanup_fclose_ FILE *of = NULL;
313 _cleanup_close_ int sysfd = -1;
314 struct ps_struct *ps_first;
315 double graph_start;
316 double log_start;
317 double interval;
318 char output_file[PATH_MAX];
319 char datestr[200];
320 int pscount = 0;
321 int n_cpus = 0;
322 int overrun = 0;
323 time_t t = 0;
324 int r, samples;
325 struct ps_struct *ps;
326 struct rlimit rlim;
327 struct list_sample_data *head;
328 struct sigaction sig = {
329 .sa_handler = signal_handler,
330 };
331
332 parse_conf();
333
334 r = parse_argv(argc, argv);
335 if (r < 0)
336 return EXIT_FAILURE;
337
338 if (r == 0)
339 return EXIT_SUCCESS;
340
341 /*
342 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
343 * fork:
344 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
345 * - child logs data
346 */
347 if (getpid() == 1) {
348 if (fork())
349 /* parent */
350 execl(arg_init_path, arg_init_path, NULL);
351 }
352 argv[0][0] = '@';
353
354 rlim.rlim_cur = 4096;
355 rlim.rlim_max = 4096;
356 (void) setrlimit(RLIMIT_NOFILE, &rlim);
357
358 /* start with empty ps LL */
359 ps_first = new0(struct ps_struct, 1);
360 if (!ps_first) {
361 log_oom();
362 return EXIT_FAILURE;
363 }
364
365 /* handle TERM/INT nicely */
366 sigaction(SIGHUP, &sig, NULL);
367
368 interval = (1.0 / arg_hz) * 1000000000.0;
369
370 if (arg_relative)
371 graph_start = log_start = gettime_ns();
372 else {
373 struct timespec n;
374 double uptime;
375
376 clock_gettime(clock_boottime_or_monotonic(), &n);
377 uptime = (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
378
379 log_start = gettime_ns();
380 graph_start = log_start - uptime;
381 }
382
383 if (graph_start < 0.0) {
384 log_error("Failed to setup graph start time.\n\n"
385 "The system uptime probably includes time that the system was suspended. "
386 "Use --rel to bypass this issue.");
387 return EXIT_FAILURE;
388 }
389
390 LIST_HEAD_INIT(head);
391
392 /* main program loop */
393 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
394 int res;
395 double sample_stop;
396 double elapsed;
397 double timeleft;
398
399 sampledata = new0(struct list_sample_data, 1);
400 if (sampledata == NULL) {
401 log_oom();
402 return EXIT_FAILURE;
403 }
404
405 sampledata->sampletime = gettime_ns();
406 sampledata->counter = samples;
407
408 if (sysfd < 0)
409 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
410
411 if (!build) {
412 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
413 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
414 }
415
416 if (proc)
417 rewinddir(proc);
418 else
419 proc = opendir("/proc");
420
421 /* wait for /proc to become available, discarding samples */
422 if (proc) {
423 r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus);
424 if (r < 0)
425 return EXIT_FAILURE;
426 }
427
428 sample_stop = gettime_ns();
429
430 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
431 timeleft = interval - elapsed;
432
433 /*
434 * check if we have not consumed our entire timeslice. If we
435 * do, don't sleep and take a new sample right away.
436 * we'll lose all the missed samples and overrun our total
437 * time
438 */
439 if (timeleft > 0) {
440 struct timespec req;
441
442 req.tv_sec = (time_t)(timeleft / 1000000000.0);
443 req.tv_nsec = (long)(timeleft - (req.tv_sec * 1000000000.0));
444
445 res = nanosleep(&req, NULL);
446 if (res) {
447 if (errno == EINTR)
448 /* caught signal, probably HUP! */
449 break;
450 log_error_errno(errno, "nanosleep() failed: %m");
451 return EXIT_FAILURE;
452 }
453 } else {
454 overrun++;
455 /* calculate how many samples we lost and scrap them */
456 arg_samples_len -= (int)(-timeleft / interval);
457 }
458 LIST_PREPEND(link, head, sampledata);
459 }
460
461 /* do some cleanup, close fd's */
462 ps = ps_first;
463 while (ps->next_ps) {
464 ps = ps->next_ps;
465 ps->schedstat = safe_close(ps->schedstat);
466 ps->sched = safe_close(ps->sched);
467 ps->smaps = safe_fclose(ps->smaps);
468 }
469
470 if (!of) {
471 t = time(NULL);
472 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
473 assert_se(r > 0);
474
475 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
476 of = fopen(output_file, "we");
477 }
478
479 if (!of) {
480 log_error("Error opening output file '%s': %m\n", output_file);
481 return EXIT_FAILURE;
482 }
483
484 r = svg_do(of, strna(build), head, ps_first,
485 samples, pscount, n_cpus, graph_start,
486 log_start, interval, overrun);
487
488 if (r < 0) {
489 log_error_errno(r, "Error generating svg file: %m");
490 return EXIT_FAILURE;
491 }
492
493 log_info("systemd-bootchart wrote %s\n", output_file);
494
495 r = do_journal_append(output_file);
496 if (r < 0)
497 return EXIT_FAILURE;
498
499 /* nitpic cleanups */
500 ps = ps_first->next_ps;
501 while (ps->next_ps) {
502 struct ps_struct *old;
503
504 old = ps;
505 old->sample = ps->first;
506 ps = ps->next_ps;
507 while (old->sample->next) {
508 struct ps_sched_struct *oldsample = old->sample;
509
510 old->sample = old->sample->next;
511 free(oldsample);
512 }
513 free(old->cgroup);
514 free(old->sample);
515 free(old);
516 }
517
518 free(ps->cgroup);
519 free(ps->sample);
520 free(ps);
521
522 sampledata = head;
523 while (sampledata->link_prev) {
524 struct list_sample_data *old_sampledata = sampledata;
525 sampledata = sampledata->link_prev;
526 free(old_sampledata);
527 }
528 free(sampledata);
529
530 /* don't complain when overrun once, happens most commonly on 1st sample */
531 if (overrun > 1)
532 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun);
533
534 return 0;
535 }