]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/bootchart/bootchart.c
bootchart: split out option parsing from main()
[thirdparty/systemd.git] / src / bootchart / bootchart.c
CommitLineData
6d031c0b
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
cd3bccaa 3/***
6d031c0b 4 This file is part of systemd.
83fdc450 5
cd3bccaa
AK
6 Copyright (C) 2009-2013 Intel Coproration
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 ***/
83fdc450 24
f1c24fea
ZJS
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
83fdc450
AK
36#include <sys/time.h>
37#include <sys/types.h>
38#include <sys/resource.h>
b823b5e2 39#include <sys/stat.h>
83fdc450
AK
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>
b823b5e2 49#include <fcntl.h>
f7900e25 50#include <stdbool.h>
83fdc450 51
53f5329f 52#include "util.h"
a5c32cff 53#include "fileio.h"
f7900e25
TA
54#include "macro.h"
55#include "conf-parser.h"
56#include "strxcpyx.h"
0e4ffbff 57#include "path-util.h"
6d031c0b
LP
58#include "store.h"
59#include "svg.h"
60#include "bootchart.h"
83fdc450
AK
61
62double graph_start;
63double log_start;
64double sampletime[MAXSAMPLES];
65struct ps_struct *ps_first;
66struct block_stat_struct blockstat[MAXSAMPLES];
67int entropy_avail[MAXSAMPLES];
68struct cpu_stat_struct cpustat[MAXCPUS];
69int pscount;
70int cpus;
71double interval;
1f3523ba 72FILE *of = NULL;
83fdc450
AK
73int overrun = 0;
74static int exiting = 0;
1f3523ba 75int sysfd=-1;
83fdc450
AK
76
77/* graph defaults */
6d031c0b 78bool arg_entropy = false;
f7900e25 79bool initcall = true;
6d031c0b
LP
80bool arg_relative = false;
81bool arg_filter = true;
82bool arg_show_cmdline = false;
83bool arg_pss = false;
83fdc450 84int samples;
6d031c0b
LP
85int arg_samples_len = 500; /* we record len+1 (1 start sample) */
86double arg_hz = 25.0; /* 20 seconds log time */
87double arg_scale_x = 100.0; /* 100px = 1sec */
88double arg_scale_y = 20.0; /* 16px = 1 process bar */
83fdc450 89
6d031c0b
LP
90char arg_init_path[PATH_MAX] = "/sbin/init";
91char arg_output_path[PATH_MAX] = "/run/log";
83fdc450 92
6d031c0b 93static void signal_handler(int sig) {
28989b63
TA
94 if (sig++)
95 sig--;
96 exiting = 1;
83fdc450
AK
97}
98
4cd5f79d 99#define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
f7900e25 100
4cd5f79d
ZJS
101static void parse_conf(void) {
102 char *init = NULL, *output = NULL;
f7900e25 103 const ConfigTableItem items[] = {
6d031c0b
LP
104 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
105 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
106 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
107 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
108 { "Bootchart", "Output", config_parse_path, 0, &output },
109 { "Bootchart", "Init", config_parse_path, 0, &init },
110 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
111 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
112 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
113 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
f7900e25
TA
114 { NULL, NULL, NULL, 0, NULL }
115 };
4cd5f79d
ZJS
116 _cleanup_fclose_ FILE *f;
117 int r;
28989b63 118
4cd5f79d
ZJS
119 f = fopen(BOOTCHART_CONF, "re");
120 if (!f)
121 return;
28989b63 122
4cd5f79d
ZJS
123 r = config_parse(BOOTCHART_CONF, f,
124 NULL, config_item_table_lookup, (void*) items, true, NULL);
125 if (r < 0)
126 log_warning("Failed to parse configuration file: %s", strerror(-r));
127
128 if (init != NULL)
129 strscpy(arg_init_path, sizeof(arg_init_path), init);
130 if (output != NULL)
131 strscpy(arg_output_path, sizeof(arg_output_path), output);
132}
133
134static int parse_args(int argc, char *argv[]) {
135 static struct option options[] = {
136 {"rel", no_argument, NULL, 'r'},
137 {"freq", required_argument, NULL, 'f'},
138 {"samples", required_argument, NULL, 'n'},
139 {"pss", no_argument, NULL, 'p'},
140 {"output", required_argument, NULL, 'o'},
141 {"init", required_argument, NULL, 'i'},
142 {"no-filter", no_argument, NULL, 'F'},
143 {"cmdline", no_argument, NULL, 'C'},
144 {"help", no_argument, NULL, 'h'},
145 {"scale-x", required_argument, NULL, 'x'},
146 {"scale-y", required_argument, NULL, 'y'},
147 {"entropy", no_argument, NULL, 'e'},
148 {NULL, 0, NULL, 0}
149 };
150 int c;
151
152 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FChx:y:", options, NULL)) >= 0) {
153 int r;
154
155 switch (c) {
28989b63 156 case 'r':
6d031c0b 157 arg_relative = true;
28989b63
TA
158 break;
159 case 'f':
6d031c0b 160 r = safe_atod(optarg, &arg_hz);
547ba5a9
ZJS
161 if (r < 0)
162 log_warning("failed to parse --freq/-f argument '%s': %s",
163 optarg, strerror(-r));
28989b63
TA
164 break;
165 case 'F':
6d031c0b 166 arg_filter = false;
28989b63 167 break;
e90f9fa4 168 case 'C':
6d031c0b 169 arg_show_cmdline = true;
e90f9fa4 170 break;
28989b63 171 case 'n':
6d031c0b 172 r = safe_atoi(optarg, &arg_samples_len);
547ba5a9
ZJS
173 if (r < 0)
174 log_warning("failed to parse --samples/-n argument '%s': %s",
175 optarg, strerror(-r));
28989b63
TA
176 break;
177 case 'o':
0e4ffbff 178 path_kill_slashes(optarg);
6d031c0b 179 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
28989b63
TA
180 break;
181 case 'i':
0e4ffbff 182 path_kill_slashes(optarg);
6d031c0b 183 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
28989b63
TA
184 break;
185 case 'p':
6d031c0b 186 arg_pss = true;
28989b63
TA
187 break;
188 case 'x':
6d031c0b 189 r = safe_atod(optarg, &arg_scale_x);
547ba5a9
ZJS
190 if (r < 0)
191 log_warning("failed to parse --scale-x/-x argument '%s': %s",
192 optarg, strerror(-r));
28989b63
TA
193 break;
194 case 'y':
6d031c0b 195 r = safe_atod(optarg, &arg_scale_y);
547ba5a9
ZJS
196 if (r < 0)
197 log_warning("failed to parse --scale-y/-y argument '%s': %s",
198 optarg, strerror(-r));
28989b63
TA
199 break;
200 case 'e':
6d031c0b 201 arg_entropy = true;
28989b63
TA
202 break;
203 case 'h':
204 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
6d267991 205 fprintf(stderr, " --rel, -r Record time relative to recording\n");
6d031c0b
LP
206 fprintf(stderr, " --freq, -f f Sample frequency [%f]\n", arg_hz);
207 fprintf(stderr, " --samples, -n N Stop sampling at [%d] samples\n", arg_samples_len);
208 fprintf(stderr, " --scale-x, -x N Scale the graph horizontally [%f] \n", arg_scale_x);
209 fprintf(stderr, " --scale-y, -y N Scale the graph vertically [%f] \n", arg_scale_y);
6d267991
ZJS
210 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
211 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
6d031c0b
LP
212 fprintf(stderr, " --output, -o [PATH] Path to output files [%s]\n", arg_output_path);
213 fprintf(stderr, " --init, -i [PATH] Path to init executable [%s]\n", arg_init_path);
6d267991 214 fprintf(stderr, " --no-filter, -F Disable filtering of processes from the graph\n");
28989b63 215 fprintf(stderr, " that are of less importance or short-lived\n");
78da2cd9
HH
216 fprintf(stderr, " --cmdline, -C Display the full command line with arguments\n");
217 fprintf(stderr, " of processes, instead of only the process name\n");
6d267991 218 fprintf(stderr, " --help, -h Display this message\n");
547ba5a9 219 fprintf(stderr, "See bootchart.conf for more information.\n");
28989b63
TA
220 exit (EXIT_SUCCESS);
221 break;
222 default:
223 break;
224 }
225 }
226
6d031c0b 227 if (arg_samples_len > MAXSAMPLES) {
28989b63 228 fprintf(stderr, "Error: samples exceeds maximum\n");
4cd5f79d 229 return -EINVAL;
28989b63
TA
230 }
231
6d031c0b 232 if (arg_hz <= 0.0) {
28989b63 233 fprintf(stderr, "Error: Frequency needs to be > 0\n");
4cd5f79d 234 return -EINVAL;
28989b63
TA
235 }
236
4cd5f79d
ZJS
237 return 0;
238}
239
240int main(int argc, char *argv[]) {
241 _cleanup_free_ char *build = NULL;
242 struct sigaction sig;
243 struct ps_struct *ps;
244 char output_file[PATH_MAX];
245 char datestr[200];
246 time_t t = 0;
247 int r;
248 struct rlimit rlim;
249
250 parse_conf();
251
252 r = parse_args(argc, argv);
253 if (r < 0)
254 return EXIT_FAILURE;
255
28989b63 256 /*
547ba5a9 257 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
28989b63
TA
258 * fork:
259 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
260 * - child logs data
261 */
262 if (getpid() == 1) {
263 if (fork()) {
264 /* parent */
6d031c0b 265 execl(arg_init_path, arg_init_path, NULL);
28989b63
TA
266 }
267 }
0e4ffbff 268 argv[0][0] = '@';
28989b63 269
361514ac
LP
270 rlim.rlim_cur = 4096;
271 rlim.rlim_max = 4096;
272 (void) setrlimit(RLIMIT_NOFILE, &rlim);
273
28989b63 274 /* start with empty ps LL */
a2e9b338 275 ps_first = calloc(1, sizeof(struct ps_struct));
28989b63 276 if (!ps_first) {
a2e9b338 277 perror("calloc(ps_struct)");
28989b63
TA
278 exit(EXIT_FAILURE);
279 }
28989b63
TA
280
281 /* handle TERM/INT nicely */
282 memset(&sig, 0, sizeof(struct sigaction));
283 sig.sa_handler = signal_handler;
284 sigaction(SIGHUP, &sig, NULL);
285
6d031c0b 286 interval = (1.0 / arg_hz) * 1000000000.0;
28989b63
TA
287
288 log_uptime();
289
290 /* main program loop */
291 while (!exiting) {
292 int res;
293 double sample_stop;
294 struct timespec req;
295 time_t newint_s;
296 long newint_ns;
297 double elapsed;
298 double timeleft;
299
300 sampletime[samples] = gettime_ns();
301
6d031c0b 302 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
f2f85884
HH
303 t = time(NULL);
304 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
6d031c0b 305 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
f2f85884
HH
306 of = fopen(output_file, "w");
307 }
308
6d031c0b 309 if (sysfd < 0)
b823b5e2 310 sysfd = open("/sys", O_RDONLY);
f2f85884 311
6d031c0b 312 if (!build)
e93450c6
HH
313 parse_env_file("/etc/os-release", NEWLINE,
314 "PRETTY_NAME", &build,
315 NULL);
e93450c6 316
28989b63
TA
317 /* wait for /proc to become available, discarding samples */
318 if (!(graph_start > 0.0))
319 log_uptime();
320 else
321 log_sample(samples);
322
323 sample_stop = gettime_ns();
324
325 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
326 timeleft = interval - elapsed;
327
328 newint_s = (time_t)(timeleft / 1000000000.0);
329 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
330
331 /*
332 * check if we have not consumed our entire timeslice. If we
333 * do, don't sleep and take a new sample right away.
334 * we'll lose all the missed samples and overrun our total
335 * time
336 */
337 if ((newint_ns > 0) || (newint_s > 0)) {
338 req.tv_sec = newint_s;
339 req.tv_nsec = newint_ns;
340
341 res = nanosleep(&req, NULL);
342 if (res) {
343 if (errno == EINTR) {
344 /* caught signal, probably HUP! */
345 break;
346 }
347 perror("nanosleep()");
348 exit (EXIT_FAILURE);
349 }
350 } else {
351 overrun++;
352 /* calculate how many samples we lost and scrap them */
6d031c0b 353 arg_samples_len = arg_samples_len + ((int)(newint_ns / interval));
28989b63
TA
354 }
355
356 samples++;
357
6d031c0b 358 if (samples > arg_samples_len)
28989b63
TA
359 break;
360
361 }
362
363 /* do some cleanup, close fd's */
364 ps = ps_first;
365 while (ps->next_ps) {
366 ps = ps->next_ps;
367 if (ps->schedstat)
368 close(ps->schedstat);
369 if (ps->sched)
370 close(ps->sched);
371 if (ps->smaps)
372 fclose(ps->smaps);
373 }
28989b63 374
f2f85884
HH
375 if (!of) {
376 t = time(NULL);
377 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
6d031c0b 378 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
f2f85884
HH
379 of = fopen(output_file, "w");
380 }
28989b63 381
28989b63 382 if (!of) {
547ba5a9 383 fprintf(stderr, "opening output file '%s': %m\n", output_file);
28989b63
TA
384 exit (EXIT_FAILURE);
385 }
386
e93450c6 387 svg_do(build);
28989b63 388
547ba5a9 389 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
6d031c0b
LP
390
391 if (of)
392 fclose(of);
28989b63 393
b823b5e2 394 closedir(proc);
6d031c0b
LP
395 if (sysfd >= 0)
396 close(sysfd);
b823b5e2 397
28989b63
TA
398 /* nitpic cleanups */
399 ps = ps_first;
400 while (ps->next_ps) {
401 struct ps_struct *old = ps;
402 ps = ps->next_ps;
403 free(old->sample);
404 free(old);
405 }
406 free(ps->sample);
407 free(ps);
408
409 /* don't complain when overrun once, happens most commonly on 1st sample */
410 if (overrun > 1)
547ba5a9 411 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
28989b63
TA
412
413 return 0;
83fdc450 414}