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