]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
treewide: use log_*_errno whenever %m is in the format string
[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 "/sbin/init"
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(NULL, BOOTCHART_CONF, NULL,
129 NULL,
130 config_item_table_lookup, items,
131 true, false, true, NULL);
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
139 static 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
166 static int parse_argv(int argc, char *argv[]) {
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 {}
182 };
183 int c, r;
184
185 if (getpid() == 1)
186 opterr = 0;
187
188 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
189 switch (c) {
190
191 case 'r':
192 arg_relative = true;
193 break;
194 case 'f':
195 r = safe_atod(optarg, &arg_hz);
196 if (r < 0)
197 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
198 optarg);
199 break;
200 case 'F':
201 arg_filter = false;
202 break;
203 case 'C':
204 arg_show_cmdline = true;
205 break;
206 case 'c':
207 arg_show_cgroup = true;
208 break;
209 case 'n':
210 r = safe_atoi(optarg, &arg_samples_len);
211 if (r < 0)
212 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
213 optarg);
214 break;
215 case 'o':
216 path_kill_slashes(optarg);
217 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
218 break;
219 case 'i':
220 path_kill_slashes(optarg);
221 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
222 break;
223 case 'p':
224 arg_pss = true;
225 break;
226 case 'x':
227 r = safe_atod(optarg, &arg_scale_x);
228 if (r < 0)
229 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
230 optarg);
231 break;
232 case 'y':
233 r = safe_atod(optarg, &arg_scale_y);
234 if (r < 0)
235 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
236 optarg);
237 break;
238 case 'e':
239 arg_entropy = true;
240 break;
241 case 'h':
242 help();
243 return 0;
244 case '?':
245 if (getpid() != 1)
246 return -EINVAL;
247 else
248 return 0;
249 default:
250 assert_not_reached("Unhandled option code.");
251 }
252
253 if (arg_hz <= 0) {
254 log_error("Frequency needs to be > 0");
255 return -EINVAL;
256 }
257
258 return 1;
259 }
260
261 static void do_journal_append(char *file) {
262 struct iovec iovec[5];
263 int r, f, j = 0;
264 ssize_t n;
265 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
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) {
280 log_oom();
281 return;
282 }
283
284 memcpy(p, "BOOTCHART=", 10);
285
286 f = open(file, O_RDONLY|O_CLOEXEC);
287 if (f < 0) {
288 log_error_errno(errno, "Failed to read bootchart data: %m");
289 return;
290 }
291 n = loop_read(f, p + 10, BOOTCHART_MAX, false);
292 if (n < 0) {
293 log_error_errno(n, "Failed to read bootchart data: %m");
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)
305 log_error_errno(r, "Failed to send bootchart: %m");
306 }
307
308 int main(int argc, char *argv[]) {
309 _cleanup_free_ char *build = NULL;
310 struct sigaction sig = {
311 .sa_handler = signal_handler,
312 };
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;
319 bool has_procfs = false;
320
321 parse_conf();
322
323 r = parse_argv(argc, argv);
324 if (r <= 0)
325 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
326
327 /*
328 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
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 */
336 execl(arg_init_path, arg_init_path, NULL);
337 }
338 }
339 argv[0][0] = '@';
340
341 rlim.rlim_cur = 4096;
342 rlim.rlim_max = 4096;
343 (void) setrlimit(RLIMIT_NOFILE, &rlim);
344
345 /* start with empty ps LL */
346 ps_first = new0(struct ps_struct, 1);
347 if (!ps_first) {
348 log_oom();
349 return EXIT_FAILURE;
350 }
351
352 /* handle TERM/INT nicely */
353 sigaction(SIGHUP, &sig, NULL);
354
355 interval = (1.0 / arg_hz) * 1000000000.0;
356
357 log_uptime();
358
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
367 has_procfs = access("/proc/vmstat", F_OK) == 0;
368
369 LIST_HEAD_INIT(head);
370
371 /* main program loop */
372 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
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
381 sampledata = new0(struct list_sample_data, 1);
382 if (sampledata == NULL) {
383 log_oom();
384 return EXIT_FAILURE;
385 }
386
387 sampledata->sampletime = gettime_ns();
388 sampledata->counter = samples;
389
390 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
391 t = time(NULL);
392 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
393 assert_se(r > 0);
394
395 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
396 of = fopen(output_file, "we");
397 }
398
399 if (sysfd < 0)
400 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
401
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 }
406
407 if (has_procfs)
408 log_sample(samples, &sampledata);
409 else
410 /* wait for /proc to become available, discarding samples */
411 has_procfs = access("/proc/vmstat", F_OK) == 0;
412
413 sample_stop = gettime_ns();
414
415 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
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 */
427 if (newint_ns > 0 || newint_s > 0) {
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 }
437 log_error_errno(errno, "nanosleep() failed: %m");
438 exit(EXIT_FAILURE);
439 }
440 } else {
441 overrun++;
442 /* calculate how many samples we lost and scrap them */
443 arg_samples_len -= (int)(newint_ns / interval);
444 }
445 LIST_PREPEND(link, head, sampledata);
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 }
459
460 if (!of) {
461 t = time(NULL);
462 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
463 assert_se(r > 0);
464
465 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
466 of = fopen(output_file, "we");
467 }
468
469 if (!of) {
470 fprintf(stderr, "opening output file '%s': %m\n", output_file);
471 exit (EXIT_FAILURE);
472 }
473
474 svg_do(strna(build));
475
476 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
477
478 do_journal_append(output_file);
479
480 if (of)
481 fclose(of);
482
483 closedir(proc);
484 if (sysfd >= 0)
485 close(sysfd);
486
487 /* nitpic cleanups */
488 ps = ps_first->next_ps;
489 while (ps->next_ps) {
490 struct ps_struct *old;
491
492 old = ps;
493 old->sample = ps->first;
494 ps = ps->next_ps;
495 while (old->sample->next) {
496 struct ps_sched_struct *oldsample = old->sample;
497
498 old->sample = old->sample->next;
499 free(oldsample);
500 }
501 free(old->cgroup);
502 free(old->sample);
503 free(old);
504 }
505 free(ps->cgroup);
506 free(ps->sample);
507 free(ps);
508
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);
516 /* don't complain when overrun once, happens most commonly on 1st sample */
517 if (overrun > 1)
518 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
519
520 return 0;
521 }