chiark / gitweb /
networkd-wait-online: track links
[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 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                 {NULL, 0, NULL, 0}
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
314         parse_conf();
315
316         r = parse_args(argc, argv);
317         if (r < 0)
318                 return EXIT_FAILURE;
319
320         /*
321          * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
322          * fork:
323          * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
324          * - child logs data
325          */
326         if (getpid() == 1) {
327                 if (fork()) {
328                         /* parent */
329                         execl(arg_init_path, arg_init_path, NULL);
330                 }
331         }
332         argv[0][0] = '@';
333
334         rlim.rlim_cur = 4096;
335         rlim.rlim_max = 4096;
336         (void) setrlimit(RLIMIT_NOFILE, &rlim);
337
338         /* start with empty ps LL */
339         ps_first = new0(struct ps_struct, 1);
340         if (!ps_first) {
341                 log_oom();
342                 return EXIT_FAILURE;
343         }
344
345         /* handle TERM/INT nicely */
346         sigaction(SIGHUP, &sig, NULL);
347
348         interval = (1.0 / arg_hz) * 1000000000.0;
349
350         log_uptime();
351
352         LIST_HEAD_INIT(head);
353
354         /* main program loop */
355         for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
356                 int res;
357                 double sample_stop;
358                 struct timespec req;
359                 time_t newint_s;
360                 long newint_ns;
361                 double elapsed;
362                 double timeleft;
363
364                 sampledata = new0(struct list_sample_data, 1);
365                 if (sampledata == NULL) {
366                         log_error("Failed to allocate memory for a node: %m");
367                         return -1;
368                 }
369
370                 sampledata->sampletime = gettime_ns();
371                 sampledata->counter = samples;
372
373                 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
374                         t = time(NULL);
375                         strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
376                         snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
377                         of = fopen(output_file, "we");
378                 }
379
380                 if (sysfd < 0)
381                         sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
382
383                 if (!build) {
384                         if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT)
385                                 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL);
386                 }
387
388                 /* wait for /proc to become available, discarding samples */
389                 if (graph_start <= 0.0)
390                         log_uptime();
391                 else
392                         log_sample(samples, &sampledata);
393
394                 sample_stop = gettime_ns();
395
396                 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
397                 timeleft = interval - elapsed;
398
399                 newint_s = (time_t)(timeleft / 1000000000.0);
400                 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
401
402                 /*
403                  * check if we have not consumed our entire timeslice. If we
404                  * do, don't sleep and take a new sample right away.
405                  * we'll lose all the missed samples and overrun our total
406                  * time
407                  */
408                 if (newint_ns > 0 || newint_s > 0) {
409                         req.tv_sec = newint_s;
410                         req.tv_nsec = newint_ns;
411
412                         res = nanosleep(&req, NULL);
413                         if (res) {
414                                 if (errno == EINTR) {
415                                         /* caught signal, probably HUP! */
416                                         break;
417                                 }
418                                 log_error("nanosleep() failed: %m");
419                                 exit(EXIT_FAILURE);
420                         }
421                 } else {
422                         overrun++;
423                         /* calculate how many samples we lost and scrap them */
424                         arg_samples_len -= (int)(newint_ns / interval);
425                 }
426                 LIST_PREPEND(link, head, sampledata);
427         }
428
429         /* do some cleanup, close fd's */
430         ps = ps_first;
431         while (ps->next_ps) {
432                 ps = ps->next_ps;
433                 if (ps->schedstat)
434                         close(ps->schedstat);
435                 if (ps->sched)
436                         close(ps->sched);
437                 if (ps->smaps)
438                         fclose(ps->smaps);
439         }
440
441         if (!of) {
442                 t = time(NULL);
443                 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
444                 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
445                 of = fopen(output_file, "we");
446         }
447
448         if (!of) {
449                 fprintf(stderr, "opening output file '%s': %m\n", output_file);
450                 exit (EXIT_FAILURE);
451         }
452
453         svg_do(build);
454
455         fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
456
457         do_journal_append(output_file);
458
459         if (of)
460                 fclose(of);
461
462         closedir(proc);
463         if (sysfd >= 0)
464                 close(sysfd);
465
466         /* nitpic cleanups */
467         ps = ps_first->next_ps;
468         while (ps->next_ps) {
469                 struct ps_struct *old;
470
471                 old = ps;
472                 old->sample = ps->first;
473                 ps = ps->next_ps;
474                 while (old->sample->next) {
475                         struct ps_sched_struct *oldsample = old->sample;
476
477                         old->sample = old->sample->next;
478                         free(oldsample);
479                 }
480                 free(old->cgroup);
481                 free(old->sample);
482                 free(old);
483         }
484         free(ps->cgroup);
485         free(ps->sample);
486         free(ps);
487
488         sampledata = head;
489         while (sampledata->link_prev) {
490                 struct list_sample_data *old_sampledata = sampledata;
491                 sampledata = sampledata->link_prev;
492                 free(old_sampledata);
493         }
494         free(sampledata);
495         /* don't complain when overrun once, happens most commonly on 1st sample */
496         if (overrun > 1)
497                 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
498
499         return 0;
500 }