]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
bootchart: provide better error messages
[thirdparty/systemd.git] / src / bootchart / bootchart.c
1 /***
2 bootchart.c - This file is part of systemd-bootchart
3
4 Copyright (C) 2009-2013 Intel Coproration
5
6 Authors:
7 Auke Kok <auke-jan.h.kok@intel.com>
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 /***
24
25 Many thanks to those who contributed ideas and code:
26 - Ziga Mahkovec - Original bootchart author
27 - Anders Norgaard - PyBootchartgui
28 - Michael Meeks - bootchart2
29 - Scott James Remnant - Ubuntu C-based logger
30 - Arjan van der Ven - for the idea to merge bootgraph.pl functionality
31
32 ***/
33
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #include <sys/resource.h>
37 #include <sys/stat.h>
38 #include <stdio.h>
39 #include <signal.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <time.h>
44 #include <getopt.h>
45 #include <limits.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <stdbool.h>
49
50
51 #include "bootchart.h"
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
59 double graph_start;
60 double log_start;
61 double sampletime[MAXSAMPLES];
62 struct ps_struct *ps_first;
63 struct block_stat_struct blockstat[MAXSAMPLES];
64 int entropy_avail[MAXSAMPLES];
65 struct cpu_stat_struct cpustat[MAXCPUS];
66 int pscount;
67 int cpus;
68 double interval;
69 FILE *of = NULL;
70 int overrun = 0;
71 static int exiting = 0;
72 int sysfd=-1;
73
74 /* graph defaults */
75 bool entropy = false;
76 bool initcall = true;
77 bool relative = false;
78 bool filter = true;
79 bool pss = false;
80 int samples;
81 int len = 500; /* we record len+1 (1 start sample) */
82 double hz = 25.0; /* 20 seconds log time */
83 double scale_x = 100.0; /* 100px = 1sec */
84 double scale_y = 20.0; /* 16px = 1 process bar */
85
86 char init_path[PATH_MAX] = "/sbin/init";
87 char output_path[PATH_MAX] = "/run/log";
88
89 static struct rlimit rlim;
90
91 static void signal_handler(int sig)
92 {
93 if (sig++)
94 sig--;
95 exiting = 1;
96 }
97
98
99 int main(int argc, char *argv[])
100 {
101 _cleanup_free_ char *build = NULL;
102 struct sigaction sig;
103 struct ps_struct *ps;
104 char output_file[PATH_MAX];
105 char datestr[200];
106 time_t t = 0;
107 const char *fn;
108 _cleanup_fclose_ FILE *f;
109 int gind;
110 int i, r;
111 char *init = NULL, *output = NULL;
112
113 const ConfigTableItem items[] = {
114 { "Bootchart", "Samples", config_parse_int, 0, &len },
115 { "Bootchart", "Frequency", config_parse_double, 0, &hz },
116 { "Bootchart", "Relative", config_parse_bool, 0, &relative },
117 { "Bootchart", "Filter", config_parse_bool, 0, &filter },
118 { "Bootchart", "Output", config_parse_path, 0, &output },
119 { "Bootchart", "Init", config_parse_path, 0, &init },
120 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &pss },
121 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &entropy },
122 { "Bootchart", "ScaleX", config_parse_double, 0, &scale_x },
123 { "Bootchart", "ScaleY", config_parse_double, 0, &scale_y },
124 { NULL, NULL, NULL, 0, NULL }
125 };
126
127 rlim.rlim_cur = 4096;
128 rlim.rlim_max = 4096;
129 (void) setrlimit(RLIMIT_NOFILE, &rlim);
130
131 fn = "/etc/systemd/bootchart.conf";
132 f = fopen(fn, "re");
133 if (f) {
134 r = config_parse(fn, f, NULL, config_item_table_lookup, (void*) items, true, NULL);
135 if (r < 0)
136 log_warning("Failed to parse configuration file: %s", strerror(-r));
137
138 if (init != NULL)
139 strscpy(init_path, sizeof(init_path), init);
140 if (output != NULL)
141 strscpy(output_path, sizeof(output_path), output);
142 }
143
144 while (1) {
145 static struct option opts[] = {
146 {"rel", no_argument, NULL, 'r'},
147 {"freq", required_argument, NULL, 'f'},
148 {"samples", required_argument, NULL, 'n'},
149 {"pss", no_argument, NULL, 'p'},
150 {"output", required_argument, NULL, 'o'},
151 {"init", required_argument, NULL, 'i'},
152 {"filter", no_argument, NULL, 'F'},
153 {"help", no_argument, NULL, 'h'},
154 {"scale-x", required_argument, NULL, 'x'},
155 {"scale-y", required_argument, NULL, 'y'},
156 {"entropy", no_argument, NULL, 'e'},
157 {NULL, 0, NULL, 0}
158 };
159
160 gind = 0;
161
162 i = getopt_long(argc, argv, "erpf:n:o:i:Fhx:y:", opts, &gind);
163 if (i == -1)
164 break;
165 switch (i) {
166 case 'r':
167 relative = true;
168 break;
169 case 'f':
170 r = safe_atod(optarg, &hz);
171 if (r < 0)
172 log_warning("failed to parse --freq/-f argument '%s': %s",
173 optarg, strerror(-r));
174 break;
175 case 'F':
176 filter = false;
177 break;
178 case 'n':
179 r = safe_atoi(optarg, &len);
180 if (r < 0)
181 log_warning("failed to parse --samples/-n argument '%s': %s",
182 optarg, strerror(-r));
183 break;
184 case 'o':
185 path_kill_slashes(optarg);
186 strscpy(output_path, sizeof(output_path), optarg);
187 break;
188 case 'i':
189 path_kill_slashes(optarg);
190 strscpy(init_path, sizeof(init_path), optarg);
191 break;
192 case 'p':
193 pss = true;
194 break;
195 case 'x':
196 r = safe_atod(optarg, &scale_x);
197 if (r < 0)
198 log_warning("failed to parse --scale-x/-x argument '%s': %s",
199 optarg, strerror(-r));
200 break;
201 case 'y':
202 r = safe_atod(optarg, &scale_y);
203 if (r < 0)
204 log_warning("failed to parse --scale-y/-y argument '%s': %s",
205 optarg, strerror(-r));
206 break;
207 case 'e':
208 entropy = true;
209 break;
210 case 'h':
211 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
212 fprintf(stderr, " --rel, -r Record time relative to recording\n");
213 fprintf(stderr, " --freq, -f N Sample frequency [%f]\n", hz);
214 fprintf(stderr, " --samples, -n N Stop sampling at [%d] samples\n", len);
215 fprintf(stderr, " --scale-x, -x N Scale the graph horizontally [%f] \n", scale_x);
216 fprintf(stderr, " --scale-y, -y N Scale the graph vertically [%f] \n", scale_y);
217 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
218 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
219 fprintf(stderr, " --output, -o [PATH] Path to output files [%s]\n", output_path);
220 fprintf(stderr, " --init, -i [PATH] Path to init executable [%s]\n", init_path);
221 fprintf(stderr, " --filter, -F Disable filtering of processes from the graph\n");
222 fprintf(stderr, " that are of less importance or short-lived\n");
223 fprintf(stderr, " --help, -h Display this message\n");
224 fprintf(stderr, "See bootchart.conf for more information.\n");
225 exit (EXIT_SUCCESS);
226 break;
227 default:
228 break;
229 }
230 }
231
232 if (len > MAXSAMPLES) {
233 fprintf(stderr, "Error: samples exceeds maximum\n");
234 exit(EXIT_FAILURE);
235 }
236
237 if (hz <= 0.0) {
238 fprintf(stderr, "Error: Frequency needs to be > 0\n");
239 exit(EXIT_FAILURE);
240 }
241
242 /*
243 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
244 * fork:
245 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
246 * - child logs data
247 */
248 if (getpid() == 1) {
249 if (fork()) {
250 /* parent */
251 execl(init_path, init_path, NULL);
252 }
253 }
254 argv[0][0] = '@';
255
256 /* start with empty ps LL */
257 ps_first = calloc(1, sizeof(struct ps_struct));
258 if (!ps_first) {
259 perror("calloc(ps_struct)");
260 exit(EXIT_FAILURE);
261 }
262
263 /* handle TERM/INT nicely */
264 memset(&sig, 0, sizeof(struct sigaction));
265 sig.sa_handler = signal_handler;
266 sigaction(SIGHUP, &sig, NULL);
267
268 interval = (1.0 / hz) * 1000000000.0;
269
270 log_uptime();
271
272 /* main program loop */
273 while (!exiting) {
274 int res;
275 double sample_stop;
276 struct timespec req;
277 time_t newint_s;
278 long newint_ns;
279 double elapsed;
280 double timeleft;
281
282 sampletime[samples] = gettime_ns();
283
284 if (!of && (access(output_path, R_OK|W_OK|X_OK) == 0)) {
285 t = time(NULL);
286 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
287 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
288 of = fopen(output_file, "w");
289 }
290
291 if (sysfd < 0) {
292 sysfd = open("/sys", O_RDONLY);
293 }
294
295 if (!build) {
296 parse_env_file("/etc/os-release", NEWLINE,
297 "PRETTY_NAME", &build,
298 NULL);
299 }
300
301 /* wait for /proc to become available, discarding samples */
302 if (!(graph_start > 0.0))
303 log_uptime();
304 else
305 log_sample(samples);
306
307 sample_stop = gettime_ns();
308
309 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
310 timeleft = interval - elapsed;
311
312 newint_s = (time_t)(timeleft / 1000000000.0);
313 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
314
315 /*
316 * check if we have not consumed our entire timeslice. If we
317 * do, don't sleep and take a new sample right away.
318 * we'll lose all the missed samples and overrun our total
319 * time
320 */
321 if ((newint_ns > 0) || (newint_s > 0)) {
322 req.tv_sec = newint_s;
323 req.tv_nsec = newint_ns;
324
325 res = nanosleep(&req, NULL);
326 if (res) {
327 if (errno == EINTR) {
328 /* caught signal, probably HUP! */
329 break;
330 }
331 perror("nanosleep()");
332 exit (EXIT_FAILURE);
333 }
334 } else {
335 overrun++;
336 /* calculate how many samples we lost and scrap them */
337 len = len + ((int)(newint_ns / interval));
338 }
339
340 samples++;
341
342 if (samples > len)
343 break;
344
345 }
346
347 /* do some cleanup, close fd's */
348 ps = ps_first;
349 while (ps->next_ps) {
350 ps = ps->next_ps;
351 if (ps->schedstat)
352 close(ps->schedstat);
353 if (ps->sched)
354 close(ps->sched);
355 if (ps->smaps)
356 fclose(ps->smaps);
357 }
358
359 if (!of) {
360 t = time(NULL);
361 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
362 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
363 of = fopen(output_file, "w");
364 }
365
366 if (!of) {
367 fprintf(stderr, "opening output file '%s': %m\n", output_file);
368 exit (EXIT_FAILURE);
369 }
370
371 svg_do(build);
372
373 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
374 fclose(of);
375
376 closedir(proc);
377 close(sysfd);
378
379 /* nitpic cleanups */
380 ps = ps_first;
381 while (ps->next_ps) {
382 struct ps_struct *old = ps;
383 ps = ps->next_ps;
384 free(old->sample);
385 free(old);
386 }
387 free(ps->sample);
388 free(ps);
389
390 /* don't complain when overrun once, happens most commonly on 1st sample */
391 if (overrun > 1)
392 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
393
394 return 0;
395 }