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