]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
bootchart: rewrite usage message more generally
[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 /* graph defaults */
76 bool arg_entropy = false;
77 bool 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 int samples;
84 int arg_samples_len = 500; /* we record len+1 (1 start sample) */
85 double arg_hz = 25.0; /* 20 seconds log time */
86 double arg_scale_x = 100.0; /* 100px = 1sec */
87 double arg_scale_y = 20.0; /* 16px = 1 process bar */
88 static struct list_sample_data *sampledata;
89 struct list_sample_data *head;
90
91 char arg_init_path[PATH_MAX] = "/sbin/init";
92 char arg_output_path[PATH_MAX] = "/run/log";
93
94 static void signal_handler(int sig) {
95 if (sig++)
96 sig--;
97 exiting = 1;
98 }
99
100 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
101
102 #define BOOTCHART_MAX (16*1024*1024)
103
104 static void parse_conf(void) {
105 char *init = NULL, *output = NULL;
106 const ConfigTableItem items[] = {
107 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
108 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
109 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
110 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
111 { "Bootchart", "Output", config_parse_path, 0, &output },
112 { "Bootchart", "Init", config_parse_path, 0, &init },
113 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
114 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
115 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
116 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
117 { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup },
118 { NULL, NULL, NULL, 0, NULL }
119 };
120 _cleanup_fclose_ FILE *f;
121 int r;
122
123 f = fopen(BOOTCHART_CONF, "re");
124 if (!f)
125 return;
126
127 r = config_parse(NULL, BOOTCHART_CONF, f,
128 NULL, config_item_table_lookup, (void*) items, true, false, NULL);
129 if (r < 0)
130 log_warning("Failed to parse configuration file: %s", strerror(-r));
131
132 if (init != NULL)
133 strscpy(arg_init_path, sizeof(arg_init_path), init);
134 if (output != NULL)
135 strscpy(arg_output_path, sizeof(arg_output_path), output);
136 }
137
138 static int parse_args(int argc, char *argv[]) {
139 static struct option options[] = {
140 {"rel", no_argument, NULL, 'r'},
141 {"freq", required_argument, NULL, 'f'},
142 {"samples", required_argument, NULL, 'n'},
143 {"pss", no_argument, NULL, 'p'},
144 {"output", required_argument, NULL, 'o'},
145 {"init", required_argument, NULL, 'i'},
146 {"no-filter", no_argument, NULL, 'F'},
147 {"cmdline", no_argument, NULL, 'C'},
148 {"control-group", no_argument, NULL, 'c'},
149 {"help", no_argument, NULL, 'h'},
150 {"scale-x", required_argument, NULL, 'x'},
151 {"scale-y", required_argument, NULL, 'y'},
152 {"entropy", no_argument, NULL, 'e'},
153 {NULL, 0, NULL, 0}
154 };
155 int c;
156
157 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0) {
158 int r;
159
160 switch (c) {
161 case 'r':
162 arg_relative = true;
163 break;
164 case 'f':
165 r = safe_atod(optarg, &arg_hz);
166 if (r < 0)
167 log_warning("failed to parse --freq/-f argument '%s': %s",
168 optarg, strerror(-r));
169 break;
170 case 'F':
171 arg_filter = false;
172 break;
173 case 'C':
174 arg_show_cmdline = true;
175 break;
176 case 'c':
177 arg_show_cgroup = true;
178 break;
179 case 'n':
180 r = safe_atoi(optarg, &arg_samples_len);
181 if (r < 0)
182 log_warning("failed to parse --samples/-n argument '%s': %s",
183 optarg, strerror(-r));
184 break;
185 case 'o':
186 path_kill_slashes(optarg);
187 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
188 break;
189 case 'i':
190 path_kill_slashes(optarg);
191 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
192 break;
193 case 'p':
194 arg_pss = true;
195 break;
196 case 'x':
197 r = safe_atod(optarg, &arg_scale_x);
198 if (r < 0)
199 log_warning("failed to parse --scale-x/-x argument '%s': %s",
200 optarg, strerror(-r));
201 break;
202 case 'y':
203 r = safe_atod(optarg, &arg_scale_y);
204 if (r < 0)
205 log_warning("failed to parse --scale-y/-y argument '%s': %s",
206 optarg, strerror(-r));
207 break;
208 case 'e':
209 arg_entropy = true;
210 break;
211 case 'h':
212 fprintf(stderr, "Usage: %s [OPTIONS]\n\n", argv[0]);
213 fprintf(stderr, "Options:\n");
214 fprintf(stderr, " -r, --rel Record time relative to recording\n");
215 fprintf(stderr, " -f, --freq=FREQ Sample frequency [%f]\n", arg_hz);
216 fprintf(stderr, " -n, --samples=N Stop sampling at [%d] samples\n", arg_samples_len);
217 fprintf(stderr, " -x, --scale-x=N Scale the graph horizontally [%f] \n", arg_scale_x);
218 fprintf(stderr, " -y, --scale-y=N Scale the graph vertically [%f] \n", arg_scale_y);
219 fprintf(stderr, " -p, --pss Enable PSS graph (CPU intensive)\n");
220 fprintf(stderr, " -e, --entropy Enable the entropy_avail graph\n");
221 fprintf(stderr, " -o, --output=PATH Path to output files [%s]\n", arg_output_path);
222 fprintf(stderr, " -i, --init=PATH Path to init executable [%s]\n", arg_init_path);
223 fprintf(stderr, " -F, --no-filter Disable filtering of processes from the graph\n");
224 fprintf(stderr, " that are of less importance or short-lived\n");
225 fprintf(stderr, " -C, --cmdline Display the full command line with arguments\n");
226 fprintf(stderr, " of processes, instead of only the process name\n");
227 fprintf(stderr, " -c, --control-group Display process control group\n");
228 fprintf(stderr, " -h, --help Display this message\n\n");
229 fprintf(stderr, "See bootchart.conf for more information.\n");
230 exit (EXIT_SUCCESS);
231 default:
232 break;
233 }
234 }
235
236 if (arg_hz <= 0.0) {
237 fprintf(stderr, "Error: Frequency needs to be > 0\n");
238 return -EINVAL;
239 }
240
241 return 0;
242 }
243
244 static void do_journal_append(char *file) {
245 struct iovec iovec[5];
246 int r, f, j = 0;
247 ssize_t n;
248 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
249 *p = NULL;
250
251 bootchart_file = strappend("BOOTCHART_FILE=", file);
252 if (bootchart_file)
253 IOVEC_SET_STRING(iovec[j++], bootchart_file);
254
255 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
256 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
257 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
258 if (bootchart_message)
259 IOVEC_SET_STRING(iovec[j++], bootchart_message);
260
261 p = malloc(9 + BOOTCHART_MAX);
262 if (!p) {
263 log_oom();
264 return;
265 }
266
267 memcpy(p, "BOOTCHART=", 10);
268
269 f = open(file, O_RDONLY|O_CLOEXEC);
270 if (f < 0) {
271 log_error("Failed to read bootchart data: %m");
272 return;
273 }
274 n = loop_read(f, p + 10, BOOTCHART_MAX, false);
275 if (n < 0) {
276 log_error("Failed to read bootchart data: %s", strerror(-n));
277 close(f);
278 return;
279 }
280 close(f);
281
282 iovec[j].iov_base = p;
283 iovec[j].iov_len = 10 + n;
284 j++;
285
286 r = sd_journal_sendv(iovec, j);
287 if (r < 0)
288 log_error("Failed to send bootchart: %s", strerror(-r));
289 }
290
291 int main(int argc, char *argv[]) {
292 _cleanup_free_ char *build = NULL;
293 struct sigaction sig = {
294 .sa_handler = signal_handler,
295 };
296 struct ps_struct *ps;
297 char output_file[PATH_MAX];
298 char datestr[200];
299 time_t t = 0;
300 int r;
301 struct rlimit rlim;
302
303 parse_conf();
304
305 r = parse_args(argc, argv);
306 if (r < 0)
307 return EXIT_FAILURE;
308
309 /*
310 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
311 * fork:
312 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
313 * - child logs data
314 */
315 if (getpid() == 1) {
316 if (fork()) {
317 /* parent */
318 execl(arg_init_path, arg_init_path, NULL);
319 }
320 }
321 argv[0][0] = '@';
322
323 rlim.rlim_cur = 4096;
324 rlim.rlim_max = 4096;
325 (void) setrlimit(RLIMIT_NOFILE, &rlim);
326
327 /* start with empty ps LL */
328 ps_first = new0(struct ps_struct, 1);
329 if (!ps_first) {
330 log_oom();
331 return EXIT_FAILURE;
332 }
333
334 /* handle TERM/INT nicely */
335 sigaction(SIGHUP, &sig, NULL);
336
337 interval = (1.0 / arg_hz) * 1000000000.0;
338
339 log_uptime();
340
341 LIST_HEAD_INIT(head);
342
343 /* main program loop */
344 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
345 int res;
346 double sample_stop;
347 struct timespec req;
348 time_t newint_s;
349 long newint_ns;
350 double elapsed;
351 double timeleft;
352
353 sampledata = new0(struct list_sample_data, 1);
354 if (sampledata == NULL) {
355 log_error("Failed to allocate memory for a node: %m");
356 return -1;
357 }
358
359 sampledata->sampletime = gettime_ns();
360 sampledata->counter = samples;
361
362 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
363 t = time(NULL);
364 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
365 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
366 of = fopen(output_file, "we");
367 }
368
369 if (sysfd < 0)
370 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
371
372 if (!build)
373 parse_env_file("/etc/os-release", NEWLINE,
374 "PRETTY_NAME", &build,
375 NULL);
376
377 /* wait for /proc to become available, discarding samples */
378 if (graph_start <= 0.0)
379 log_uptime();
380 else
381 log_sample(samples, &sampledata);
382
383 sample_stop = gettime_ns();
384
385 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
386 timeleft = interval - elapsed;
387
388 newint_s = (time_t)(timeleft / 1000000000.0);
389 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
390
391 /*
392 * check if we have not consumed our entire timeslice. If we
393 * do, don't sleep and take a new sample right away.
394 * we'll lose all the missed samples and overrun our total
395 * time
396 */
397 if (newint_ns > 0 || newint_s > 0) {
398 req.tv_sec = newint_s;
399 req.tv_nsec = newint_ns;
400
401 res = nanosleep(&req, NULL);
402 if (res) {
403 if (errno == EINTR) {
404 /* caught signal, probably HUP! */
405 break;
406 }
407 log_error("nanosleep() failed: %m");
408 exit(EXIT_FAILURE);
409 }
410 } else {
411 overrun++;
412 /* calculate how many samples we lost and scrap them */
413 arg_samples_len -= (int)(newint_ns / interval);
414 }
415 LIST_PREPEND(link, head, sampledata);
416 }
417
418 /* do some cleanup, close fd's */
419 ps = ps_first;
420 while (ps->next_ps) {
421 ps = ps->next_ps;
422 if (ps->schedstat)
423 close(ps->schedstat);
424 if (ps->sched)
425 close(ps->sched);
426 if (ps->smaps)
427 fclose(ps->smaps);
428 }
429
430 if (!of) {
431 t = time(NULL);
432 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
433 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
434 of = fopen(output_file, "we");
435 }
436
437 if (!of) {
438 fprintf(stderr, "opening output file '%s': %m\n", output_file);
439 exit (EXIT_FAILURE);
440 }
441
442 svg_do(build);
443
444 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
445
446 do_journal_append(output_file);
447
448 if (of)
449 fclose(of);
450
451 closedir(proc);
452 if (sysfd >= 0)
453 close(sysfd);
454
455 /* nitpic cleanups */
456 ps = ps_first->next_ps;
457 while (ps->next_ps) {
458 struct ps_struct *old;
459
460 old = ps;
461 old->sample = ps->first;
462 ps = ps->next_ps;
463 while (old->sample->next) {
464 struct ps_sched_struct *oldsample = old->sample;
465
466 old->sample = old->sample->next;
467 free(oldsample);
468 }
469 free(old->cgroup);
470 free(old->sample);
471 free(old);
472 }
473 free(ps->cgroup);
474 free(ps->sample);
475 free(ps);
476
477 sampledata = head;
478 while (sampledata->link_prev) {
479 struct list_sample_data *old_sampledata = sampledata;
480 sampledata = sampledata->link_prev;
481 free(old_sampledata);
482 }
483 free(sampledata);
484 /* don't complain when overrun once, happens most commonly on 1st sample */
485 if (overrun > 1)
486 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
487
488 return 0;
489 }