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