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