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