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