chiark / gitweb /
bootchart: add control group option
[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", argv[0]);
213                         fprintf(stderr, " --rel,       -r          Record time relative to recording\n");
214                         fprintf(stderr, " --freq,      -f f        Sample frequency [%f]\n", arg_hz);
215                         fprintf(stderr, " --samples,   -n N        Stop sampling at [%d] samples\n", arg_samples_len);
216                         fprintf(stderr, " --scale-x,   -x N        Scale the graph horizontally [%f] \n", arg_scale_x);
217                         fprintf(stderr, " --scale-y,   -y N        Scale the graph vertically [%f] \n", arg_scale_y);
218                         fprintf(stderr, " --pss,       -p          Enable PSS graph (CPU intensive)\n");
219                         fprintf(stderr, " --entropy,   -e          Enable the entropy_avail graph\n");
220                         fprintf(stderr, " --output,    -o [PATH]   Path to output files [%s]\n", arg_output_path);
221                         fprintf(stderr, " --init,      -i [PATH]   Path to init executable [%s]\n", arg_init_path);
222                         fprintf(stderr, " --no-filter, -F          Disable filtering of processes from the graph\n");
223                         fprintf(stderr, "                          that are of less importance or short-lived\n");
224                         fprintf(stderr, " --cmdline,   -C          Display the full command line with arguments\n");
225                         fprintf(stderr, "                          of processes, instead of only the process name\n");
226                         fprintf(stderr, " --control-group, -c      Display process control group\n");
227                         fprintf(stderr, " --help,      -h          Display this message\n");
228                         fprintf(stderr, "See bootchart.conf for more information.\n");
229                         exit (EXIT_SUCCESS);
230                 default:
231                         break;
232                 }
233         }
234
235         if (arg_hz <= 0.0) {
236                 fprintf(stderr, "Error: Frequency needs to be > 0\n");
237                 return -EINVAL;
238         }
239
240         return 0;
241 }
242
243 static void do_journal_append(char *file) {
244         struct iovec iovec[5];
245         int r, f, j = 0;
246         ssize_t n;
247         _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
248                 *p = NULL;
249
250         bootchart_file = strappend("BOOTCHART_FILE=", file);
251         if (bootchart_file)
252                 IOVEC_SET_STRING(iovec[j++], bootchart_file);
253
254         IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
255         IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
256         bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
257         if (bootchart_message)
258                 IOVEC_SET_STRING(iovec[j++], bootchart_message);
259
260         p = malloc(9 + BOOTCHART_MAX);
261         if (!p) {
262                 log_oom();
263                 return;
264         }
265
266         memcpy(p, "BOOTCHART=", 10);
267
268         f = open(file, O_RDONLY|O_CLOEXEC);
269         if (f < 0) {
270                 log_error("Failed to read bootchart data: %m");
271                 return;
272         }
273         n = loop_read(f, p + 10, BOOTCHART_MAX, false);
274         if (n < 0) {
275                 log_error("Failed to read bootchart data: %s", strerror(-n));
276                 close(f);
277                 return;
278         }
279         close(f);
280
281         iovec[j].iov_base = p;
282         iovec[j].iov_len = 10 + n;
283         j++;
284
285         r = sd_journal_sendv(iovec, j);
286         if (r < 0)
287                 log_error("Failed to send bootchart: %s", strerror(-r));
288 }
289
290 int main(int argc, char *argv[]) {
291         _cleanup_free_ char *build = NULL;
292         struct sigaction sig = {
293                 .sa_handler = signal_handler,
294         };
295         struct ps_struct *ps;
296         char output_file[PATH_MAX];
297         char datestr[200];
298         time_t t = 0;
299         int r;
300         struct rlimit rlim;
301
302         parse_conf();
303
304         r = parse_args(argc, argv);
305         if (r < 0)
306                 return EXIT_FAILURE;
307
308         /*
309          * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
310          * fork:
311          * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
312          * - child logs data
313          */
314         if (getpid() == 1) {
315                 if (fork()) {
316                         /* parent */
317                         execl(arg_init_path, arg_init_path, NULL);
318                 }
319         }
320         argv[0][0] = '@';
321
322         rlim.rlim_cur = 4096;
323         rlim.rlim_max = 4096;
324         (void) setrlimit(RLIMIT_NOFILE, &rlim);
325
326         /* start with empty ps LL */
327         ps_first = new0(struct ps_struct, 1);
328         if (!ps_first) {
329                 log_oom();
330                 return EXIT_FAILURE;
331         }
332
333         /* handle TERM/INT nicely */
334         sigaction(SIGHUP, &sig, NULL);
335
336         interval = (1.0 / arg_hz) * 1000000000.0;
337
338         log_uptime();
339
340         LIST_HEAD_INIT(head);
341
342         /* main program loop */
343         for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
344                 int res;
345                 double sample_stop;
346                 struct timespec req;
347                 time_t newint_s;
348                 long newint_ns;
349                 double elapsed;
350                 double timeleft;
351
352                 sampledata = new0(struct list_sample_data, 1);
353                 if (sampledata == NULL) {
354                         log_error("Failed to allocate memory for a node: %m");
355                         return -1;
356                 }
357
358                 sampledata->sampletime = gettime_ns();
359                 sampledata->counter = samples;
360
361                 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
362                         t = time(NULL);
363                         strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
364                         snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
365                         of = fopen(output_file, "we");
366                 }
367
368                 if (sysfd < 0)
369                         sysfd = open("/sys", O_RDONLY|O_CLOEXEC);
370
371                 if (!build)
372                         parse_env_file("/etc/os-release", NEWLINE,
373                                        "PRETTY_NAME", &build,
374                                        NULL);
375
376                 /* wait for /proc to become available, discarding samples */
377                 if (graph_start <= 0.0)
378                         log_uptime();
379                 else
380                         log_sample(samples, &sampledata);
381
382                 sample_stop = gettime_ns();
383
384                 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
385                 timeleft = interval - elapsed;
386
387                 newint_s = (time_t)(timeleft / 1000000000.0);
388                 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
389
390                 /*
391                  * check if we have not consumed our entire timeslice. If we
392                  * do, don't sleep and take a new sample right away.
393                  * we'll lose all the missed samples and overrun our total
394                  * time
395                  */
396                 if (newint_ns > 0 || newint_s > 0) {
397                         req.tv_sec = newint_s;
398                         req.tv_nsec = newint_ns;
399
400                         res = nanosleep(&req, NULL);
401                         if (res) {
402                                 if (errno == EINTR) {
403                                         /* caught signal, probably HUP! */
404                                         break;
405                                 }
406                                 log_error("nanosleep() failed: %m");
407                                 exit(EXIT_FAILURE);
408                         }
409                 } else {
410                         overrun++;
411                         /* calculate how many samples we lost and scrap them */
412                         arg_samples_len -= (int)(newint_ns / interval);
413                 }
414                 LIST_PREPEND(link, head, sampledata);
415         }
416
417         /* do some cleanup, close fd's */
418         ps = ps_first;
419         while (ps->next_ps) {
420                 ps = ps->next_ps;
421                 if (ps->schedstat)
422                         close(ps->schedstat);
423                 if (ps->sched)
424                         close(ps->sched);
425                 if (ps->smaps)
426                         fclose(ps->smaps);
427         }
428
429         if (!of) {
430                 t = time(NULL);
431                 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
432                 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
433                 of = fopen(output_file, "we");
434         }
435
436         if (!of) {
437                 fprintf(stderr, "opening output file '%s': %m\n", output_file);
438                 exit (EXIT_FAILURE);
439         }
440
441         svg_do(build);
442
443         fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
444
445         do_journal_append(output_file);
446
447         if (of)
448                 fclose(of);
449
450         closedir(proc);
451         if (sysfd >= 0)
452                 close(sysfd);
453
454         /* nitpic cleanups */
455         ps = ps_first->next_ps;
456         while (ps->next_ps) {
457                 struct ps_struct *old;
458
459                 old = ps;
460                 old->sample = ps->first;
461                 ps = ps->next_ps;
462                 while (old->sample->next) {
463                         struct ps_sched_struct *oldsample = old->sample;
464
465                         old->sample = old->sample->next;
466                         free(oldsample);
467                 }
468                 free(old->cgroup);
469                 free(old->sample);
470                 free(old);
471         }
472         free(ps->cgroup);
473         free(ps->sample);
474         free(ps);
475
476         sampledata = head;
477         while (sampledata->link_prev) {
478                 struct list_sample_data *old_sampledata = sampledata;
479                 sampledata = sampledata->link_prev;
480                 free(old_sampledata);
481         }
482         free(sampledata);
483         /* don't complain when overrun once, happens most commonly on 1st sample */
484         if (overrun > 1)
485                 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
486
487         return 0;
488 }