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