]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
bootchart: display each CPU utilization/wait
[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 bool arg_percpu = false;
91 int samples;
92 int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */
93 double arg_hz = DEFAULT_HZ;
94 double arg_scale_x = DEFAULT_SCALE_X;
95 double arg_scale_y = DEFAULT_SCALE_Y;
96 static struct list_sample_data *sampledata;
97 struct list_sample_data *head;
98
99 char arg_init_path[PATH_MAX] = DEFAULT_INIT;
100 char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT;
101
102 static void signal_handler(int sig) {
103 if (sig++)
104 sig--;
105 exiting = 1;
106 }
107
108 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
109
110 #define BOOTCHART_MAX (16*1024*1024)
111
112 static void parse_conf(void) {
113 char *init = NULL, *output = NULL;
114 const ConfigTableItem items[] = {
115 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
116 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
117 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
118 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
119 { "Bootchart", "Output", config_parse_path, 0, &output },
120 { "Bootchart", "Init", config_parse_path, 0, &init },
121 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
122 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
123 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
124 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
125 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
126 { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu },
127 { NULL, NULL, NULL, 0, NULL }
128 };
129
130 config_parse_many(BOOTCHART_CONF,
131 CONF_DIRS_NULSTR("systemd/bootchart.conf"),
132 NULL, config_item_table_lookup, items, true, NULL);
133
134 if (init != NULL)
135 strscpy(arg_init_path, sizeof(arg_init_path), init);
136 if (output != NULL)
137 strscpy(arg_output_path, sizeof(arg_output_path), output);
138 }
139
140 static void help(void) {
141 fprintf(stdout,
142 "Usage: %s [OPTIONS]\n\n"
143 "Options:\n"
144 " -r, --rel Record time relative to recording\n"
145 " -f, --freq=FREQ Sample frequency [%g]\n"
146 " -n, --samples=N Stop sampling at [%d] samples\n"
147 " -x, --scale-x=N Scale the graph horizontally [%g] \n"
148 " -y, --scale-y=N Scale the graph vertically [%g] \n"
149 " -p, --pss Enable PSS graph (CPU intensive)\n"
150 " -e, --entropy Enable the entropy_avail graph\n"
151 " -o, --output=PATH Path to output files [%s]\n"
152 " -i, --init=PATH Path to init executable [%s]\n"
153 " -F, --no-filter Disable filtering of unimportant or ephemeral processes\n"
154 " -C, --cmdline Display full command lines with arguments\n"
155 " -c, --control-group Display process control group\n"
156 " --per-cpu Draw each CPU utilization and wait bar also\n"
157 " -h, --help Display this message\n\n"
158 "See bootchart.conf for more information.\n",
159 program_invocation_short_name,
160 DEFAULT_HZ,
161 DEFAULT_SAMPLES_LEN,
162 DEFAULT_SCALE_X,
163 DEFAULT_SCALE_Y,
164 DEFAULT_OUTPUT,
165 DEFAULT_INIT);
166 }
167
168 static int parse_argv(int argc, char *argv[]) {
169
170 enum {
171 ARG_PERCPU = 0x100,
172 };
173
174 static const struct option options[] = {
175 {"rel", no_argument, NULL, 'r' },
176 {"freq", required_argument, NULL, 'f' },
177 {"samples", required_argument, NULL, 'n' },
178 {"pss", no_argument, NULL, 'p' },
179 {"output", required_argument, NULL, 'o' },
180 {"init", required_argument, NULL, 'i' },
181 {"no-filter", no_argument, NULL, 'F' },
182 {"cmdline", no_argument, NULL, 'C' },
183 {"control-group", no_argument, NULL, 'c' },
184 {"help", no_argument, NULL, 'h' },
185 {"scale-x", required_argument, NULL, 'x' },
186 {"scale-y", required_argument, NULL, 'y' },
187 {"entropy", no_argument, NULL, 'e' },
188 {"per-cpu", no_argument, NULL, ARG_PERCPU},
189 {}
190 };
191 int c, r;
192
193 if (getpid() == 1)
194 opterr = 0;
195
196 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
197 switch (c) {
198
199 case 'r':
200 arg_relative = true;
201 break;
202 case 'f':
203 r = safe_atod(optarg, &arg_hz);
204 if (r < 0)
205 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
206 optarg);
207 break;
208 case 'F':
209 arg_filter = false;
210 break;
211 case 'C':
212 arg_show_cmdline = true;
213 break;
214 case 'c':
215 arg_show_cgroup = true;
216 break;
217 case 'n':
218 r = safe_atoi(optarg, &arg_samples_len);
219 if (r < 0)
220 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
221 optarg);
222 break;
223 case 'o':
224 path_kill_slashes(optarg);
225 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
226 break;
227 case 'i':
228 path_kill_slashes(optarg);
229 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
230 break;
231 case 'p':
232 arg_pss = true;
233 break;
234 case 'x':
235 r = safe_atod(optarg, &arg_scale_x);
236 if (r < 0)
237 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
238 optarg);
239 break;
240 case 'y':
241 r = safe_atod(optarg, &arg_scale_y);
242 if (r < 0)
243 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
244 optarg);
245 break;
246 case 'e':
247 arg_entropy = true;
248 break;
249 case ARG_PERCPU:
250 arg_percpu = true;
251 break;
252 case 'h':
253 help();
254 return 0;
255 case '?':
256 if (getpid() != 1)
257 return -EINVAL;
258 else
259 return 0;
260 default:
261 assert_not_reached("Unhandled option code.");
262 }
263
264 if (arg_hz <= 0) {
265 log_error("Frequency needs to be > 0");
266 return -EINVAL;
267 }
268
269 return 1;
270 }
271
272 static void do_journal_append(char *file) {
273 struct iovec iovec[5];
274 int r, f, j = 0;
275 ssize_t n;
276 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
277 *p = NULL;
278
279 bootchart_file = strappend("BOOTCHART_FILE=", file);
280 if (bootchart_file)
281 IOVEC_SET_STRING(iovec[j++], bootchart_file);
282
283 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
284 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
285 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
286 if (bootchart_message)
287 IOVEC_SET_STRING(iovec[j++], bootchart_message);
288
289 p = malloc(9 + BOOTCHART_MAX);
290 if (!p) {
291 log_oom();
292 return;
293 }
294
295 memcpy(p, "BOOTCHART=", 10);
296
297 f = open(file, O_RDONLY|O_CLOEXEC);
298 if (f < 0) {
299 log_error_errno(errno, "Failed to read bootchart data: %m");
300 return;
301 }
302 n = loop_read(f, p + 10, BOOTCHART_MAX, false);
303 if (n < 0) {
304 log_error_errno(n, "Failed to read bootchart data: %m");
305 close(f);
306 return;
307 }
308 close(f);
309
310 iovec[j].iov_base = p;
311 iovec[j].iov_len = 10 + n;
312 j++;
313
314 r = sd_journal_sendv(iovec, j);
315 if (r < 0)
316 log_error_errno(r, "Failed to send bootchart: %m");
317 }
318
319 int main(int argc, char *argv[]) {
320 _cleanup_free_ char *build = NULL;
321 struct sigaction sig = {
322 .sa_handler = signal_handler,
323 };
324 struct ps_struct *ps;
325 char output_file[PATH_MAX];
326 char datestr[200];
327 time_t t = 0;
328 int r;
329 struct rlimit rlim;
330 bool has_procfs = false;
331
332 parse_conf();
333
334 r = parse_argv(argc, argv);
335 if (r <= 0)
336 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
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 }
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 log_uptime();
369
370 if (graph_start < 0.0) {
371 fprintf(stderr,
372 "Failed to setup graph start time.\n\nThe system uptime "
373 "probably includes time that the system was suspended. "
374 "Use --rel to bypass this issue.\n");
375 exit (EXIT_FAILURE);
376 }
377
378 has_procfs = access("/proc/vmstat", F_OK) == 0;
379
380 LIST_HEAD_INIT(head);
381
382 /* main program loop */
383 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
384 int res;
385 double sample_stop;
386 struct timespec req;
387 time_t newint_s;
388 long newint_ns;
389 double elapsed;
390 double timeleft;
391
392 sampledata = new0(struct list_sample_data, 1);
393 if (sampledata == NULL) {
394 log_oom();
395 return EXIT_FAILURE;
396 }
397
398 sampledata->sampletime = gettime_ns();
399 sampledata->counter = samples;
400
401 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
402 t = time(NULL);
403 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
404 assert_se(r > 0);
405
406 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
407 of = fopen(output_file, "we");
408 }
409
410 if (sysfd < 0)
411 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
412
413 if (!build) {
414 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
415 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
416 }
417
418 if (has_procfs)
419 log_sample(samples, &sampledata);
420 else
421 /* wait for /proc to become available, discarding samples */
422 has_procfs = access("/proc/vmstat", F_OK) == 0;
423
424 sample_stop = gettime_ns();
425
426 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
427 timeleft = interval - elapsed;
428
429 newint_s = (time_t)(timeleft / 1000000000.0);
430 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
431
432 /*
433 * check if we have not consumed our entire timeslice. If we
434 * do, don't sleep and take a new sample right away.
435 * we'll lose all the missed samples and overrun our total
436 * time
437 */
438 if (newint_ns > 0 || newint_s > 0) {
439 req.tv_sec = newint_s;
440 req.tv_nsec = newint_ns;
441
442 res = nanosleep(&req, NULL);
443 if (res) {
444 if (errno == EINTR) {
445 /* caught signal, probably HUP! */
446 break;
447 }
448 log_error_errno(errno, "nanosleep() failed: %m");
449 exit(EXIT_FAILURE);
450 }
451 } else {
452 overrun++;
453 /* calculate how many samples we lost and scrap them */
454 arg_samples_len -= (int)(newint_ns / 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 if (ps->schedstat)
464 close(ps->schedstat);
465 if (ps->sched)
466 close(ps->sched);
467 if (ps->smaps)
468 fclose(ps->smaps);
469 }
470
471 if (!of) {
472 t = time(NULL);
473 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
474 assert_se(r > 0);
475
476 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
477 of = fopen(output_file, "we");
478 }
479
480 if (!of) {
481 fprintf(stderr, "opening output file '%s': %m\n", output_file);
482 exit (EXIT_FAILURE);
483 }
484
485 svg_do(strna(build));
486
487 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
488
489 do_journal_append(output_file);
490
491 if (of)
492 fclose(of);
493
494 closedir(proc);
495 if (sysfd >= 0)
496 close(sysfd);
497
498 /* nitpic cleanups */
499 ps = ps_first->next_ps;
500 while (ps->next_ps) {
501 struct ps_struct *old;
502
503 old = ps;
504 old->sample = ps->first;
505 ps = ps->next_ps;
506 while (old->sample->next) {
507 struct ps_sched_struct *oldsample = old->sample;
508
509 old->sample = old->sample->next;
510 free(oldsample);
511 }
512 free(old->cgroup);
513 free(old->sample);
514 free(old);
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 /* don't complain when overrun once, happens most commonly on 1st sample */
528 if (overrun > 1)
529 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
530
531 return 0;
532 }