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