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