]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
Constify option table and add missing option
[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_args(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;
184
185 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0) {
186 int r;
187
188 switch (c) {
189 case 'r':
190 arg_relative = true;
191 break;
192 case 'f':
193 r = safe_atod(optarg, &arg_hz);
194 if (r < 0)
195 log_warning("failed to parse --freq/-f argument '%s': %s",
196 optarg, strerror(-r));
197 break;
198 case 'F':
199 arg_filter = false;
200 break;
201 case 'C':
202 arg_show_cmdline = true;
203 break;
204 case 'c':
205 arg_show_cgroup = true;
206 break;
207 case 'n':
208 r = safe_atoi(optarg, &arg_samples_len);
209 if (r < 0)
210 log_warning("failed to parse --samples/-n argument '%s': %s",
211 optarg, strerror(-r));
212 break;
213 case 'o':
214 path_kill_slashes(optarg);
215 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
216 break;
217 case 'i':
218 path_kill_slashes(optarg);
219 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
220 break;
221 case 'p':
222 arg_pss = true;
223 break;
224 case 'x':
225 r = safe_atod(optarg, &arg_scale_x);
226 if (r < 0)
227 log_warning("failed to parse --scale-x/-x argument '%s': %s",
228 optarg, strerror(-r));
229 break;
230 case 'y':
231 r = safe_atod(optarg, &arg_scale_y);
232 if (r < 0)
233 log_warning("failed to parse --scale-y/-y argument '%s': %s",
234 optarg, strerror(-r));
235 break;
236 case 'e':
237 arg_entropy = true;
238 break;
239 case 'h':
240 help();
241 exit (EXIT_SUCCESS);
242 default:
243 break;
244 }
245 }
246
247 if (arg_hz <= 0.0) {
248 fprintf(stderr, "Error: Frequency needs to be > 0\n");
249 return -EINVAL;
250 }
251
252 return 0;
253 }
254
255 static void do_journal_append(char *file) {
256 struct iovec iovec[5];
257 int r, f, j = 0;
258 ssize_t n;
259 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
260 *p = NULL;
261
262 bootchart_file = strappend("BOOTCHART_FILE=", file);
263 if (bootchart_file)
264 IOVEC_SET_STRING(iovec[j++], bootchart_file);
265
266 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
267 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
268 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
269 if (bootchart_message)
270 IOVEC_SET_STRING(iovec[j++], bootchart_message);
271
272 p = malloc(9 + BOOTCHART_MAX);
273 if (!p) {
274 log_oom();
275 return;
276 }
277
278 memcpy(p, "BOOTCHART=", 10);
279
280 f = open(file, O_RDONLY|O_CLOEXEC);
281 if (f < 0) {
282 log_error("Failed to read bootchart data: %m");
283 return;
284 }
285 n = loop_read(f, p + 10, BOOTCHART_MAX, false);
286 if (n < 0) {
287 log_error("Failed to read bootchart data: %s", strerror(-n));
288 close(f);
289 return;
290 }
291 close(f);
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("Failed to send bootchart: %s", strerror(-r));
300 }
301
302 int main(int argc, char *argv[]) {
303 _cleanup_free_ char *build = NULL;
304 struct sigaction sig = {
305 .sa_handler = signal_handler,
306 };
307 struct ps_struct *ps;
308 char output_file[PATH_MAX];
309 char datestr[200];
310 time_t t = 0;
311 int r;
312 struct rlimit rlim;
313
314 parse_conf();
315
316 r = parse_args(argc, argv);
317 if (r < 0)
318 return EXIT_FAILURE;
319
320 /*
321 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
322 * fork:
323 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
324 * - child logs data
325 */
326 if (getpid() == 1) {
327 if (fork()) {
328 /* parent */
329 execl(arg_init_path, arg_init_path, NULL);
330 }
331 }
332 argv[0][0] = '@';
333
334 rlim.rlim_cur = 4096;
335 rlim.rlim_max = 4096;
336 (void) setrlimit(RLIMIT_NOFILE, &rlim);
337
338 /* start with empty ps LL */
339 ps_first = new0(struct ps_struct, 1);
340 if (!ps_first) {
341 log_oom();
342 return EXIT_FAILURE;
343 }
344
345 /* handle TERM/INT nicely */
346 sigaction(SIGHUP, &sig, NULL);
347
348 interval = (1.0 / arg_hz) * 1000000000.0;
349
350 log_uptime();
351
352 LIST_HEAD_INIT(head);
353
354 /* main program loop */
355 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
356 int res;
357 double sample_stop;
358 struct timespec req;
359 time_t newint_s;
360 long newint_ns;
361 double elapsed;
362 double timeleft;
363
364 sampledata = new0(struct list_sample_data, 1);
365 if (sampledata == NULL) {
366 log_error("Failed to allocate memory for a node: %m");
367 return -1;
368 }
369
370 sampledata->sampletime = gettime_ns();
371 sampledata->counter = samples;
372
373 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
374 t = time(NULL);
375 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
376 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
377 of = fopen(output_file, "we");
378 }
379
380 if (sysfd < 0)
381 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
382
383 if (!build) {
384 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
385 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
386 }
387
388 /* wait for /proc to become available, discarding samples */
389 if (graph_start <= 0.0)
390 log_uptime();
391 else
392 log_sample(samples, &sampledata);
393
394 sample_stop = gettime_ns();
395
396 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
397 timeleft = interval - elapsed;
398
399 newint_s = (time_t)(timeleft / 1000000000.0);
400 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
401
402 /*
403 * check if we have not consumed our entire timeslice. If we
404 * do, don't sleep and take a new sample right away.
405 * we'll lose all the missed samples and overrun our total
406 * time
407 */
408 if (newint_ns > 0 || newint_s > 0) {
409 req.tv_sec = newint_s;
410 req.tv_nsec = newint_ns;
411
412 res = nanosleep(&req, NULL);
413 if (res) {
414 if (errno == EINTR) {
415 /* caught signal, probably HUP! */
416 break;
417 }
418 log_error("nanosleep() failed: %m");
419 exit(EXIT_FAILURE);
420 }
421 } else {
422 overrun++;
423 /* calculate how many samples we lost and scrap them */
424 arg_samples_len -= (int)(newint_ns / interval);
425 }
426 LIST_PREPEND(link, head, sampledata);
427 }
428
429 /* do some cleanup, close fd's */
430 ps = ps_first;
431 while (ps->next_ps) {
432 ps = ps->next_ps;
433 if (ps->schedstat)
434 close(ps->schedstat);
435 if (ps->sched)
436 close(ps->sched);
437 if (ps->smaps)
438 fclose(ps->smaps);
439 }
440
441 if (!of) {
442 t = time(NULL);
443 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
444 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
445 of = fopen(output_file, "we");
446 }
447
448 if (!of) {
449 fprintf(stderr, "opening output file '%s': %m\n", output_file);
450 exit (EXIT_FAILURE);
451 }
452
453 svg_do(build);
454
455 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
456
457 do_journal_append(output_file);
458
459 if (of)
460 fclose(of);
461
462 closedir(proc);
463 if (sysfd >= 0)
464 close(sysfd);
465
466 /* nitpic cleanups */
467 ps = ps_first->next_ps;
468 while (ps->next_ps) {
469 struct ps_struct *old;
470
471 old = ps;
472 old->sample = ps->first;
473 ps = ps->next_ps;
474 while (old->sample->next) {
475 struct ps_sched_struct *oldsample = old->sample;
476
477 old->sample = old->sample->next;
478 free(oldsample);
479 }
480 free(old->cgroup);
481 free(old->sample);
482 free(old);
483 }
484 free(ps->cgroup);
485 free(ps->sample);
486 free(ps);
487
488 sampledata = head;
489 while (sampledata->link_prev) {
490 struct list_sample_data *old_sampledata = sampledata;
491 sampledata = sampledata->link_prev;
492 free(old_sampledata);
493 }
494 free(sampledata);
495 /* don't complain when overrun once, happens most commonly on 1st sample */
496 if (overrun > 1)
497 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
498
499 return 0;
500 }