]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
Use initalization instead of explicit zeroing in more 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 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 ***/
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
52 #include "util.h"
53 #include "fileio.h"
54 #include "macro.h"
55 #include "conf-parser.h"
56 #include "strxcpyx.h"
57 #include "path-util.h"
58 #include "store.h"
59 #include "svg.h"
60 #include "bootchart.h"
61
62 double graph_start;
63 double log_start;
64 double sampletime[MAXSAMPLES];
65 struct ps_struct *ps_first;
66 struct block_stat_struct blockstat[MAXSAMPLES];
67 int entropy_avail[MAXSAMPLES];
68 struct cpu_stat_struct cpustat[MAXCPUS];
69 int pscount;
70 int cpus;
71 double interval;
72 FILE *of = NULL;
73 int overrun = 0;
74 static int exiting = 0;
75 int sysfd=-1;
76
77 /* graph defaults */
78 bool arg_entropy = false;
79 bool initcall = true;
80 bool arg_relative = false;
81 bool arg_filter = true;
82 bool arg_show_cmdline = false;
83 bool arg_pss = false;
84 int samples;
85 int arg_samples_len = 500; /* we record len+1 (1 start sample) */
86 double arg_hz = 25.0; /* 20 seconds log time */
87 double arg_scale_x = 100.0; /* 100px = 1sec */
88 double arg_scale_y = 20.0; /* 16px = 1 process bar */
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 static void parse_conf(void) {
102 char *init = NULL, *output = NULL;
103 const ConfigTableItem items[] = {
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 },
114 { NULL, NULL, NULL, 0, NULL }
115 };
116 _cleanup_fclose_ FILE *f;
117 int r;
118
119 f = fopen(BOOTCHART_CONF, "re");
120 if (!f)
121 return;
122
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
134 static 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) {
156 case 'r':
157 arg_relative = true;
158 break;
159 case 'f':
160 r = safe_atod(optarg, &arg_hz);
161 if (r < 0)
162 log_warning("failed to parse --freq/-f argument '%s': %s",
163 optarg, strerror(-r));
164 break;
165 case 'F':
166 arg_filter = false;
167 break;
168 case 'C':
169 arg_show_cmdline = true;
170 break;
171 case 'n':
172 r = safe_atoi(optarg, &arg_samples_len);
173 if (r < 0)
174 log_warning("failed to parse --samples/-n argument '%s': %s",
175 optarg, strerror(-r));
176 break;
177 case 'o':
178 path_kill_slashes(optarg);
179 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
180 break;
181 case 'i':
182 path_kill_slashes(optarg);
183 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
184 break;
185 case 'p':
186 arg_pss = true;
187 break;
188 case 'x':
189 r = safe_atod(optarg, &arg_scale_x);
190 if (r < 0)
191 log_warning("failed to parse --scale-x/-x argument '%s': %s",
192 optarg, strerror(-r));
193 break;
194 case 'y':
195 r = safe_atod(optarg, &arg_scale_y);
196 if (r < 0)
197 log_warning("failed to parse --scale-y/-y argument '%s': %s",
198 optarg, strerror(-r));
199 break;
200 case 'e':
201 arg_entropy = true;
202 break;
203 case 'h':
204 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
205 fprintf(stderr, " --rel, -r Record time relative to recording\n");
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);
210 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
211 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
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);
214 fprintf(stderr, " --no-filter, -F Disable filtering of processes from the graph\n");
215 fprintf(stderr, " that are of less importance or short-lived\n");
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");
218 fprintf(stderr, " --help, -h Display this message\n");
219 fprintf(stderr, "See bootchart.conf for more information.\n");
220 exit (EXIT_SUCCESS);
221 break;
222 default:
223 break;
224 }
225 }
226
227 if (arg_samples_len > MAXSAMPLES) {
228 fprintf(stderr, "Error: samples exceeds maximum\n");
229 return -EINVAL;
230 }
231
232 if (arg_hz <= 0.0) {
233 fprintf(stderr, "Error: Frequency needs to be > 0\n");
234 return -EINVAL;
235 }
236
237 return 0;
238 }
239
240 int main(int argc, char *argv[]) {
241 _cleanup_free_ char *build = NULL;
242 struct sigaction sig = {
243 .sa_handler = signal_handler,
244 };
245 struct ps_struct *ps;
246 char output_file[PATH_MAX];
247 char datestr[200];
248 time_t t = 0;
249 int r;
250 struct rlimit rlim;
251
252 parse_conf();
253
254 r = parse_args(argc, argv);
255 if (r < 0)
256 return EXIT_FAILURE;
257
258 /*
259 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
260 * fork:
261 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
262 * - child logs data
263 */
264 if (getpid() == 1) {
265 if (fork()) {
266 /* parent */
267 execl(arg_init_path, arg_init_path, NULL);
268 }
269 }
270 argv[0][0] = '@';
271
272 rlim.rlim_cur = 4096;
273 rlim.rlim_max = 4096;
274 (void) setrlimit(RLIMIT_NOFILE, &rlim);
275
276 /* start with empty ps LL */
277 ps_first = calloc(1, sizeof(struct ps_struct));
278 if (!ps_first) {
279 perror("calloc(ps_struct)");
280 exit(EXIT_FAILURE);
281 }
282
283 /* handle TERM/INT nicely */
284 sigaction(SIGHUP, &sig, NULL);
285
286 interval = (1.0 / arg_hz) * 1000000000.0;
287
288 log_uptime();
289
290 /* main program loop */
291 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
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
302 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
303 t = time(NULL);
304 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
305 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
306 of = fopen(output_file, "w");
307 }
308
309 if (sysfd < 0)
310 sysfd = open("/sys", O_RDONLY);
311
312 if (!build)
313 parse_env_file("/etc/os-release", NEWLINE,
314 "PRETTY_NAME", &build,
315 NULL);
316
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 */
353 arg_samples_len -= (int)(newint_ns / interval);
354 }
355 }
356
357 /* do some cleanup, close fd's */
358 ps = ps_first;
359 while (ps->next_ps) {
360 ps = ps->next_ps;
361 if (ps->schedstat)
362 close(ps->schedstat);
363 if (ps->sched)
364 close(ps->sched);
365 if (ps->smaps)
366 fclose(ps->smaps);
367 }
368
369 if (!of) {
370 t = time(NULL);
371 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
372 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
373 of = fopen(output_file, "w");
374 }
375
376 if (!of) {
377 fprintf(stderr, "opening output file '%s': %m\n", output_file);
378 exit (EXIT_FAILURE);
379 }
380
381 svg_do(build);
382
383 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
384
385 if (of)
386 fclose(of);
387
388 closedir(proc);
389 if (sysfd >= 0)
390 close(sysfd);
391
392 /* nitpic cleanups */
393 ps = ps_first;
394 while (ps->next_ps) {
395 struct ps_struct *old = ps;
396 ps = ps->next_ps;
397 free(old->sample);
398 free(old);
399 }
400 free(ps->sample);
401 free(ps);
402
403 /* don't complain when overrun once, happens most commonly on 1st sample */
404 if (overrun > 1)
405 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
406
407 return 0;
408 }