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