chiark / gitweb /
bootchart: don't parse /proc/uptime, use CLOCK_BOOTTIME
[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_args(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;
184
185         while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0) {
186                 int r;
187
188                 switch (c) {
189                 case 'r':
190                         arg_relative = true;
191                         break;
192                 case 'f':
193                         r = safe_atod(optarg, &arg_hz);
194                         if (r < 0)
195                                 log_warning("failed to parse --freq/-f argument '%s': %s",
196                                             optarg, strerror(-r));
197                         break;
198                 case 'F':
199                         arg_filter = false;
200                         break;
201                 case 'C':
202                         arg_show_cmdline = true;
203                         break;
204                 case 'c':
205                         arg_show_cgroup = true;
206                         break;
207                 case 'n':
208                         r = safe_atoi(optarg, &arg_samples_len);
209                         if (r < 0)
210                                 log_warning("failed to parse --samples/-n argument '%s': %s",
211                                             optarg, strerror(-r));
212                         break;
213                 case 'o':
214                         path_kill_slashes(optarg);
215                         strscpy(arg_output_path, sizeof(arg_output_path), optarg);
216                         break;
217                 case 'i':
218                         path_kill_slashes(optarg);
219                         strscpy(arg_init_path, sizeof(arg_init_path), optarg);
220                         break;
221                 case 'p':
222                         arg_pss = true;
223                         break;
224                 case 'x':
225                         r = safe_atod(optarg, &arg_scale_x);
226                         if (r < 0)
227                                 log_warning("failed to parse --scale-x/-x argument '%s': %s",
228                                             optarg, strerror(-r));
229                         break;
230                 case 'y':
231                         r = safe_atod(optarg, &arg_scale_y);
232                         if (r < 0)
233                                 log_warning("failed to parse --scale-y/-y argument '%s': %s",
234                                             optarg, strerror(-r));
235                         break;
236                 case 'e':
237                         arg_entropy = true;
238                         break;
239                 case 'h':
240                         help();
241                         exit (EXIT_SUCCESS);
242                 default:
243                         break;
244                 }
245         }
246
247         if (arg_hz <= 0.0) {
248                 fprintf(stderr, "Error: Frequency needs to be > 0\n");
249                 return -EINVAL;
250         }
251
252         return 0;
253 }
254
255 static void do_journal_append(char *file) {
256         struct iovec iovec[5];
257         int r, f, j = 0;
258         ssize_t n;
259         _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
260                 *p = NULL;
261
262         bootchart_file = strappend("BOOTCHART_FILE=", file);
263         if (bootchart_file)
264                 IOVEC_SET_STRING(iovec[j++], bootchart_file);
265
266         IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
267         IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
268         bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
269         if (bootchart_message)
270                 IOVEC_SET_STRING(iovec[j++], bootchart_message);
271
272         p = malloc(9 + BOOTCHART_MAX);
273         if (!p) {
274                 log_oom();
275                 return;
276         }
277
278         memcpy(p, "BOOTCHART=", 10);
279
280         f = open(file, O_RDONLY|O_CLOEXEC);
281         if (f < 0) {
282                 log_error("Failed to read bootchart data: %m");
283                 return;
284         }
285         n = loop_read(f, p + 10, BOOTCHART_MAX, false);
286         if (n < 0) {
287                 log_error("Failed to read bootchart data: %s", strerror(-n));
288                 close(f);
289                 return;
290         }
291         close(f);
292
293         iovec[j].iov_base = p;
294         iovec[j].iov_len = 10 + n;
295         j++;
296
297         r = sd_journal_sendv(iovec, j);
298         if (r < 0)
299                 log_error("Failed to send bootchart: %s", strerror(-r));
300 }
301
302 int main(int argc, char *argv[]) {
303         _cleanup_free_ char *build = NULL;
304         struct sigaction sig = {
305                 .sa_handler = signal_handler,
306         };
307         struct ps_struct *ps;
308         char output_file[PATH_MAX];
309         char datestr[200];
310         time_t t = 0;
311         int r;
312         struct rlimit rlim;
313         bool has_procfs = false;
314
315         parse_conf();
316
317         r = parse_args(argc, argv);
318         if (r < 0)
319                 return EXIT_FAILURE;
320
321         /*
322          * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
323          * fork:
324          * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
325          * - child logs data
326          */
327         if (getpid() == 1) {
328                 if (fork()) {
329                         /* parent */
330                         execl(arg_init_path, arg_init_path, NULL);
331                 }
332         }
333         argv[0][0] = '@';
334
335         rlim.rlim_cur = 4096;
336         rlim.rlim_max = 4096;
337         (void) setrlimit(RLIMIT_NOFILE, &rlim);
338
339         /* start with empty ps LL */
340         ps_first = new0(struct ps_struct, 1);
341         if (!ps_first) {
342                 log_oom();
343                 return EXIT_FAILURE;
344         }
345
346         /* handle TERM/INT nicely */
347         sigaction(SIGHUP, &sig, NULL);
348
349         interval = (1.0 / arg_hz) * 1000000000.0;
350
351         log_uptime();
352
353         has_procfs = access("/proc/vmstat", F_OK) == 0;
354
355         LIST_HEAD_INIT(head);
356
357         /* main program loop */
358         for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
359                 int res;
360                 double sample_stop;
361                 struct timespec req;
362                 time_t newint_s;
363                 long newint_ns;
364                 double elapsed;
365                 double timeleft;
366
367                 sampledata = new0(struct list_sample_data, 1);
368                 if (sampledata == NULL) {
369                         log_error("Failed to allocate memory for a node: %m");
370                         return -1;
371                 }
372
373                 sampledata->sampletime = gettime_ns();
374                 sampledata->counter = samples;
375
376                 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
377                         t = time(NULL);
378                         strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
379                         snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
380                         of = fopen(output_file, "we");
381                 }
382
383                 if (sysfd < 0)
384                         sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
385
386                 if (!build) {
387                         if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
388                                 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
389                 }
390
391                 if (has_procfs)
392                         log_sample(samples, &sampledata);
393                 else
394                         /* wait for /proc to become available, discarding samples */
395                         has_procfs = access("/proc/vmstat", F_OK) == 0;
396
397                 sample_stop = gettime_ns();
398
399                 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
400                 timeleft = interval - elapsed;
401
402                 newint_s = (time_t)(timeleft / 1000000000.0);
403                 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
404
405                 /*
406                  * check if we have not consumed our entire timeslice. If we
407                  * do, don't sleep and take a new sample right away.
408                  * we'll lose all the missed samples and overrun our total
409                  * time
410                  */
411                 if (newint_ns > 0 || newint_s > 0) {
412                         req.tv_sec = newint_s;
413                         req.tv_nsec = newint_ns;
414
415                         res = nanosleep(&req, NULL);
416                         if (res) {
417                                 if (errno == EINTR) {
418                                         /* caught signal, probably HUP! */
419                                         break;
420                                 }
421                                 log_error("nanosleep() failed: %m");
422                                 exit(EXIT_FAILURE);
423                         }
424                 } else {
425                         overrun++;
426                         /* calculate how many samples we lost and scrap them */
427                         arg_samples_len -= (int)(newint_ns / interval);
428                 }
429                 LIST_PREPEND(link, head, sampledata);
430         }
431
432         /* do some cleanup, close fd's */
433         ps = ps_first;
434         while (ps->next_ps) {
435                 ps = ps->next_ps;
436                 if (ps->schedstat)
437                         close(ps->schedstat);
438                 if (ps->sched)
439                         close(ps->sched);
440                 if (ps->smaps)
441                         fclose(ps->smaps);
442         }
443
444         if (!of) {
445                 t = time(NULL);
446                 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
447                 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
448                 of = fopen(output_file, "we");
449         }
450
451         if (!of) {
452                 fprintf(stderr, "opening output file '%s': %m\n", output_file);
453                 exit (EXIT_FAILURE);
454         }
455
456         svg_do(build);
457
458         fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
459
460         do_journal_append(output_file);
461
462         if (of)
463                 fclose(of);
464
465         closedir(proc);
466         if (sysfd >= 0)
467                 close(sysfd);
468
469         /* nitpic cleanups */
470         ps = ps_first->next_ps;
471         while (ps->next_ps) {
472                 struct ps_struct *old;
473
474                 old = ps;
475                 old->sample = ps->first;
476                 ps = ps->next_ps;
477                 while (old->sample->next) {
478                         struct ps_sched_struct *oldsample = old->sample;
479
480                         old->sample = old->sample->next;
481                         free(oldsample);
482                 }
483                 free(old->cgroup);
484                 free(old->sample);
485                 free(old);
486         }
487         free(ps->cgroup);
488         free(ps->sample);
489         free(ps);
490
491         sampledata = head;
492         while (sampledata->link_prev) {
493                 struct list_sample_data *old_sampledata = sampledata;
494                 sampledata = sampledata->link_prev;
495                 free(old_sampledata);
496         }
497         free(sampledata);
498         /* don't complain when overrun once, happens most commonly on 1st sample */
499         if (overrun > 1)
500                 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
501
502         return 0;
503 }