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