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