]> git.ipfire.org Git - thirdparty/systemd.git/blob - 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
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 <errno.h>
37 #include <fcntl.h>
38 #include <getopt.h>
39 #include <limits.h>
40 #include <signal.h>
41 #include <stdbool.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <sys/resource.h>
46 #include <time.h>
47 #include <unistd.h>
48
49 #include "sd-journal.h"
50
51 #include "bootchart.h"
52 #include "conf-parser.h"
53 #include "fd-util.h"
54 #include "fileio.h"
55 #include "io-util.h"
56 #include "list.h"
57 #include "macro.h"
58 #include "parse-util.h"
59 #include "path-util.h"
60 #include "store.h"
61 #include "string-util.h"
62 #include "strxcpyx.h"
63 #include "svg.h"
64 #include "util.h"
65
66 static int exiting = 0;
67
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 */
72 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
73 #define DEFAULT_OUTPUT "/run/log"
74
75 /* graph defaults */
76 bool arg_entropy = false;
77 bool arg_initcall = true;
78 bool arg_relative = false;
79 bool arg_filter = true;
80 bool arg_show_cmdline = false;
81 bool arg_show_cgroup = false;
82 bool arg_pss = false;
83 bool arg_percpu = false;
84 int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */
85 double arg_hz = DEFAULT_HZ;
86 double arg_scale_x = DEFAULT_SCALE_X;
87 double arg_scale_y = DEFAULT_SCALE_Y;
88
89 char arg_init_path[PATH_MAX] = DEFAULT_INIT;
90 char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT;
91
92 static void signal_handler(int sig) {
93 exiting = 1;
94 }
95
96 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
97
98 #define BOOTCHART_MAX (16*1024*1024)
99
100 static void parse_conf(void) {
101 char *init = NULL, *output = NULL;
102 const ConfigTableItem items[] = {
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 },
113 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
114 { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu },
115 { NULL, NULL, NULL, 0, NULL }
116 };
117
118 config_parse_many(BOOTCHART_CONF,
119 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
120 NULL, config_item_table_lookup, items, true, NULL);
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
128 static void help(void) {
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);
153 }
154
155 static int parse_argv(int argc, char *argv[]) {
156
157 enum {
158 ARG_PERCPU = 0x100,
159 };
160
161 static const struct option options[] = {
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},
176 {}
177 };
178 int c, r;
179
180 if (getpid() == 1)
181 opterr = 0;
182
183 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
184 switch (c) {
185
186 case 'r':
187 arg_relative = true;
188 break;
189 case 'f':
190 r = safe_atod(optarg, &arg_hz);
191 if (r < 0)
192 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
193 optarg);
194 break;
195 case 'F':
196 arg_filter = false;
197 break;
198 case 'C':
199 arg_show_cmdline = true;
200 break;
201 case 'c':
202 arg_show_cgroup = true;
203 break;
204 case 'n':
205 r = safe_atoi(optarg, &arg_samples_len);
206 if (r < 0)
207 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
208 optarg);
209 break;
210 case 'o':
211 path_kill_slashes(optarg);
212 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
213 break;
214 case 'i':
215 path_kill_slashes(optarg);
216 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
217 break;
218 case 'p':
219 arg_pss = true;
220 break;
221 case 'x':
222 r = safe_atod(optarg, &arg_scale_x);
223 if (r < 0)
224 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
225 optarg);
226 break;
227 case 'y':
228 r = safe_atod(optarg, &arg_scale_y);
229 if (r < 0)
230 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
231 optarg);
232 break;
233 case 'e':
234 arg_entropy = true;
235 break;
236 case ARG_PERCPU:
237 arg_percpu = true;
238 break;
239 case 'h':
240 help();
241 return 0;
242 case '?':
243 if (getpid() != 1)
244 return -EINVAL;
245 else
246 return 0;
247 default:
248 assert_not_reached("Unhandled option code.");
249 }
250
251 if (arg_hz <= 0) {
252 log_error("Frequency needs to be > 0");
253 return -EINVAL;
254 }
255
256 return 1;
257 }
258
259 static 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;
264 struct iovec iovec[5];
265 int r, j = 0;
266 ssize_t n;
267
268 bootchart_file = strappend("BOOTCHART_FILE=", file);
269 if (!bootchart_file)
270 return log_oom();
271
272 IOVEC_SET_STRING(iovec[j++], bootchart_file);
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);
276 if (!bootchart_message)
277 return log_oom();
278
279 IOVEC_SET_STRING(iovec[j++], bootchart_message);
280
281 p = malloc(10 + BOOTCHART_MAX);
282 if (!p)
283 return log_oom();
284
285 memcpy(p, "BOOTCHART=", 10);
286
287 fd = open(file, O_RDONLY|O_CLOEXEC);
288 if (fd < 0)
289 return log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file);
290
291 n = loop_read(fd, p + 10, BOOTCHART_MAX, false);
292 if (n < 0)
293 return log_error_errno(n, "Failed to read bootchart data: %m");
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)
301 log_error_errno(r, "Failed to send bootchart: %m");
302
303 return 0;
304 }
305
306 int main(int argc, char *argv[]) {
307 static struct list_sample_data *sampledata;
308 _cleanup_closedir_ DIR *proc = NULL;
309 _cleanup_free_ char *build = NULL;
310 _cleanup_fclose_ FILE *of = NULL;
311 _cleanup_close_ int sysfd = -1;
312 struct ps_struct *ps_first;
313 double graph_start;
314 double log_start;
315 double interval;
316 char output_file[PATH_MAX];
317 char datestr[200];
318 int pscount = 0;
319 int n_cpus = 0;
320 int overrun = 0;
321 time_t t = 0;
322 int r, samples;
323 struct ps_struct *ps;
324 struct rlimit rlim;
325 struct list_sample_data *head;
326 struct sigaction sig = {
327 .sa_handler = signal_handler,
328 };
329
330 parse_conf();
331
332 r = parse_argv(argc, argv);
333 if (r < 0)
334 return EXIT_FAILURE;
335
336 if (r == 0)
337 return EXIT_SUCCESS;
338
339 /*
340 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
341 * fork:
342 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
343 * - child logs data
344 */
345 if (getpid() == 1) {
346 if (fork())
347 /* parent */
348 execl(arg_init_path, arg_init_path, NULL);
349 }
350 argv[0][0] = '@';
351
352 rlim.rlim_cur = 4096;
353 rlim.rlim_max = 4096;
354 (void) setrlimit(RLIMIT_NOFILE, &rlim);
355
356 /* start with empty ps LL */
357 ps_first = new0(struct ps_struct, 1);
358 if (!ps_first) {
359 log_oom();
360 return EXIT_FAILURE;
361 }
362
363 /* handle TERM/INT nicely */
364 sigaction(SIGHUP, &sig, NULL);
365
366 interval = (1.0 / arg_hz) * 1000000000.0;
367
368 if (arg_relative)
369 graph_start = log_start = gettime_ns();
370 else {
371 struct timespec n;
372 double uptime;
373
374 clock_gettime(clock_boottime_or_monotonic(), &n);
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 }
380
381 if (graph_start < 0.0) {
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.");
385 return EXIT_FAILURE;
386 }
387
388 LIST_HEAD_INIT(head);
389
390 /* main program loop */
391 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
392 int res;
393 double sample_stop;
394 double elapsed;
395 double timeleft;
396
397 sampledata = new0(struct list_sample_data, 1);
398 if (sampledata == NULL) {
399 log_oom();
400 return EXIT_FAILURE;
401 }
402
403 sampledata->sampletime = gettime_ns();
404 sampledata->counter = samples;
405
406 if (sysfd < 0)
407 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
408
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 }
413
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) {
421 r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus);
422 if (r < 0)
423 return EXIT_FAILURE;
424 }
425
426 sample_stop = gettime_ns();
427
428 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
429 timeleft = interval - elapsed;
430
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 */
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));
442
443 res = nanosleep(&req, NULL);
444 if (res) {
445 if (errno == EINTR)
446 /* caught signal, probably HUP! */
447 break;
448 log_error_errno(errno, "nanosleep() failed: %m");
449 return EXIT_FAILURE;
450 }
451 } else {
452 overrun++;
453 /* calculate how many samples we lost and scrap them */
454 arg_samples_len -= (int)(-timeleft / interval);
455 }
456 LIST_PREPEND(link, head, sampledata);
457 }
458
459 /* do some cleanup, close fd's */
460 ps = ps_first;
461 while (ps->next_ps) {
462 ps = ps->next_ps;
463 ps->schedstat = safe_close(ps->schedstat);
464 ps->sched = safe_close(ps->sched);
465 ps->smaps = safe_fclose(ps->smaps);
466 }
467
468 if (!of) {
469 t = time(NULL);
470 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
471 assert_se(r > 0);
472
473 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
474 of = fopen(output_file, "we");
475 }
476
477 if (!of) {
478 log_error("Error opening output file '%s': %m\n", output_file);
479 return EXIT_FAILURE;
480 }
481
482 r = svg_do(of, strna(build), head, ps_first,
483 samples, pscount, n_cpus, graph_start,
484 log_start, interval, overrun);
485
486 if (r < 0) {
487 log_error_errno(r, "Error generating svg file: %m");
488 return EXIT_FAILURE;
489 }
490
491 log_info("systemd-bootchart wrote %s\n", output_file);
492
493 r = do_journal_append(output_file);
494 if (r < 0)
495 return EXIT_FAILURE;
496
497 /* nitpic cleanups */
498 ps = ps_first->next_ps;
499 while (ps->next_ps) {
500 struct ps_struct *old;
501
502 old = ps;
503 old->sample = ps->first;
504 ps = ps->next_ps;
505 while (old->sample->next) {
506 struct ps_sched_struct *oldsample = old->sample;
507
508 old->sample = old->sample->next;
509 free(oldsample);
510 }
511 free(old->cgroup);
512 free(old->sample);
513 free(old);
514 }
515
516 free(ps->cgroup);
517 free(ps->sample);
518 free(ps);
519
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);
527
528 /* don't complain when overrun once, happens most commonly on 1st sample */
529 if (overrun > 1)
530 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun);
531
532 return 0;
533 }