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