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