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