]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/bootchart/bootchart.c
tree-wide: drop {} from one-line if blocks
[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
83fdc450
AK
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>
b823b5e2 46#include <fcntl.h>
f7900e25 47#include <stdbool.h>
73f860db 48#include "systemd/sd-journal.h"
83fdc450 49
53f5329f 50#include "util.h"
a5c32cff 51#include "fileio.h"
f7900e25
TA
52#include "macro.h"
53#include "conf-parser.h"
54#include "strxcpyx.h"
0e4ffbff 55#include "path-util.h"
6d031c0b
LP
56#include "store.h"
57#include "svg.h"
58#include "bootchart.h"
8dfb6e71 59#include "list.h"
83fdc450 60
83fdc450
AK
61static int exiting = 0;
62
c7fc641e
ZJS
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 */
a804d849 67#define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
c7fc641e
ZJS
68#define DEFAULT_OUTPUT "/run/log"
69
83fdc450 70/* graph defaults */
6d031c0b 71bool arg_entropy = false;
1f2ecb03 72bool arg_initcall = true;
6d031c0b
LP
73bool arg_relative = false;
74bool arg_filter = true;
75bool arg_show_cmdline = false;
49e5b2a9 76bool arg_show_cgroup = false;
6d031c0b 77bool arg_pss = false;
749806b3 78bool arg_percpu = false;
c7fc641e
ZJS
79int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */
80double arg_hz = DEFAULT_HZ;
81double arg_scale_x = DEFAULT_SCALE_X;
82double arg_scale_y = DEFAULT_SCALE_Y;
83fdc450 83
c7fc641e
ZJS
84char arg_init_path[PATH_MAX] = DEFAULT_INIT;
85char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT;
83fdc450 86
6d031c0b 87static void signal_handler(int sig) {
28989b63 88 exiting = 1;
83fdc450
AK
89}
90
4cd5f79d 91#define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
f7900e25 92
c4d58b0b
AK
93#define BOOTCHART_MAX (16*1024*1024)
94
4cd5f79d
ZJS
95static void parse_conf(void) {
96 char *init = NULL, *output = NULL;
f7900e25 97 const ConfigTableItem items[] = {
6d031c0b
LP
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 },
49e5b2a9 108 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
749806b3 109 { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu },
f7900e25
TA
110 { NULL, NULL, NULL, 0, NULL }
111 };
28989b63 112
396f9e2b
JT
113 config_parse_many(BOOTCHART_CONF,
114 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
115 NULL, config_item_table_lookup, items, true, NULL);
4cd5f79d
ZJS
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
c7fc641e 123static void help(void) {
03995863
DM
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);
c7fc641e
ZJS
148}
149
601185b4 150static int parse_argv(int argc, char *argv[]) {
749806b3
WC
151
152 enum {
153 ARG_PERCPU = 0x100,
154 };
155
5d459d6b 156 static const struct option options[] = {
749806b3
WC
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},
5d459d6b 171 {}
4cd5f79d 172 };
601185b4 173 int c, r;
4cd5f79d 174
601185b4
ZJS
175 if (getpid() == 1)
176 opterr = 0;
4cd5f79d 177
601185b4 178 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
4cd5f79d 179 switch (c) {
601185b4 180
28989b63 181 case 'r':
6d031c0b 182 arg_relative = true;
28989b63
TA
183 break;
184 case 'f':
6d031c0b 185 r = safe_atod(optarg, &arg_hz);
547ba5a9 186 if (r < 0)
c33b3297
MS
187 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
188 optarg);
28989b63
TA
189 break;
190 case 'F':
6d031c0b 191 arg_filter = false;
28989b63 192 break;
e90f9fa4 193 case 'C':
6d031c0b 194 arg_show_cmdline = true;
e90f9fa4 195 break;
49e5b2a9
WC
196 case 'c':
197 arg_show_cgroup = true;
198 break;
28989b63 199 case 'n':
6d031c0b 200 r = safe_atoi(optarg, &arg_samples_len);
547ba5a9 201 if (r < 0)
c33b3297
MS
202 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
203 optarg);
28989b63
TA
204 break;
205 case 'o':
0e4ffbff 206 path_kill_slashes(optarg);
6d031c0b 207 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
28989b63
TA
208 break;
209 case 'i':
0e4ffbff 210 path_kill_slashes(optarg);
6d031c0b 211 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
28989b63
TA
212 break;
213 case 'p':
6d031c0b 214 arg_pss = true;
28989b63
TA
215 break;
216 case 'x':
6d031c0b 217 r = safe_atod(optarg, &arg_scale_x);
547ba5a9 218 if (r < 0)
c33b3297
MS
219 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
220 optarg);
28989b63
TA
221 break;
222 case 'y':
6d031c0b 223 r = safe_atod(optarg, &arg_scale_y);
547ba5a9 224 if (r < 0)
c33b3297
MS
225 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
226 optarg);
28989b63
TA
227 break;
228 case 'e':
6d031c0b 229 arg_entropy = true;
28989b63 230 break;
749806b3
WC
231 case ARG_PERCPU:
232 arg_percpu = true;
233 break;
28989b63 234 case 'h':
c7fc641e 235 help();
601185b4
ZJS
236 return 0;
237 case '?':
238 if (getpid() != 1)
239 return -EINVAL;
240 else
241 return 0;
28989b63 242 default:
601185b4 243 assert_not_reached("Unhandled option code.");
28989b63 244 }
28989b63 245
601185b4
ZJS
246 if (arg_hz <= 0) {
247 log_error("Frequency needs to be > 0");
4cd5f79d 248 return -EINVAL;
28989b63
TA
249 }
250
601185b4 251 return 1;
4cd5f79d
ZJS
252}
253
af672f03
DM
254static 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;
c4d58b0b 259 struct iovec iovec[5];
d92f98b4 260 int r, j = 0;
c4d58b0b 261 ssize_t n;
c4d58b0b
AK
262
263 bootchart_file = strappend("BOOTCHART_FILE=", file);
af672f03
DM
264 if (!bootchart_file)
265 return log_oom();
c4d58b0b 266
af672f03 267 IOVEC_SET_STRING(iovec[j++], bootchart_file);
c4d58b0b
AK
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);
af672f03
DM
271 if (!bootchart_message)
272 return log_oom();
c4d58b0b 273
af672f03
DM
274 IOVEC_SET_STRING(iovec[j++], bootchart_message);
275
276 p = malloc(10 + BOOTCHART_MAX);
277 if (!p)
278 return log_oom();
c4d58b0b
AK
279
280 memcpy(p, "BOOTCHART=", 10);
281
d92f98b4 282 fd = open(file, O_RDONLY|O_CLOEXEC);
af672f03
DM
283 if (fd < 0)
284 return log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file);
d92f98b4
ZJS
285
286 n = loop_read(fd, p + 10, BOOTCHART_MAX, false);
af672f03
DM
287 if (n < 0)
288 return log_error_errno(n, "Failed to read bootchart data: %m");
c4d58b0b
AK
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)
da927ba9 296 log_error_errno(r, "Failed to send bootchart: %m");
af672f03
DM
297
298 return 0;
c4d58b0b
AK
299}
300
4cd5f79d 301int main(int argc, char *argv[]) {
af672f03 302 static struct list_sample_data *sampledata;
f9178132 303 _cleanup_closedir_ DIR *proc = NULL;
af672f03 304 _cleanup_free_ char *build = NULL;
1f2ecb03 305 _cleanup_fclose_ FILE *of = NULL;
af672f03
DM
306 _cleanup_close_ int sysfd = -1;
307 struct ps_struct *ps_first;
1f2ecb03
DM
308 double graph_start;
309 double log_start;
310 double interval;
4cd5f79d
ZJS
311 char output_file[PATH_MAX];
312 char datestr[200];
1f2ecb03
DM
313 int pscount = 0;
314 int n_cpus = 0;
315 int overrun = 0;
af672f03
DM
316 time_t t = 0;
317 int r, samples;
318 struct ps_struct *ps;
4cd5f79d 319 struct rlimit rlim;
1f2ecb03 320 struct list_sample_data *head;
af672f03
DM
321 struct sigaction sig = {
322 .sa_handler = signal_handler,
323 };
4cd5f79d
ZJS
324
325 parse_conf();
326
601185b4 327 r = parse_argv(argc, argv);
34a4071e
DM
328 if (r < 0)
329 return EXIT_FAILURE;
330
331 if (r == 0)
332 return EXIT_SUCCESS;
4cd5f79d 333
28989b63 334 /*
547ba5a9 335 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
28989b63 336 * fork:
6e1bf7ab 337 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
28989b63
TA
338 * - child logs data
339 */
340 if (getpid() == 1) {
341 if (fork()) {
342 /* parent */
6d031c0b 343 execl(arg_init_path, arg_init_path, NULL);
28989b63
TA
344 }
345 }
0e4ffbff 346 argv[0][0] = '@';
28989b63 347
361514ac
LP
348 rlim.rlim_cur = 4096;
349 rlim.rlim_max = 4096;
350 (void) setrlimit(RLIMIT_NOFILE, &rlim);
351
28989b63 352 /* start with empty ps LL */
955d98c9 353 ps_first = new0(struct ps_struct, 1);
28989b63 354 if (!ps_first) {
955d98c9
LP
355 log_oom();
356 return EXIT_FAILURE;
28989b63 357 }
28989b63
TA
358
359 /* handle TERM/INT nicely */
28989b63
TA
360 sigaction(SIGHUP, &sig, NULL);
361
6d031c0b 362 interval = (1.0 / arg_hz) * 1000000000.0;
28989b63 363
1f2ecb03
DM
364 if (arg_relative)
365 graph_start = log_start = gettime_ns();
366 else {
367 struct timespec n;
368 double uptime;
369
27ec691b 370 clock_gettime(clock_boottime_or_monotonic(), &n);
1f2ecb03
DM
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 }
28989b63 376
9a6f36c0 377 if (graph_start < 0.0) {
03995863
DM
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.");
34a4071e 381 return EXIT_FAILURE;
9a6f36c0
KZ
382 }
383
71fda00f 384 LIST_HEAD_INIT(head);
8dfb6e71 385
28989b63 386 /* main program loop */
522cd7f1 387 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
28989b63
TA
388 int res;
389 double sample_stop;
28989b63
TA
390 double elapsed;
391 double timeleft;
392
8dfb6e71
NC
393 sampledata = new0(struct list_sample_data, 1);
394 if (sampledata == NULL) {
4155f7d4
LP
395 log_oom();
396 return EXIT_FAILURE;
8dfb6e71
NC
397 }
398
399 sampledata->sampletime = gettime_ns();
400 sampledata->counter = samples;
28989b63 401
6d031c0b 402 if (sysfd < 0)
c8a202b7 403 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
f2f85884 404
5ae4d543
LP
405 if (!build) {
406 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
407 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
408 }
e93450c6 409
f9178132
DM
410 if (proc)
411 rewinddir(proc);
412 else
413 proc = opendir("/proc");
414
415 /* wait for /proc to become available, discarding samples */
416 if (proc) {
1f2ecb03 417 r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus);
34a4071e
DM
418 if (r < 0)
419 return EXIT_FAILURE;
34a4071e 420 }
28989b63
TA
421
422 sample_stop = gettime_ns();
423
8dfb6e71 424 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
28989b63
TA
425 timeleft = interval - elapsed;
426
28989b63
TA
427 /*
428 * check if we have not consumed our entire timeslice. If we
429 * do, don't sleep and take a new sample right away.
430 * we'll lose all the missed samples and overrun our total
431 * time
432 */
0a327854
DM
433 if (timeleft > 0) {
434 struct timespec req;
435
436 req.tv_sec = (time_t)(timeleft / 1000000000.0);
437 req.tv_nsec = (long)(timeleft - (req.tv_sec * 1000000000.0));
28989b63
TA
438
439 res = nanosleep(&req, NULL);
440 if (res) {
ece174c5 441 if (errno == EINTR)
28989b63
TA
442 /* caught signal, probably HUP! */
443 break;
56f64d95 444 log_error_errno(errno, "nanosleep() failed: %m");
34a4071e 445 return EXIT_FAILURE;
28989b63
TA
446 }
447 } else {
448 overrun++;
449 /* calculate how many samples we lost and scrap them */
0a327854 450 arg_samples_len -= (int)(-timeleft / interval);
28989b63 451 }
71fda00f 452 LIST_PREPEND(link, head, sampledata);
28989b63
TA
453 }
454
455 /* do some cleanup, close fd's */
456 ps = ps_first;
457 while (ps->next_ps) {
458 ps = ps->next_ps;
af672f03
DM
459 ps->schedstat = safe_close(ps->schedstat);
460 ps->sched = safe_close(ps->sched);
461 if (ps->smaps) {
28989b63 462 fclose(ps->smaps);
af672f03
DM
463 ps->smaps = NULL;
464 }
28989b63 465 }
28989b63 466
f2f85884
HH
467 if (!of) {
468 t = time(NULL);
e931d3f4
TA
469 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
470 assert_se(r > 0);
471
6d031c0b 472 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
c8a202b7 473 of = fopen(output_file, "we");
f2f85884 474 }
28989b63 475
28989b63 476 if (!of) {
f9178132
DM
477 log_error("Error opening output file '%s': %m\n", output_file);
478 return EXIT_FAILURE;
28989b63
TA
479 }
480
af672f03
DM
481 r = svg_do(of, strna(build), head, ps_first,
482 samples, pscount, n_cpus, graph_start,
483 log_start, interval, overrun);
1f2ecb03 484
f9178132 485 if (r < 0) {
5d236c1f 486 log_error_errno(r, "Error generating svg file: %m");
f9178132
DM
487 return EXIT_FAILURE;
488 }
28989b63 489
f9178132 490 log_info("systemd-bootchart wrote %s\n", output_file);
6d031c0b 491
af672f03
DM
492 r = do_journal_append(output_file);
493 if (r < 0)
494 return EXIT_FAILURE;
c4d58b0b 495
28989b63 496 /* nitpic cleanups */
8dfb6e71 497 ps = ps_first->next_ps;
28989b63 498 while (ps->next_ps) {
8dfb6e71
NC
499 struct ps_struct *old;
500
501 old = ps;
502 old->sample = ps->first;
28989b63 503 ps = ps->next_ps;
8dfb6e71
NC
504 while (old->sample->next) {
505 struct ps_sched_struct *oldsample = old->sample;
506
507 old->sample = old->sample->next;
508 free(oldsample);
509 }
49e5b2a9 510 free(old->cgroup);
28989b63
TA
511 free(old->sample);
512 free(old);
513 }
af672f03 514
49e5b2a9 515 free(ps->cgroup);
28989b63
TA
516 free(ps->sample);
517 free(ps);
518
8dfb6e71
NC
519 sampledata = head;
520 while (sampledata->link_prev) {
521 struct list_sample_data *old_sampledata = sampledata;
522 sampledata = sampledata->link_prev;
523 free(old_sampledata);
524 }
525 free(sampledata);
af672f03 526
28989b63
TA
527 /* don't complain when overrun once, happens most commonly on 1st sample */
528 if (overrun > 1)
6aec8359 529 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun);
28989b63
TA
530
531 return 0;
83fdc450 532}