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