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