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