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