]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
util-lib: split our string related calls from util.[ch] into its own file 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 <errno.h>
37 #include <fcntl.h>
38 #include <getopt.h>
39 #include <limits.h>
40 #include <signal.h>
41 #include <stdbool.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <sys/resource.h>
46 #include <time.h>
47 #include <unistd.h>
48
49 #include "sd-journal.h"
50
51 #include "bootchart.h"
52 #include "conf-parser.h"
53 #include "fileio.h"
54 #include "list.h"
55 #include "macro.h"
56 #include "path-util.h"
57 #include "store.h"
58 #include "string-util.h"
59 #include "strxcpyx.h"
60 #include "svg.h"
61 #include "util.h"
62
63 static int exiting = 0;
64
65 #define DEFAULT_SAMPLES_LEN 500
66 #define DEFAULT_HZ 25.0
67 #define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */
68 #define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */
69 #define DEFAULT_INIT ROOTLIBEXECDIR "/systemd"
70 #define DEFAULT_OUTPUT "/run/log"
71
72 /* graph defaults */
73 bool arg_entropy = false;
74 bool arg_initcall = true;
75 bool arg_relative = false;
76 bool arg_filter = true;
77 bool arg_show_cmdline = false;
78 bool arg_show_cgroup = false;
79 bool arg_pss = false;
80 bool arg_percpu = false;
81 int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */
82 double arg_hz = DEFAULT_HZ;
83 double arg_scale_x = DEFAULT_SCALE_X;
84 double arg_scale_y = DEFAULT_SCALE_Y;
85
86 char arg_init_path[PATH_MAX] = DEFAULT_INIT;
87 char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT;
88
89 static void signal_handler(int 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 int do_journal_append(char *file) {
257 _cleanup_free_ char *bootchart_message = NULL;
258 _cleanup_free_ char *bootchart_file = NULL;
259 _cleanup_free_ char *p = NULL;
260 _cleanup_close_ int fd = -1;
261 struct iovec iovec[5];
262 int r, j = 0;
263 ssize_t n;
264
265 bootchart_file = strappend("BOOTCHART_FILE=", file);
266 if (!bootchart_file)
267 return log_oom();
268
269 IOVEC_SET_STRING(iovec[j++], bootchart_file);
270 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
271 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
272 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
273 if (!bootchart_message)
274 return log_oom();
275
276 IOVEC_SET_STRING(iovec[j++], bootchart_message);
277
278 p = malloc(10 + BOOTCHART_MAX);
279 if (!p)
280 return log_oom();
281
282 memcpy(p, "BOOTCHART=", 10);
283
284 fd = open(file, O_RDONLY|O_CLOEXEC);
285 if (fd < 0)
286 return log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file);
287
288 n = loop_read(fd, p + 10, BOOTCHART_MAX, false);
289 if (n < 0)
290 return log_error_errno(n, "Failed to read bootchart data: %m");
291
292 iovec[j].iov_base = p;
293 iovec[j].iov_len = 10 + n;
294 j++;
295
296 r = sd_journal_sendv(iovec, j);
297 if (r < 0)
298 log_error_errno(r, "Failed to send bootchart: %m");
299
300 return 0;
301 }
302
303 int main(int argc, char *argv[]) {
304 static struct list_sample_data *sampledata;
305 _cleanup_closedir_ DIR *proc = NULL;
306 _cleanup_free_ char *build = NULL;
307 _cleanup_fclose_ FILE *of = NULL;
308 _cleanup_close_ int sysfd = -1;
309 struct ps_struct *ps_first;
310 double graph_start;
311 double log_start;
312 double interval;
313 char output_file[PATH_MAX];
314 char datestr[200];
315 int pscount = 0;
316 int n_cpus = 0;
317 int overrun = 0;
318 time_t t = 0;
319 int r, samples;
320 struct ps_struct *ps;
321 struct rlimit rlim;
322 struct list_sample_data *head;
323 struct sigaction sig = {
324 .sa_handler = signal_handler,
325 };
326
327 parse_conf();
328
329 r = parse_argv(argc, argv);
330 if (r < 0)
331 return EXIT_FAILURE;
332
333 if (r == 0)
334 return EXIT_SUCCESS;
335
336 /*
337 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
338 * fork:
339 * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1
340 * - child logs data
341 */
342 if (getpid() == 1) {
343 if (fork())
344 /* parent */
345 execl(arg_init_path, arg_init_path, NULL);
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 if (arg_relative)
366 graph_start = log_start = gettime_ns();
367 else {
368 struct timespec n;
369 double uptime;
370
371 clock_gettime(clock_boottime_or_monotonic(), &n);
372 uptime = (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
373
374 log_start = gettime_ns();
375 graph_start = log_start - uptime;
376 }
377
378 if (graph_start < 0.0) {
379 log_error("Failed to setup graph start time.\n\n"
380 "The system uptime probably includes time that the system was suspended. "
381 "Use --rel to bypass this issue.");
382 return EXIT_FAILURE;
383 }
384
385 LIST_HEAD_INIT(head);
386
387 /* main program loop */
388 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
389 int res;
390 double sample_stop;
391 double elapsed;
392 double timeleft;
393
394 sampledata = new0(struct list_sample_data, 1);
395 if (sampledata == NULL) {
396 log_oom();
397 return EXIT_FAILURE;
398 }
399
400 sampledata->sampletime = gettime_ns();
401 sampledata->counter = samples;
402
403 if (sysfd < 0)
404 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
405
406 if (!build) {
407 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
408 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
409 }
410
411 if (proc)
412 rewinddir(proc);
413 else
414 proc = opendir("/proc");
415
416 /* wait for /proc to become available, discarding samples */
417 if (proc) {
418 r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus);
419 if (r < 0)
420 return EXIT_FAILURE;
421 }
422
423 sample_stop = gettime_ns();
424
425 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
426 timeleft = interval - elapsed;
427
428 /*
429 * check if we have not consumed our entire timeslice. If we
430 * do, don't sleep and take a new sample right away.
431 * we'll lose all the missed samples and overrun our total
432 * time
433 */
434 if (timeleft > 0) {
435 struct timespec req;
436
437 req.tv_sec = (time_t)(timeleft / 1000000000.0);
438 req.tv_nsec = (long)(timeleft - (req.tv_sec * 1000000000.0));
439
440 res = nanosleep(&req, NULL);
441 if (res) {
442 if (errno == EINTR)
443 /* caught signal, probably HUP! */
444 break;
445 log_error_errno(errno, "nanosleep() failed: %m");
446 return EXIT_FAILURE;
447 }
448 } else {
449 overrun++;
450 /* calculate how many samples we lost and scrap them */
451 arg_samples_len -= (int)(-timeleft / interval);
452 }
453 LIST_PREPEND(link, head, sampledata);
454 }
455
456 /* do some cleanup, close fd's */
457 ps = ps_first;
458 while (ps->next_ps) {
459 ps = ps->next_ps;
460 ps->schedstat = safe_close(ps->schedstat);
461 ps->sched = safe_close(ps->sched);
462 ps->smaps = safe_fclose(ps->smaps);
463 }
464
465 if (!of) {
466 t = time(NULL);
467 r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
468 assert_se(r > 0);
469
470 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
471 of = fopen(output_file, "we");
472 }
473
474 if (!of) {
475 log_error("Error opening output file '%s': %m\n", output_file);
476 return EXIT_FAILURE;
477 }
478
479 r = svg_do(of, strna(build), head, ps_first,
480 samples, pscount, n_cpus, graph_start,
481 log_start, interval, overrun);
482
483 if (r < 0) {
484 log_error_errno(r, "Error generating svg file: %m");
485 return EXIT_FAILURE;
486 }
487
488 log_info("systemd-bootchart wrote %s\n", output_file);
489
490 r = do_journal_append(output_file);
491 if (r < 0)
492 return EXIT_FAILURE;
493
494 /* nitpic cleanups */
495 ps = ps_first->next_ps;
496 while (ps->next_ps) {
497 struct ps_struct *old;
498
499 old = ps;
500 old->sample = ps->first;
501 ps = ps->next_ps;
502 while (old->sample->next) {
503 struct ps_sched_struct *oldsample = old->sample;
504
505 old->sample = old->sample->next;
506 free(oldsample);
507 }
508 free(old->cgroup);
509 free(old->sample);
510 free(old);
511 }
512
513 free(ps->cgroup);
514 free(ps->sample);
515 free(ps);
516
517 sampledata = head;
518 while (sampledata->link_prev) {
519 struct list_sample_data *old_sampledata = sampledata;
520 sampledata = sampledata->link_prev;
521 free(old_sampledata);
522 }
523 free(sampledata);
524
525 /* don't complain when overrun once, happens most commonly on 1st sample */
526 if (overrun > 1)
527 log_warning("systemd-bootchart: sample time overrun %i times\n", overrun);
528
529 return 0;
530 }