]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
os-release: define /usr/lib/os-release as fallback for /etc/os-release
[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 _cleanup_fclose_ FILE *f;
128 int r;
129
130 f = fopen(BOOTCHART_CONF, "re");
131 if (!f)
132 return;
133
134 r = config_parse(NULL, BOOTCHART_CONF, f,
135 NULL, config_item_table_lookup, (void*) items, true, false, NULL);
136 if (r < 0)
137 log_warning("Failed to parse configuration file: %s", strerror(-r));
138
139 if (init != NULL)
140 strscpy(arg_init_path, sizeof(arg_init_path), init);
141 if (output != NULL)
142 strscpy(arg_output_path, sizeof(arg_output_path), output);
143 }
144
145 static void help(void) {
146 fprintf(stdout,
147 "Usage: %s [OPTIONS]\n\n"
148 "Options:\n"
149 " -r, --rel Record time relative to recording\n"
150 " -f, --freq=FREQ Sample frequency [%g]\n"
151 " -n, --samples=N Stop sampling at [%d] samples\n"
152 " -x, --scale-x=N Scale the graph horizontally [%g] \n"
153 " -y, --scale-y=N Scale the graph vertically [%g] \n"
154 " -p, --pss Enable PSS graph (CPU intensive)\n"
155 " -e, --entropy Enable the entropy_avail graph\n"
156 " -o, --output=PATH Path to output files [%s]\n"
157 " -i, --init=PATH Path to init executable [%s]\n"
158 " -F, --no-filter Disable filtering of unimportant or ephemeral processes\n"
159 " -C, --cmdline Display full command lines with arguments\n"
160 " -c, --control-group Display process control group\n"
161 " -h, --help Display this message\n\n"
162 "See bootchart.conf for more information.\n",
163 program_invocation_short_name,
164 DEFAULT_HZ,
165 DEFAULT_SAMPLES_LEN,
166 DEFAULT_SCALE_X,
167 DEFAULT_SCALE_Y,
168 DEFAULT_OUTPUT,
169 DEFAULT_INIT);
170 }
171
172 static int parse_args(int argc, char *argv[]) {
173 static struct option options[] = {
174 {"rel", no_argument, NULL, 'r'},
175 {"freq", required_argument, NULL, 'f'},
176 {"samples", required_argument, NULL, 'n'},
177 {"pss", no_argument, NULL, 'p'},
178 {"output", required_argument, NULL, 'o'},
179 {"init", required_argument, NULL, 'i'},
180 {"no-filter", no_argument, NULL, 'F'},
181 {"cmdline", no_argument, NULL, 'C'},
182 {"control-group", no_argument, NULL, 'c'},
183 {"help", no_argument, NULL, 'h'},
184 {"scale-x", required_argument, NULL, 'x'},
185 {"scale-y", required_argument, NULL, 'y'},
186 {"entropy", no_argument, NULL, 'e'},
187 {NULL, 0, NULL, 0}
188 };
189 int c;
190
191 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0) {
192 int r;
193
194 switch (c) {
195 case 'r':
196 arg_relative = true;
197 break;
198 case 'f':
199 r = safe_atod(optarg, &arg_hz);
200 if (r < 0)
201 log_warning("failed to parse --freq/-f argument '%s': %s",
202 optarg, strerror(-r));
203 break;
204 case 'F':
205 arg_filter = false;
206 break;
207 case 'C':
208 arg_show_cmdline = true;
209 break;
210 case 'c':
211 arg_show_cgroup = true;
212 break;
213 case 'n':
214 r = safe_atoi(optarg, &arg_samples_len);
215 if (r < 0)
216 log_warning("failed to parse --samples/-n argument '%s': %s",
217 optarg, strerror(-r));
218 break;
219 case 'o':
220 path_kill_slashes(optarg);
221 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
222 break;
223 case 'i':
224 path_kill_slashes(optarg);
225 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
226 break;
227 case 'p':
228 arg_pss = true;
229 break;
230 case 'x':
231 r = safe_atod(optarg, &arg_scale_x);
232 if (r < 0)
233 log_warning("failed to parse --scale-x/-x argument '%s': %s",
234 optarg, strerror(-r));
235 break;
236 case 'y':
237 r = safe_atod(optarg, &arg_scale_y);
238 if (r < 0)
239 log_warning("failed to parse --scale-y/-y argument '%s': %s",
240 optarg, strerror(-r));
241 break;
242 case 'e':
243 arg_entropy = true;
244 break;
245 case 'h':
246 help();
247 exit (EXIT_SUCCESS);
248 default:
249 break;
250 }
251 }
252
253 if (arg_hz <= 0.0) {
254 fprintf(stderr, "Error: Frequency needs to be > 0\n");
255 return -EINVAL;
256 }
257
258 return 0;
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
320 parse_conf();
321
322 r = parse_args(argc, argv);
323 if (r < 0)
324 return EXIT_FAILURE;
325
326 /*
327 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
328 * fork:
329 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
330 * - child logs data
331 */
332 if (getpid() == 1) {
333 if (fork()) {
334 /* parent */
335 execl(arg_init_path, arg_init_path, NULL);
336 }
337 }
338 argv[0][0] = '@';
339
340 rlim.rlim_cur = 4096;
341 rlim.rlim_max = 4096;
342 (void) setrlimit(RLIMIT_NOFILE, &rlim);
343
344 /* start with empty ps LL */
345 ps_first = new0(struct ps_struct, 1);
346 if (!ps_first) {
347 log_oom();
348 return EXIT_FAILURE;
349 }
350
351 /* handle TERM/INT nicely */
352 sigaction(SIGHUP, &sig, NULL);
353
354 interval = (1.0 / arg_hz) * 1000000000.0;
355
356 log_uptime();
357
358 LIST_HEAD_INIT(head);
359
360 /* main program loop */
361 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
362 int res;
363 double sample_stop;
364 struct timespec req;
365 time_t newint_s;
366 long newint_ns;
367 double elapsed;
368 double timeleft;
369
370 sampledata = new0(struct list_sample_data, 1);
371 if (sampledata == NULL) {
372 log_error("Failed to allocate memory for a node: %m");
373 return -1;
374 }
375
376 sampledata->sampletime = gettime_ns();
377 sampledata->counter = samples;
378
379 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
380 t = time(NULL);
381 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
382 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
383 of = fopen(output_file, "we");
384 }
385
386 if (sysfd < 0)
387 sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
388
389 if (!build) {
390 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
391 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
392 }
393
394 /* wait for /proc to become available, discarding samples */
395 if (graph_start <= 0.0)
396 log_uptime();
397 else
398 log_sample(samples, &sampledata);
399
400 sample_stop = gettime_ns();
401
402 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
403 timeleft = interval - elapsed;
404
405 newint_s = (time_t)(timeleft / 1000000000.0);
406 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
407
408 /*
409 * check if we have not consumed our entire timeslice. If we
410 * do, don't sleep and take a new sample right away.
411 * we'll lose all the missed samples and overrun our total
412 * time
413 */
414 if (newint_ns > 0 || newint_s > 0) {
415 req.tv_sec = newint_s;
416 req.tv_nsec = newint_ns;
417
418 res = nanosleep(&req, NULL);
419 if (res) {
420 if (errno == EINTR) {
421 /* caught signal, probably HUP! */
422 break;
423 }
424 log_error("nanosleep() failed: %m");
425 exit(EXIT_FAILURE);
426 }
427 } else {
428 overrun++;
429 /* calculate how many samples we lost and scrap them */
430 arg_samples_len -= (int)(newint_ns / interval);
431 }
432 LIST_PREPEND(link, head, sampledata);
433 }
434
435 /* do some cleanup, close fd's */
436 ps = ps_first;
437 while (ps->next_ps) {
438 ps = ps->next_ps;
439 if (ps->schedstat)
440 close(ps->schedstat);
441 if (ps->sched)
442 close(ps->sched);
443 if (ps->smaps)
444 fclose(ps->smaps);
445 }
446
447 if (!of) {
448 t = time(NULL);
449 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
450 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
451 of = fopen(output_file, "we");
452 }
453
454 if (!of) {
455 fprintf(stderr, "opening output file '%s': %m\n", output_file);
456 exit (EXIT_FAILURE);
457 }
458
459 svg_do(build);
460
461 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
462
463 do_journal_append(output_file);
464
465 if (of)
466 fclose(of);
467
468 closedir(proc);
469 if (sysfd >= 0)
470 close(sysfd);
471
472 /* nitpic cleanups */
473 ps = ps_first->next_ps;
474 while (ps->next_ps) {
475 struct ps_struct *old;
476
477 old = ps;
478 old->sample = ps->first;
479 ps = ps->next_ps;
480 while (old->sample->next) {
481 struct ps_sched_struct *oldsample = old->sample;
482
483 old->sample = old->sample->next;
484 free(oldsample);
485 }
486 free(old->cgroup);
487 free(old->sample);
488 free(old);
489 }
490 free(ps->cgroup);
491 free(ps->sample);
492 free(ps);
493
494 sampledata = head;
495 while (sampledata->link_prev) {
496 struct list_sample_data *old_sampledata = sampledata;
497 sampledata = sampledata->link_prev;
498 free(old_sampledata);
499 }
500 free(sampledata);
501 /* don't complain when overrun once, happens most commonly on 1st sample */
502 if (overrun > 1)
503 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
504
505 return 0;
506 }