]> git.proxmox.com Git - systemd.git/blob - src/bootchart/bootchart.c
Merge tag 'upstream/229'
[systemd.git] / src / bootchart / bootchart.c
1 /***
2 This file is part of systemd.
3
4 Copyright (C) 2009-2013 Intel Corporation
5
6 Authors:
7 Auke Kok <auke-jan.h.kok@intel.com>
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 /***
24
25 Many thanks to those who contributed ideas and code:
26 - Ziga Mahkovec - Original bootchart author
27 - Anders Norgaard - PyBootchartgui
28 - Michael Meeks - bootchart2
29 - Scott James Remnant - Ubuntu C-based logger
30 - Arjan van der Ven - for the idea to merge bootgraph.pl functionality
31
32 ***/
33
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <getopt.h>
37 #include <limits.h>
38 #include <signal.h>
39 #include <stdbool.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <sys/resource.h>
44 #include <time.h>
45 #include <unistd.h>
46
47 #include "sd-journal.h"
48
49 #include "alloc-util.h"
50 #include "bootchart.h"
51 #include "conf-parser.h"
52 #include "def.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_MAX (16*1024*1024)
97
98 static void parse_conf(void) {
99 char *init = NULL, *output = NULL;
100 const ConfigTableItem items[] = {
101 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
102 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
103 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
104 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
105 { "Bootchart", "Output", config_parse_path, 0, &output },
106 { "Bootchart", "Init", config_parse_path, 0, &init },
107 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
108 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
109 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
110 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
111 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
112 { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu },
113 { NULL, NULL, NULL, 0, NULL }
114 };
115
116 config_parse_many(PKGSYSCONFDIR "/bootchart.conf",
117 CONF_PATHS_NULSTR("systemd/bootchart.conf.d"),
118 NULL, config_item_table_lookup, items, true, NULL);
119
120 if (init != NULL)
121 strscpy(arg_init_path, sizeof(arg_init_path), init);
122 if (output != NULL)
123 strscpy(arg_output_path, sizeof(arg_output_path), output);
124 }
125
126 static void help(void) {
127 printf("Usage: %s [OPTIONS]\n\n"
128 "Options:\n"
129 " -r --rel Record time relative to recording\n"
130 " -f --freq=FREQ Sample frequency [%g]\n"
131 " -n --samples=N Stop sampling at [%d] samples\n"
132 " -x --scale-x=N Scale the graph horizontally [%g] \n"
133 " -y --scale-y=N Scale the graph vertically [%g] \n"
134 " -p --pss Enable PSS graph (CPU intensive)\n"
135 " -e --entropy Enable the entropy_avail graph\n"
136 " -o --output=PATH Path to output files [%s]\n"
137 " -i --init=PATH Path to init executable [%s]\n"
138 " -F --no-filter Disable filtering of unimportant or ephemeral processes\n"
139 " -C --cmdline Display full command lines with arguments\n"
140 " -c --control-group Display process control group\n"
141 " --per-cpu Draw each CPU utilization and wait bar also\n"
142 " -h --help Display this message\n\n"
143 "See bootchart.conf for more information.\n",
144 program_invocation_short_name,
145 DEFAULT_HZ,
146 DEFAULT_SAMPLES_LEN,
147 DEFAULT_SCALE_X,
148 DEFAULT_SCALE_Y,
149 DEFAULT_OUTPUT,
150 DEFAULT_INIT);
151 }
152
153 static int parse_argv(int argc, char *argv[]) {
154
155 enum {
156 ARG_PERCPU = 0x100,
157 };
158
159 static const struct option options[] = {
160 {"rel", no_argument, NULL, 'r' },
161 {"freq", required_argument, NULL, 'f' },
162 {"samples", required_argument, NULL, 'n' },
163 {"pss", no_argument, NULL, 'p' },
164 {"output", required_argument, NULL, 'o' },
165 {"init", required_argument, NULL, 'i' },
166 {"no-filter", no_argument, NULL, 'F' },
167 {"cmdline", no_argument, NULL, 'C' },
168 {"control-group", no_argument, NULL, 'c' },
169 {"help", no_argument, NULL, 'h' },
170 {"scale-x", required_argument, NULL, 'x' },
171 {"scale-y", required_argument, NULL, 'y' },
172 {"entropy", no_argument, NULL, 'e' },
173 {"per-cpu", no_argument, NULL, ARG_PERCPU},
174 {}
175 };
176 int c, r;
177
178 if (getpid() == 1)
179 opterr = 0;
180
181 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0)
182 switch (c) {
183
184 case 'r':
185 arg_relative = true;
186 break;
187 case 'f':
188 r = safe_atod(optarg, &arg_hz);
189 if (r < 0)
190 log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m",
191 optarg);
192 break;
193 case 'F':
194 arg_filter = false;
195 break;
196 case 'C':
197 arg_show_cmdline = true;
198 break;
199 case 'c':
200 arg_show_cgroup = true;
201 break;
202 case 'n':
203 r = safe_atoi(optarg, &arg_samples_len);
204 if (r < 0)
205 log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m",
206 optarg);
207 break;
208 case 'o':
209 path_kill_slashes(optarg);
210 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
211 break;
212 case 'i':
213 path_kill_slashes(optarg);
214 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
215 break;
216 case 'p':
217 arg_pss = true;
218 break;
219 case 'x':
220 r = safe_atod(optarg, &arg_scale_x);
221 if (r < 0)
222 log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m",
223 optarg);
224 break;
225 case 'y':
226 r = safe_atod(optarg, &arg_scale_y);
227 if (r < 0)
228 log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m",
229 optarg);
230 break;
231 case 'e':
232 arg_entropy = true;
233 break;
234 case ARG_PERCPU:
235 arg_percpu = true;
236 break;
237 case 'h':
238 help();
239 return 0;
240 case '?':
241 if (getpid() != 1)
242 return -EINVAL;
243 else
244 return 0;
245 default:
246 assert_not_reached("Unhandled option code.");
247 }
248
249 if (arg_hz <= 0) {
250 log_error("Frequency needs to be > 0");
251 return -EINVAL;
252 }
253
254 return 1;
255 }
256
257 static int do_journal_append(char *file) {
258 _cleanup_free_ char *bootchart_message = NULL;
259 _cleanup_free_ char *bootchart_file = NULL;
260 _cleanup_free_ char *p = NULL;
261 _cleanup_close_ int fd = -1;
262 struct iovec iovec[5];
263 int r, j = 0;
264 ssize_t n;
265
266 bootchart_file = strappend("BOOTCHART_FILE=", file);
267 if (!bootchart_file)
268 return log_oom();
269
270 IOVEC_SET_STRING(iovec[j++], bootchart_file);
271 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
272 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
273 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
274 if (!bootchart_message)
275 return log_oom();
276
277 IOVEC_SET_STRING(iovec[j++], bootchart_message);
278
279 p = malloc(10 + BOOTCHART_MAX);
280 if (!p)
281 return log_oom();
282
283 memcpy(p, "BOOTCHART=", 10);
284
285 fd = open(file, O_RDONLY|O_CLOEXEC);
286 if (fd < 0)
287 return log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file);
288
289 n = loop_read(fd, p + 10, BOOTCHART_MAX, false);
290 if (n < 0)
291 return log_error_errno(n, "Failed to read bootchart data: %m");
292
293 iovec[j].iov_base = p;
294 iovec[j].iov_len = 10 + n;
295 j++;
296
297 r = sd_journal_sendv(iovec, j);
298 if (r < 0)
299 log_error_errno(r, "Failed to send bootchart: %m");
300
301 return 0;
302 }
303
304 int main(int argc, char *argv[]) {
305 static struct list_sample_data *sampledata;
306 _cleanup_closedir_ DIR *proc = NULL;
307 _cleanup_free_ char *build = NULL;
308 _cleanup_fclose_ FILE *of = NULL;
309 _cleanup_close_ int sysfd = -1;
310 struct ps_struct *ps_first;
311 double graph_start;
312 double log_start;
313 double interval;
314 char output_file[PATH_MAX];
315 char datestr[200];
316 int pscount = 0;
317 int n_cpus = 0;
318 int overrun = 0;
319 time_t t = 0;
320 int r, samples;
321 struct ps_struct *ps;
322 struct rlimit rlim;
323 struct list_sample_data *head;
324 struct sigaction sig = {
325 .sa_handler = signal_handler,
326 };
327
328 parse_conf();
329
330 r = parse_argv(argc, argv);
331 if (r < 0)
332 return EXIT_FAILURE;
333
334 if (r == 0)
335 return EXIT_SUCCESS;
336
337 /*
338 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
339 * fork:
340 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
341 * - child logs data
342 */
343 if (getpid() == 1) {
344 if (fork())
345 /* parent */
346 execl(arg_init_path, arg_init_path, NULL);
347 }
348 argv[0][0] = '@';
349
350 rlim.rlim_cur = 4096;
351 rlim.rlim_max = 4096;
352 (void) setrlimit(RLIMIT_NOFILE, &rlim);
353
354 /* start with empty ps LL */
355 ps_first = new0(struct ps_struct, 1);
356 if (!ps_first) {
357 log_oom();
358 return EXIT_FAILURE;
359 }
360
361 /* handle TERM/INT nicely */
362 sigaction(SIGHUP, &sig, NULL);
363
364 interval = (1.0 / arg_hz) * 1000000000.0;
365
366 if (arg_relative)
367 graph_start = log_start = gettime_ns();
368 else {
369 struct timespec n;
370 double uptime;
371
372 clock_gettime(clock_boottime_or_monotonic(), &n);
373 uptime = (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
374
375 log_start = gettime_ns();
376 graph_start = log_start - uptime;
377 }
378
379 if (graph_start < 0.0) {
380 log_error("Failed to setup graph start time.\n\n"
381 "The system uptime probably includes time that the system was suspended. "
382 "Use --rel to bypass this issue.");
383 return EXIT_FAILURE;
384 }
385
386 LIST_HEAD_INIT(head);
387
388 /* main program loop */
389 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
390 int res;
391 double sample_stop;
392 double elapsed;
393 double timeleft;
394
395 sampledata = new0(struct list_sample_data, 1);
396 if (sampledata == NULL) {
397 log_oom();
398 return EXIT_FAILURE;
399 }
400
401 sampledata->sampletime = gettime_ns();
402 sampledata->counter = samples;
403
404 if (sysfd < 0)
405 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
406
407 if (!build) {
408 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
409 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
410 }
411
412 if (proc)
413 rewinddir(proc);
414 else
415 proc = opendir("/proc");
416
417 /* wait for /proc to become available, discarding samples */
418 if (proc) {
419 r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus);
420 if (r < 0)
421 return EXIT_FAILURE;
422 }
423
424 sample_stop = gettime_ns();
425
426 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
427 timeleft = interval - elapsed;
428
429 /*
430 * check if we have not consumed our entire timeslice. If we
431 * do, don't sleep and take a new sample right away.
432 * we'll lose all the missed samples and overrun our total
433 * time
434 */
435 if (timeleft > 0) {
436 struct timespec req;
437
438 req.tv_sec = (time_t)(timeleft / 1000000000.0);
439 req.tv_nsec = (long)(timeleft - (req.tv_sec * 1000000000.0));
440
441 res = nanosleep(&req, NULL);
442 if (res) {
443 if (errno == EINTR)
444 /* caught signal, probably HUP! */
445 break;
446 log_error_errno(errno, "nanosleep() failed: %m");
447 return EXIT_FAILURE;
448 }
449 } else {
450 overrun++;
451 /* calculate how many samples we lost and scrap them */
452 arg_samples_len -= (int)(-timeleft / interval);
453 }
454 LIST_PREPEND(link, head, sampledata);
455 }
456
457 /* do some cleanup, close fd's */
458 ps = ps_first;
459 while (ps->next_ps) {
460 ps = ps->next_ps;
461 ps->schedstat = safe_close(ps->schedstat);
462 ps->sched = safe_close(ps->sched);
463 ps->smaps = safe_fclose(ps->smaps);
464 }
465
466 if (!of) {
467 t = time(NULL);
468 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
469 assert_se(r > 0);
470
471 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
472 of = fopen(output_file, "we");
473 }
474
475 if (!of) {
476 log_error("Error opening output file '%s': %m\n", output_file);
477 return EXIT_FAILURE;
478 }
479
480 r = svg_do(of, strna(build), head, ps_first,
481 samples, pscount, n_cpus, graph_start,
482 log_start, interval, overrun);
483
484 if (r < 0) {
485 log_error_errno(r, "Error generating svg file: %m");
486 return EXIT_FAILURE;
487 }
488
489 log_info("systemd-bootchart wrote %s\n", output_file);
490
491 r = do_journal_append(output_file);
492 if (r < 0)
493 return EXIT_FAILURE;
494
495 /* nitpic cleanups */
496 ps = ps_first->next_ps;
497 while (ps->next_ps) {
498 struct ps_struct *old;
499
500 old = ps;
501 old->sample = ps->first;
502 ps = ps->next_ps;
503 while (old->sample->next) {
504 struct ps_sched_struct *oldsample = old->sample;
505
506 old->sample = old->sample->next;
507 free(oldsample);
508 }
509 free(old->cgroup);
510 free(old->sample);
511 free(old);
512 }
513
514 free(ps->cgroup);
515 free(ps->sample);
516 free(ps);
517
518 sampledata = head;
519 while (sampledata->link_prev) {
520 struct list_sample_data *old_sampledata = sampledata;
521 sampledata = sampledata->link_prev;
522 free(old_sampledata);
523 }
524 free(sampledata);
525
526 /* don't complain when overrun once, happens most commonly on 1st sample */
527 if (overrun > 1)
528 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun);
529
530 return 0;
531 }