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