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