]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
Report about syntax errors with metadata
[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 Coproration
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
63 double graph_start;
64 double log_start;
65 double sampletime[MAXSAMPLES];
66 struct ps_struct *ps_first;
67 struct block_stat_struct blockstat[MAXSAMPLES];
68 int entropy_avail[MAXSAMPLES];
69 struct cpu_stat_struct cpustat[MAXCPUS];
70 int pscount;
71 int cpus;
72 double interval;
73 FILE *of = NULL;
74 int overrun = 0;
75 static int exiting = 0;
76 int sysfd=-1;
77
78 /* graph defaults */
79 bool arg_entropy = false;
80 bool initcall = true;
81 bool arg_relative = false;
82 bool arg_filter = true;
83 bool arg_show_cmdline = false;
84 bool arg_pss = false;
85 int samples;
86 int arg_samples_len = 500; /* we record len+1 (1 start sample) */
87 double arg_hz = 25.0; /* 20 seconds log time */
88 double arg_scale_x = 100.0; /* 100px = 1sec */
89 double arg_scale_y = 20.0; /* 16px = 1 process bar */
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 { NULL, NULL, NULL, 0, NULL }
118 };
119 _cleanup_fclose_ FILE *f;
120 int r;
121
122 f = fopen(BOOTCHART_CONF, "re");
123 if (!f)
124 return;
125
126 r = config_parse(NULL, BOOTCHART_CONF, f,
127 NULL, config_item_table_lookup, (void*) items, true, NULL);
128 if (r < 0)
129 log_warning("Failed to parse configuration file: %s", strerror(-r));
130
131 if (init != NULL)
132 strscpy(arg_init_path, sizeof(arg_init_path), init);
133 if (output != NULL)
134 strscpy(arg_output_path, sizeof(arg_output_path), output);
135 }
136
137 static int parse_args(int argc, char *argv[]) {
138 static struct option options[] = {
139 {"rel", no_argument, NULL, 'r'},
140 {"freq", required_argument, NULL, 'f'},
141 {"samples", required_argument, NULL, 'n'},
142 {"pss", no_argument, NULL, 'p'},
143 {"output", required_argument, NULL, 'o'},
144 {"init", required_argument, NULL, 'i'},
145 {"no-filter", no_argument, NULL, 'F'},
146 {"cmdline", no_argument, NULL, 'C'},
147 {"help", no_argument, NULL, 'h'},
148 {"scale-x", required_argument, NULL, 'x'},
149 {"scale-y", required_argument, NULL, 'y'},
150 {"entropy", no_argument, NULL, 'e'},
151 {NULL, 0, NULL, 0}
152 };
153 int c;
154
155 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FChx:y:", options, NULL)) >= 0) {
156 int r;
157
158 switch (c) {
159 case 'r':
160 arg_relative = true;
161 break;
162 case 'f':
163 r = safe_atod(optarg, &arg_hz);
164 if (r < 0)
165 log_warning("failed to parse --freq/-f argument '%s': %s",
166 optarg, strerror(-r));
167 break;
168 case 'F':
169 arg_filter = false;
170 break;
171 case 'C':
172 arg_show_cmdline = true;
173 break;
174 case 'n':
175 r = safe_atoi(optarg, &arg_samples_len);
176 if (r < 0)
177 log_warning("failed to parse --samples/-n argument '%s': %s",
178 optarg, strerror(-r));
179 break;
180 case 'o':
181 path_kill_slashes(optarg);
182 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
183 break;
184 case 'i':
185 path_kill_slashes(optarg);
186 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
187 break;
188 case 'p':
189 arg_pss = true;
190 break;
191 case 'x':
192 r = safe_atod(optarg, &arg_scale_x);
193 if (r < 0)
194 log_warning("failed to parse --scale-x/-x argument '%s': %s",
195 optarg, strerror(-r));
196 break;
197 case 'y':
198 r = safe_atod(optarg, &arg_scale_y);
199 if (r < 0)
200 log_warning("failed to parse --scale-y/-y argument '%s': %s",
201 optarg, strerror(-r));
202 break;
203 case 'e':
204 arg_entropy = true;
205 break;
206 case 'h':
207 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
208 fprintf(stderr, " --rel, -r Record time relative to recording\n");
209 fprintf(stderr, " --freq, -f f Sample frequency [%f]\n", arg_hz);
210 fprintf(stderr, " --samples, -n N Stop sampling at [%d] samples\n", arg_samples_len);
211 fprintf(stderr, " --scale-x, -x N Scale the graph horizontally [%f] \n", arg_scale_x);
212 fprintf(stderr, " --scale-y, -y N Scale the graph vertically [%f] \n", arg_scale_y);
213 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
214 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
215 fprintf(stderr, " --output, -o [PATH] Path to output files [%s]\n", arg_output_path);
216 fprintf(stderr, " --init, -i [PATH] Path to init executable [%s]\n", arg_init_path);
217 fprintf(stderr, " --no-filter, -F Disable filtering of processes from the graph\n");
218 fprintf(stderr, " that are of less importance or short-lived\n");
219 fprintf(stderr, " --cmdline, -C Display the full command line with arguments\n");
220 fprintf(stderr, " of processes, instead of only the process name\n");
221 fprintf(stderr, " --help, -h Display this message\n");
222 fprintf(stderr, "See bootchart.conf for more information.\n");
223 exit (EXIT_SUCCESS);
224 break;
225 default:
226 break;
227 }
228 }
229
230 if (arg_samples_len > MAXSAMPLES) {
231 fprintf(stderr, "Error: samples exceeds maximum\n");
232 return -EINVAL;
233 }
234
235 if (arg_hz <= 0.0) {
236 fprintf(stderr, "Error: Frequency needs to be > 0\n");
237 return -EINVAL;
238 }
239
240 return 0;
241 }
242
243 static void do_journal_append(char *file)
244 {
245 struct iovec iovec[5];
246 int r, f, j = 0;
247 ssize_t n;
248 char _cleanup_free_ *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 r = log_oom();
264 return;
265 }
266
267 memcpy(p, "BOOTCHART=", 10);
268
269 f = open(file, O_RDONLY);
270 if (f < 0) {
271 log_error("Failed to read bootchart data: %s\n", strerror(-errno));
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\n", 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 = calloc(1, sizeof(struct ps_struct));
329 if (!ps_first) {
330 perror("calloc(ps_struct)");
331 exit(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 /* main program loop */
342 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
343 int res;
344 double sample_stop;
345 struct timespec req;
346 time_t newint_s;
347 long newint_ns;
348 double elapsed;
349 double timeleft;
350
351 sampletime[samples] = gettime_ns();
352
353 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
354 t = time(NULL);
355 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
356 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
357 of = fopen(output_file, "w");
358 }
359
360 if (sysfd < 0)
361 sysfd = open("/sys", O_RDONLY);
362
363 if (!build)
364 parse_env_file("/etc/os-release", NEWLINE,
365 "PRETTY_NAME", &build,
366 NULL);
367
368 /* wait for /proc to become available, discarding samples */
369 if (graph_start <= 0.0)
370 log_uptime();
371 else
372 log_sample(samples);
373
374 sample_stop = gettime_ns();
375
376 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
377 timeleft = interval - elapsed;
378
379 newint_s = (time_t)(timeleft / 1000000000.0);
380 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
381
382 /*
383 * check if we have not consumed our entire timeslice. If we
384 * do, don't sleep and take a new sample right away.
385 * we'll lose all the missed samples and overrun our total
386 * time
387 */
388 if (newint_ns > 0 || newint_s > 0) {
389 req.tv_sec = newint_s;
390 req.tv_nsec = newint_ns;
391
392 res = nanosleep(&req, NULL);
393 if (res) {
394 if (errno == EINTR) {
395 /* caught signal, probably HUP! */
396 break;
397 }
398 perror("nanosleep()");
399 exit (EXIT_FAILURE);
400 }
401 } else {
402 overrun++;
403 /* calculate how many samples we lost and scrap them */
404 arg_samples_len -= (int)(newint_ns / interval);
405 }
406 }
407
408 /* do some cleanup, close fd's */
409 ps = ps_first;
410 while (ps->next_ps) {
411 ps = ps->next_ps;
412 if (ps->schedstat)
413 close(ps->schedstat);
414 if (ps->sched)
415 close(ps->sched);
416 if (ps->smaps)
417 fclose(ps->smaps);
418 }
419
420 if (!of) {
421 t = time(NULL);
422 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
423 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
424 of = fopen(output_file, "w");
425 }
426
427 if (!of) {
428 fprintf(stderr, "opening output file '%s': %m\n", output_file);
429 exit (EXIT_FAILURE);
430 }
431
432 svg_do(build);
433
434 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
435
436 do_journal_append(output_file);
437
438 if (of)
439 fclose(of);
440
441 closedir(proc);
442 if (sysfd >= 0)
443 close(sysfd);
444
445 /* nitpic cleanups */
446 ps = ps_first;
447 while (ps->next_ps) {
448 struct ps_struct *old = ps;
449 ps = ps->next_ps;
450 free(old->sample);
451 free(old);
452 }
453 free(ps->sample);
454 free(ps);
455
456 /* don't complain when overrun once, happens most commonly on 1st sample */
457 if (overrun > 1)
458 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
459
460 return 0;
461 }