chiark / gitweb /
bus: make the kdbus code valgrind clean
[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                 .sa_handler = signal_handler,
244         };
245         struct ps_struct *ps;
246         char output_file[PATH_MAX];
247         char datestr[200];
248         time_t t = 0;
249         int r;
250         struct rlimit rlim;
251
252         parse_conf();
253
254         r = parse_args(argc, argv);
255         if (r < 0)
256                 return EXIT_FAILURE;
257
258         /*
259          * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
260          * fork:
261          * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
262          * - child logs data
263          */
264         if (getpid() == 1) {
265                 if (fork()) {
266                         /* parent */
267                         execl(arg_init_path, arg_init_path, NULL);
268                 }
269         }
270         argv[0][0] = '@';
271
272         rlim.rlim_cur = 4096;
273         rlim.rlim_max = 4096;
274         (void) setrlimit(RLIMIT_NOFILE, &rlim);
275
276         /* start with empty ps LL */
277         ps_first = calloc(1, sizeof(struct ps_struct));
278         if (!ps_first) {
279                 perror("calloc(ps_struct)");
280                 exit(EXIT_FAILURE);
281         }
282
283         /* handle TERM/INT nicely */
284         sigaction(SIGHUP, &sig, NULL);
285
286         interval = (1.0 / arg_hz) * 1000000000.0;
287
288         log_uptime();
289
290         /* main program loop */
291         for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
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 -= (int)(newint_ns / interval);
354                 }
355         }
356
357         /* do some cleanup, close fd's */
358         ps = ps_first;
359         while (ps->next_ps) {
360                 ps = ps->next_ps;
361                 if (ps->schedstat)
362                         close(ps->schedstat);
363                 if (ps->sched)
364                         close(ps->sched);
365                 if (ps->smaps)
366                         fclose(ps->smaps);
367         }
368
369         if (!of) {
370                 t = time(NULL);
371                 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
372                 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
373                 of = fopen(output_file, "w");
374         }
375
376         if (!of) {
377                 fprintf(stderr, "opening output file '%s': %m\n", output_file);
378                 exit (EXIT_FAILURE);
379         }
380
381         svg_do(build);
382
383         fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
384
385         if (of)
386                 fclose(of);
387
388         closedir(proc);
389         if (sysfd >= 0)
390                 close(sysfd);
391
392         /* nitpic cleanups */
393         ps = ps_first;
394         while (ps->next_ps) {
395                 struct ps_struct *old = ps;
396                 ps = ps->next_ps;
397                 free(old->sample);
398                 free(old);
399         }
400         free(ps->sample);
401         free(ps);
402
403         /* don't complain when overrun once, happens most commonly on 1st sample */
404         if (overrun > 1)
405                 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
406
407         return 0;
408 }