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