]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
man: move bootchart README to manpage, docbooksify
[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 safe_atod(optarg, &hz);
171 break;
172 case 'F':
173 filter = false;
174 break;
175 case 'n':
176 safe_atoi(optarg, &len);
177 break;
178 case 'o':
179 path_kill_slashes(optarg);
180 strscpy(output_path, sizeof(output_path), optarg);
181 break;
182 case 'i':
183 path_kill_slashes(optarg);
184 strscpy(init_path, sizeof(init_path), optarg);
185 break;
186 case 'p':
187 pss = true;
188 break;
189 case 'x':
190 safe_atod(optarg, &scale_x);
191 break;
192 case 'y':
193 safe_atod(optarg, &scale_y);
194 break;
195 case 'e':
196 entropy = true;
197 break;
198 case 'h':
199 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
200 fprintf(stderr, " --rel, -r Record time relative to recording\n");
201 fprintf(stderr, " --freq, -f N Sample frequency [%f]\n", hz);
202 fprintf(stderr, " --samples, -n N Stop sampling at [%d] samples\n", len);
203 fprintf(stderr, " --scale-x, -x N Scale the graph horizontally [%f] \n", scale_x);
204 fprintf(stderr, " --scale-y, -y N Scale the graph vertically [%f] \n", scale_y);
205 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
206 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
207 fprintf(stderr, " --output, -o [PATH] Path to output files [%s]\n", output_path);
208 fprintf(stderr, " --init, -i [PATH] Path to init executable [%s]\n", init_path);
209 fprintf(stderr, " --filter, -F Disable filtering of processes from the graph\n");
210 fprintf(stderr, " that are of less importance or short-lived\n");
211 fprintf(stderr, " --help, -h Display this message\n");
212 fprintf(stderr, "See the installed README and bootchartd.conf.example for more information.\n");
213 exit (EXIT_SUCCESS);
214 break;
215 default:
216 break;
217 }
218 }
219
220 if (len > MAXSAMPLES) {
221 fprintf(stderr, "Error: samples exceeds maximum\n");
222 exit(EXIT_FAILURE);
223 }
224
225 if (hz <= 0.0) {
226 fprintf(stderr, "Error: Frequency needs to be > 0\n");
227 exit(EXIT_FAILURE);
228 }
229
230 /*
231 * If the kernel executed us through init=/sbin/bootchartd, then
232 * fork:
233 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
234 * - child logs data
235 */
236 if (getpid() == 1) {
237 if (fork()) {
238 /* parent */
239 execl(init_path, init_path, NULL);
240 }
241 }
242 argv[0][0] = '@';
243
244 /* start with empty ps LL */
245 ps_first = calloc(1, sizeof(struct ps_struct));
246 if (!ps_first) {
247 perror("calloc(ps_struct)");
248 exit(EXIT_FAILURE);
249 }
250
251 /* handle TERM/INT nicely */
252 memset(&sig, 0, sizeof(struct sigaction));
253 sig.sa_handler = signal_handler;
254 sigaction(SIGHUP, &sig, NULL);
255
256 interval = (1.0 / hz) * 1000000000.0;
257
258 log_uptime();
259
260 /* main program loop */
261 while (!exiting) {
262 int res;
263 double sample_stop;
264 struct timespec req;
265 time_t newint_s;
266 long newint_ns;
267 double elapsed;
268 double timeleft;
269
270 sampletime[samples] = gettime_ns();
271
272 if (!of && (access(output_path, R_OK|W_OK|X_OK) == 0)) {
273 t = time(NULL);
274 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
275 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
276 of = fopen(output_file, "w");
277 }
278
279 if (sysfd < 0) {
280 sysfd = open("/sys", O_RDONLY);
281 }
282
283 if (!build) {
284 parse_env_file("/etc/os-release", NEWLINE,
285 "PRETTY_NAME", &build,
286 NULL);
287 }
288
289 /* wait for /proc to become available, discarding samples */
290 if (!(graph_start > 0.0))
291 log_uptime();
292 else
293 log_sample(samples);
294
295 sample_stop = gettime_ns();
296
297 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
298 timeleft = interval - elapsed;
299
300 newint_s = (time_t)(timeleft / 1000000000.0);
301 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
302
303 /*
304 * check if we have not consumed our entire timeslice. If we
305 * do, don't sleep and take a new sample right away.
306 * we'll lose all the missed samples and overrun our total
307 * time
308 */
309 if ((newint_ns > 0) || (newint_s > 0)) {
310 req.tv_sec = newint_s;
311 req.tv_nsec = newint_ns;
312
313 res = nanosleep(&req, NULL);
314 if (res) {
315 if (errno == EINTR) {
316 /* caught signal, probably HUP! */
317 break;
318 }
319 perror("nanosleep()");
320 exit (EXIT_FAILURE);
321 }
322 } else {
323 overrun++;
324 /* calculate how many samples we lost and scrap them */
325 len = len + ((int)(newint_ns / interval));
326 }
327
328 samples++;
329
330 if (samples > len)
331 break;
332
333 }
334
335 /* do some cleanup, close fd's */
336 ps = ps_first;
337 while (ps->next_ps) {
338 ps = ps->next_ps;
339 if (ps->schedstat)
340 close(ps->schedstat);
341 if (ps->sched)
342 close(ps->sched);
343 if (ps->smaps)
344 fclose(ps->smaps);
345 }
346
347 if (!of) {
348 t = time(NULL);
349 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
350 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
351 of = fopen(output_file, "w");
352 }
353
354 if (!of) {
355 perror("open output_file");
356 exit (EXIT_FAILURE);
357 }
358
359 svg_do(build);
360
361 fprintf(stderr, "bootchartd: Wrote %s\n", output_file);
362 fclose(of);
363
364 closedir(proc);
365 close(sysfd);
366
367 /* nitpic cleanups */
368 ps = ps_first;
369 while (ps->next_ps) {
370 struct ps_struct *old = ps;
371 ps = ps->next_ps;
372 free(old->sample);
373 free(old);
374 }
375 free(ps->sample);
376 free(ps);
377
378 /* don't complain when overrun once, happens most commonly on 1st sample */
379 if (overrun > 1)
380 fprintf(stderr, "bootchartd: Warning: sample time overrun %i times\n", overrun);
381
382 return 0;
383 }