]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/bootchart.c
bootchart: make bootchart work from within the initrd
[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 #include <sys/time.h>
24 #include <sys/types.h>
25 #include <sys/resource.h>
26 #include <stdio.h>
27 #include <signal.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <time.h>
32 #include <getopt.h>
33 #include <limits.h>
34 #include <errno.h>
35
36
37 #include "bootchart.h"
38 #include "util.h"
39
40 double graph_start;
41 double log_start;
42 double sampletime[MAXSAMPLES];
43 struct ps_struct *ps_first;
44 struct block_stat_struct blockstat[MAXSAMPLES];
45 int entropy_avail[MAXSAMPLES];
46 struct cpu_stat_struct cpustat[MAXCPUS];
47 int pscount;
48 int cpus;
49 double interval;
50 FILE *of = NULL;
51 int overrun = 0;
52 static int exiting = 0;
53
54 /* graph defaults */
55 int entropy = 0;
56 int initcall = 1;
57 int relative;
58 int filter = 1;
59 int pss = 0;
60 int samples;
61 int len = 500; /* we record len+1 (1 start sample) */
62 double hz = 25.0; /* 20 seconds log time */
63 double scale_x = 100.0; /* 100px = 1sec */
64 double scale_y = 20.0; /* 16px = 1 process bar */
65
66 char init_path[PATH_MAX] = "/sbin/init";
67 char output_path[PATH_MAX] = "/run/log";
68
69 static struct rlimit rlim;
70
71 static void signal_handler(int sig)
72 {
73 if (sig++)
74 sig--;
75 exiting = 1;
76 }
77
78
79 int main(int argc, char *argv[])
80 {
81 struct sigaction sig;
82 struct ps_struct *ps;
83 char output_file[PATH_MAX];
84 char datestr[200];
85 time_t t = 0;
86 FILE *f;
87 int gind;
88 int i;
89
90 rlim.rlim_cur = 4096;
91 rlim.rlim_max = 4096;
92 (void) setrlimit(RLIMIT_NOFILE, &rlim);
93
94 f = fopen("/etc/systemd/bootchart.conf", "r");
95 if (f) {
96 char buf[256];
97 char *key;
98 char *val;
99
100 while (fgets(buf, 80, f) != NULL) {
101 char *c;
102
103 c = strchr(buf, '\n');
104 if (c) *c = 0; /* remove trailing \n */
105
106 if (buf[0] == '#')
107 continue; /* comment line */
108
109 key = strtok(buf, "=");
110 if (!key)
111 continue;
112 val = strtok(NULL, "=");
113 if (!val)
114 continue;
115
116 // todo: filter leading/trailing whitespace
117
118 if (streq(key, "samples"))
119 len = atoi(val);
120 if (streq(key, "freq"))
121 hz = atof(val);
122 if (streq(key, "rel"))
123 relative = atoi(val);
124 if (streq(key, "filter"))
125 filter = atoi(val);
126 if (streq(key, "pss"))
127 pss = atoi(val);
128 if (streq(key, "output"))
129 strncpy(output_path, val, PATH_MAX - 1);
130 if (streq(key, "init"))
131 strncpy(init_path, val, PATH_MAX - 1);
132 if (streq(key, "scale_x"))
133 scale_x = atof(val);
134 if (streq(key, "scale_y"))
135 scale_y = atof(val);
136 if (streq(key, "entropy"))
137 entropy = atoi(val);
138 }
139 fclose(f);
140 }
141
142 while (1) {
143 static struct option opts[] = {
144 {"rel", 0, NULL, 'r'},
145 {"freq", 1, NULL, 'f'},
146 {"samples", 1, NULL, 'n'},
147 {"pss", 0, NULL, 'p'},
148 {"output", 1, NULL, 'o'},
149 {"init", 1, NULL, 'i'},
150 {"filter", 0, NULL, 'F'},
151 {"help", 0, NULL, 'h'},
152 {"scale-x", 1, NULL, 'x'},
153 {"scale-y", 1, NULL, 'y'},
154 {"entropy", 0, NULL, 'e'},
155 {NULL, 0, NULL, 0}
156 };
157
158 gind = 0;
159
160 i = getopt_long(argc, argv, "erpf:n:o:i:Fhx:y:", opts, &gind);
161 if (i == -1)
162 break;
163 switch (i) {
164 case 'r':
165 relative = 1;
166 break;
167 case 'f':
168 hz = atof(optarg);
169 break;
170 case 'F':
171 filter = 0;
172 break;
173 case 'n':
174 len = atoi(optarg);
175 break;
176 case 'o':
177 strncpy(output_path, optarg, PATH_MAX - 1);
178 break;
179 case 'i':
180 strncpy(init_path, optarg, PATH_MAX - 1);
181 break;
182 case 'p':
183 pss = 1;
184 break;
185 case 'x':
186 scale_x = atof(optarg);
187 break;
188 case 'y':
189 scale_y = atof(optarg);
190 break;
191 case 'e':
192 entropy = 1;
193 break;
194 case 'h':
195 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
196 fprintf(stderr, " --rel, -r Record time relative to recording\n");
197 fprintf(stderr, " --freq, -f N Sample frequency [%f]\n", hz);
198 fprintf(stderr, " --samples, -n N Stop sampling at [%d] samples\n", len);
199 fprintf(stderr, " --scale-x, -x N Scale the graph horizontally [%f] \n", scale_x);
200 fprintf(stderr, " --scale-y, -y N Scale the graph vertically [%f] \n", scale_y);
201 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
202 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
203 fprintf(stderr, " --output, -o [PATH] Path to output files [%s]\n", output_path);
204 fprintf(stderr, " --init, -i [PATH] Path to init executable [%s]\n", init_path);
205 fprintf(stderr, " --filter, -F Disable filtering of processes from the graph\n");
206 fprintf(stderr, " that are of less importance or short-lived\n");
207 fprintf(stderr, " --help, -h Display this message\n");
208 fprintf(stderr, "See the installed README and bootchartd.conf.example for more information.\n");
209 exit (EXIT_SUCCESS);
210 break;
211 default:
212 break;
213 }
214 }
215
216 if (len > MAXSAMPLES) {
217 fprintf(stderr, "Error: samples exceeds maximum\n");
218 exit(EXIT_FAILURE);
219 }
220
221 if (hz <= 0.0) {
222 fprintf(stderr, "Error: Frequency needs to be > 0\n");
223 exit(EXIT_FAILURE);
224 }
225
226 /*
227 * If the kernel executed us through init=/sbin/bootchartd, then
228 * fork:
229 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
230 * - child logs data
231 */
232 if (getpid() == 1) {
233 if (fork()) {
234 /* parent */
235 execl(init_path, init_path, NULL);
236 }
237 }
238 argv[0][0] = '@';
239
240 /* start with empty ps LL */
241 ps_first = calloc(1, sizeof(struct ps_struct));
242 if (!ps_first) {
243 perror("calloc(ps_struct)");
244 exit(EXIT_FAILURE);
245 }
246
247 /* handle TERM/INT nicely */
248 memset(&sig, 0, sizeof(struct sigaction));
249 sig.sa_handler = signal_handler;
250 sigaction(SIGHUP, &sig, NULL);
251
252 interval = (1.0 / hz) * 1000000000.0;
253
254 log_uptime();
255
256 /* main program loop */
257 while (!exiting) {
258 int res;
259 double sample_stop;
260 struct timespec req;
261 time_t newint_s;
262 long newint_ns;
263 double elapsed;
264 double timeleft;
265
266 sampletime[samples] = gettime_ns();
267
268 if (!of && (access(output_path, R_OK|W_OK|X_OK) == 0)) {
269 t = time(NULL);
270 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
271 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
272 of = fopen(output_file, "w");
273 }
274
275
276 /* wait for /proc to become available, discarding samples */
277 if (!(graph_start > 0.0))
278 log_uptime();
279 else
280 log_sample(samples);
281
282 sample_stop = gettime_ns();
283
284 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
285 timeleft = interval - elapsed;
286
287 newint_s = (time_t)(timeleft / 1000000000.0);
288 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
289
290 /*
291 * check if we have not consumed our entire timeslice. If we
292 * do, don't sleep and take a new sample right away.
293 * we'll lose all the missed samples and overrun our total
294 * time
295 */
296 if ((newint_ns > 0) || (newint_s > 0)) {
297 req.tv_sec = newint_s;
298 req.tv_nsec = newint_ns;
299
300 res = nanosleep(&req, NULL);
301 if (res) {
302 if (errno == EINTR) {
303 /* caught signal, probably HUP! */
304 break;
305 }
306 perror("nanosleep()");
307 exit (EXIT_FAILURE);
308 }
309 } else {
310 overrun++;
311 /* calculate how many samples we lost and scrap them */
312 len = len + ((int)(newint_ns / interval));
313 }
314
315 samples++;
316
317 if (samples > len)
318 break;
319
320 }
321
322 /* do some cleanup, close fd's */
323 ps = ps_first;
324 while (ps->next_ps) {
325 ps = ps->next_ps;
326 if (ps->schedstat)
327 close(ps->schedstat);
328 if (ps->sched)
329 close(ps->sched);
330 if (ps->smaps)
331 fclose(ps->smaps);
332 }
333 closedir(proc);
334
335 if (!of) {
336 t = time(NULL);
337 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
338 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
339 of = fopen(output_file, "w");
340 }
341
342 if (!of) {
343 perror("open output_file");
344 exit (EXIT_FAILURE);
345 }
346
347 svg_do();
348
349 fprintf(stderr, "bootchartd: Wrote %s\n", output_file);
350 fclose(of);
351
352 /* nitpic cleanups */
353 ps = ps_first;
354 while (ps->next_ps) {
355 struct ps_struct *old = ps;
356 ps = ps->next_ps;
357 free(old->sample);
358 free(old);
359 }
360 free(ps->sample);
361 free(ps);
362
363 /* don't complain when overrun once, happens most commonly on 1st sample */
364 if (overrun > 1)
365 fprintf(stderr, "bootchartd: Warning: sample time overrun %i times\n", overrun);
366
367 return 0;
368 }