]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
bootchart: remove duplicated code, prevent creating empty files
[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/resource.h>
37 #include <stdio.h>
38 #include <signal.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <time.h>
43 #include <getopt.h>
44 #include <limits.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <stdbool.h>
48 #include "systemd/sd-journal.h"
49
50 #include "util.h"
51 #include "fileio.h"
52 #include "macro.h"
53 #include "conf-parser.h"
54 #include "strxcpyx.h"
55 #include "path-util.h"
56 #include "store.h"
57 #include "svg.h"
58 #include "bootchart.h"
59 #include "list.h"
60
61 double graph_start;
62 double log_start;
63 struct ps_struct *ps_first;
64 int pscount;
65 int cpus;
66 double interval;
67 FILE *of = NULL;
68 int overrun = 0;
69 static int exiting = 0;
70 int sysfd=-1;
71
72 #define DEFAULT_SAMPLES_LEN 500
73 #define DEFAULT_HZ 25.0
74 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
75 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
76 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
77 #define DEFAULT_OUTPUT "/run/log"
78
79 /* graph defaults */
80 bool arg_entropy = false;
81 bool initcall = true;
82 bool arg_relative = false;
83 bool arg_filter = true;
84 bool arg_show_cmdline = false;
85 bool arg_show_cgroup = false;
86 bool arg_pss = false;
87 bool arg_percpu = false;
88 int samples;
89 int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */
90 double arg_hz = DEFAULT_HZ;
91 double arg_scale_x = DEFAULT_SCALE_X;
92 double arg_scale_y = DEFAULT_SCALE_Y;
93 static struct list_sample_data *sampledata;
94 struct list_sample_data *head;
95
96 char arg_init_path[PATH_MAX] = DEFAULT_INIT;
97 char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT;
98
99 static void signal_handler(int sig) {
100 if (sig++)
101 sig--;
102 exiting = 1;
103 }
104
105 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
106
107 #define BOOTCHART_MAX (16*1024*1024)
108
109 static void parse_conf(void) {
110 char *init = NULL, *output = NULL;
111 const ConfigTableItem items[] = {
112 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
113 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
114 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
115 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
116 { "Bootchart", "Output", config_parse_path, 0, &output },
117 { "Bootchart", "Init", config_parse_path, 0, &init },
118 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
119 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
120 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
121 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
122 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
123 { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu },
124 { NULL, NULL, NULL, 0, NULL }
125 };
126
127 config_parse_many(BOOTCHART_CONF,
128 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
129 NULL, config_item_table_lookup, items, true, NULL);
130
131 if (init != NULL)
132 strscpy(arg_init_path, sizeof(arg_init_path), init);
133 if (output != NULL)
134 strscpy(arg_output_path, sizeof(arg_output_path), output);
135 }
136
137 static void help(void) {
138 fprintf(stdout,
139 "Usage: %s [OPTIONS]\n\n"
140 "Options:\n"
141 " -r, --rel Record time relative to recording\n"
142 " -f, --freq=FREQ Sample frequency [%g]\n"
143 " -n, --samples=N Stop sampling at [%d] samples\n"
144 " -x, --scale-x=N Scale the graph horizontally [%g] \n"
145 " -y, --scale-y=N Scale the graph vertically [%g] \n"
146 " -p, --pss Enable PSS graph (CPU intensive)\n"
147 " -e, --entropy Enable the entropy_avail graph\n"
148 " -o, --output=PATH Path to output files [%s]\n"
149 " -i, --init=PATH Path to init executable [%s]\n"
150 " -F, --no-filter Disable filtering of unimportant or ephemeral processes\n"
151 " -C, --cmdline Display full command lines with arguments\n"
152 " -c, --control-group Display process control group\n"
153 " --per-cpu Draw each CPU utilization and wait bar also\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
167 enum {
168 ARG_PERCPU = 0x100,
169 };
170
171 static const struct option options[] = {
172 {"rel", no_argument, NULL, 'r' },
173 {"freq", required_argument, NULL, 'f' },
174 {"samples", required_argument, NULL, 'n' },
175 {"pss", no_argument, NULL, 'p' },
176 {"output", required_argument, NULL, 'o' },
177 {"init", required_argument, NULL, 'i' },
178 {"no-filter", no_argument, NULL, 'F' },
179 {"cmdline", no_argument, NULL, 'C' },
180 {"control-group", no_argument, NULL, 'c' },
181 {"help", no_argument, NULL, 'h' },
182 {"scale-x", required_argument, NULL, 'x' },
183 {"scale-y", required_argument, NULL, 'y' },
184 {"entropy", no_argument, NULL, 'e' },
185 {"per-cpu", no_argument, NULL, ARG_PERCPU},
186 {}
187 };
188 int c, r;
189
190 if (getpid() == 1)
191 opterr = 0;
192
193 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
194 switch (c) {
195
196 case 'r':
197 arg_relative = true;
198 break;
199 case 'f':
200 r = safe_atod(optarg, &arg_hz);
201 if (r < 0)
202 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
203 optarg);
204 break;
205 case 'F':
206 arg_filter = false;
207 break;
208 case 'C':
209 arg_show_cmdline = true;
210 break;
211 case 'c':
212 arg_show_cgroup = true;
213 break;
214 case 'n':
215 r = safe_atoi(optarg, &arg_samples_len);
216 if (r < 0)
217 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
218 optarg);
219 break;
220 case 'o':
221 path_kill_slashes(optarg);
222 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
223 break;
224 case 'i':
225 path_kill_slashes(optarg);
226 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
227 break;
228 case 'p':
229 arg_pss = true;
230 break;
231 case 'x':
232 r = safe_atod(optarg, &arg_scale_x);
233 if (r < 0)
234 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
235 optarg);
236 break;
237 case 'y':
238 r = safe_atod(optarg, &arg_scale_y);
239 if (r < 0)
240 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
241 optarg);
242 break;
243 case 'e':
244 arg_entropy = true;
245 break;
246 case ARG_PERCPU:
247 arg_percpu = true;
248 break;
249 case 'h':
250 help();
251 return 0;
252 case '?':
253 if (getpid() != 1)
254 return -EINVAL;
255 else
256 return 0;
257 default:
258 assert_not_reached("Unhandled option code.");
259 }
260
261 if (arg_hz <= 0) {
262 log_error("Frequency needs to be > 0");
263 return -EINVAL;
264 }
265
266 return 1;
267 }
268
269 static void do_journal_append(char *file) {
270 struct iovec iovec[5];
271 int r, j = 0;
272 ssize_t n;
273 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
274 *p = NULL;
275 _cleanup_close_ int fd = -1;
276
277 bootchart_file = strappend("BOOTCHART_FILE=", file);
278 if (bootchart_file)
279 IOVEC_SET_STRING(iovec[j++], bootchart_file);
280
281 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
282 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
283 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
284 if (bootchart_message)
285 IOVEC_SET_STRING(iovec[j++], bootchart_message);
286
287 p = malloc(9 + BOOTCHART_MAX);
288 if (!p) {
289 log_oom();
290 return;
291 }
292
293 memcpy(p, "BOOTCHART=", 10);
294
295 fd = open(file, O_RDONLY|O_CLOEXEC);
296 if (fd < 0) {
297 log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file);
298 return;
299 }
300
301 n = loop_read(fd, p + 10, BOOTCHART_MAX, false);
302 if (n < 0) {
303 log_error_errno(n, "Failed to read bootchart data: %m");
304 return;
305 }
306
307 iovec[j].iov_base = p;
308 iovec[j].iov_len = 10 + n;
309 j++;
310
311 r = sd_journal_sendv(iovec, j);
312 if (r < 0)
313 log_error_errno(r, "Failed to send bootchart: %m");
314 }
315
316 int main(int argc, char *argv[]) {
317 _cleanup_free_ char *build = NULL;
318 struct sigaction sig = {
319 .sa_handler = signal_handler,
320 };
321 struct ps_struct *ps;
322 char output_file[PATH_MAX];
323 char datestr[200];
324 time_t t = 0;
325 int r;
326 struct rlimit rlim;
327 bool has_procfs = false;
328
329 parse_conf();
330
331 r = parse_argv(argc, argv);
332 if (r <= 0)
333 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
334
335 /*
336 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
337 * fork:
338 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
339 * - child logs data
340 */
341 if (getpid() == 1) {
342 if (fork()) {
343 /* parent */
344 execl(arg_init_path, arg_init_path, NULL);
345 }
346 }
347 argv[0][0] = '@';
348
349 rlim.rlim_cur = 4096;
350 rlim.rlim_max = 4096;
351 (void) setrlimit(RLIMIT_NOFILE, &rlim);
352
353 /* start with empty ps LL */
354 ps_first = new0(struct ps_struct, 1);
355 if (!ps_first) {
356 log_oom();
357 return EXIT_FAILURE;
358 }
359
360 /* handle TERM/INT nicely */
361 sigaction(SIGHUP, &sig, NULL);
362
363 interval = (1.0 / arg_hz) * 1000000000.0;
364
365 log_uptime();
366
367 if (graph_start < 0.0) {
368 fprintf(stderr,
369 "Failed to setup graph start time.\n\nThe system uptime "
370 "probably includes time that the system was suspended. "
371 "Use --rel to bypass this issue.\n");
372 exit (EXIT_FAILURE);
373 }
374
375 has_procfs = access("/proc/vmstat", F_OK) == 0;
376
377 LIST_HEAD_INIT(head);
378
379 /* main program loop */
380 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
381 int res;
382 double sample_stop;
383 struct timespec req;
384 time_t newint_s;
385 long newint_ns;
386 double elapsed;
387 double timeleft;
388
389 sampledata = new0(struct list_sample_data, 1);
390 if (sampledata == NULL) {
391 log_oom();
392 return EXIT_FAILURE;
393 }
394
395 sampledata->sampletime = gettime_ns();
396 sampledata->counter = samples;
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 }