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