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