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