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