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