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