chiark / gitweb /
a722b2aabf60957f7258070e38f379773f1f6079
[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 Coproration
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
52 #include "util.h"
53 #include "fileio.h"
54 #include "macro.h"
55 #include "conf-parser.h"
56 #include "strxcpyx.h"
57 #include "path-util.h"
58 #include "store.h"
59 #include "svg.h"
60 #include "bootchart.h"
61
62 double graph_start;
63 double log_start;
64 double sampletime[MAXSAMPLES];
65 struct ps_struct *ps_first;
66 struct block_stat_struct blockstat[MAXSAMPLES];
67 int entropy_avail[MAXSAMPLES];
68 struct cpu_stat_struct cpustat[MAXCPUS];
69 int pscount;
70 int cpus;
71 double interval;
72 FILE *of = NULL;
73 int overrun = 0;
74 static int exiting = 0;
75 int sysfd=-1;
76
77 /* graph defaults */
78 bool arg_entropy = false;
79 bool initcall = true;
80 bool arg_relative = false;
81 bool arg_filter = true;
82 bool arg_show_cmdline = false;
83 bool arg_pss = false;
84 int samples;
85 int arg_samples_len = 500; /* we record len+1 (1 start sample) */
86 double arg_hz = 25.0;   /* 20 seconds log time */
87 double arg_scale_x = 100.0; /* 100px = 1sec */
88 double arg_scale_y = 20.0;  /* 16px = 1 process bar */
89
90 char arg_init_path[PATH_MAX] = "/sbin/init";
91 char arg_output_path[PATH_MAX] = "/run/log";
92
93 static struct rlimit rlim;
94
95 static void signal_handler(int sig) {
96         if (sig++)
97                 sig--;
98         exiting = 1;
99 }
100
101 int main(int argc, char *argv[]) {
102         _cleanup_free_ char *build = NULL;
103         struct sigaction sig;
104         struct ps_struct *ps;
105         char output_file[PATH_MAX];
106         char datestr[200];
107         time_t t = 0;
108         const char *fn;
109         _cleanup_fclose_ FILE *f;
110         int gind;
111         int i, r;
112         char *init = NULL, *output = NULL;
113
114         const ConfigTableItem items[] = {
115                 { "Bootchart", "Samples",          config_parse_int,    0, &arg_samples_len },
116                 { "Bootchart", "Frequency",        config_parse_double, 0, &arg_hz          },
117                 { "Bootchart", "Relative",         config_parse_bool,   0, &arg_relative    },
118                 { "Bootchart", "Filter",           config_parse_bool,   0, &arg_filter      },
119                 { "Bootchart", "Output",           config_parse_path,   0, &output          },
120                 { "Bootchart", "Init",             config_parse_path,   0, &init            },
121                 { "Bootchart", "PlotMemoryUsage",  config_parse_bool,   0, &arg_pss         },
122                 { "Bootchart", "PlotEntropyGraph", config_parse_bool,   0, &arg_entropy     },
123                 { "Bootchart", "ScaleX",           config_parse_double, 0, &arg_scale_x     },
124                 { "Bootchart", "ScaleY",           config_parse_double, 0, &arg_scale_y     },
125                 { NULL, NULL, NULL, 0, NULL }
126         };
127
128         rlim.rlim_cur = 4096;
129         rlim.rlim_max = 4096;
130         (void) setrlimit(RLIMIT_NOFILE, &rlim);
131
132         fn = "/etc/systemd/bootchart.conf";
133         f = fopen(fn, "re");
134         if (f) {
135             r = config_parse(fn, f, NULL, config_item_table_lookup, (void*) items, true, 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         while (1) {
146                 static struct option opts[] = {
147                         {"rel",       no_argument,        NULL,  'r'},
148                         {"freq",      required_argument,  NULL,  'f'},
149                         {"samples",   required_argument,  NULL,  'n'},
150                         {"pss",       no_argument,        NULL,  'p'},
151                         {"output",    required_argument,  NULL,  'o'},
152                         {"init",      required_argument,  NULL,  'i'},
153                         {"no-filter", no_argument,        NULL,  'F'},
154                         {"cmdline",   no_argument,        NULL,  'C'},
155                         {"help",      no_argument,        NULL,  'h'},
156                         {"scale-x",   required_argument,  NULL,  'x'},
157                         {"scale-y",   required_argument,  NULL,  'y'},
158                         {"entropy",   no_argument,        NULL,  'e'},
159                         {NULL, 0, NULL, 0}
160                 };
161
162                 gind = 0;
163
164                 i = getopt_long(argc, argv, "erpf:n:o:i:FChx:y:", opts, &gind);
165                 if (i == -1)
166                         break;
167                 switch (i) {
168                 case 'r':
169                         arg_relative = true;
170                         break;
171                 case 'f':
172                         r = safe_atod(optarg, &arg_hz);
173                         if (r < 0)
174                                 log_warning("failed to parse --freq/-f argument '%s': %s",
175                                             optarg, strerror(-r));
176                         break;
177                 case 'F':
178                         arg_filter = false;
179                         break;
180                 case 'C':
181                         arg_show_cmdline = true;
182                         break;
183                 case 'n':
184                         r = safe_atoi(optarg, &arg_samples_len);
185                         if (r < 0)
186                                 log_warning("failed to parse --samples/-n argument '%s': %s",
187                                             optarg, strerror(-r));
188                         break;
189                 case 'o':
190                         path_kill_slashes(optarg);
191                         strscpy(arg_output_path, sizeof(arg_output_path), optarg);
192                         break;
193                 case 'i':
194                         path_kill_slashes(optarg);
195                         strscpy(arg_init_path, sizeof(arg_init_path), optarg);
196                         break;
197                 case 'p':
198                         arg_pss = true;
199                         break;
200                 case 'x':
201                         r = safe_atod(optarg, &arg_scale_x);
202                         if (r < 0)
203                                 log_warning("failed to parse --scale-x/-x argument '%s': %s",
204                                             optarg, strerror(-r));
205                         break;
206                 case 'y':
207                         r = safe_atod(optarg, &arg_scale_y);
208                         if (r < 0)
209                                 log_warning("failed to parse --scale-y/-y argument '%s': %s",
210                                             optarg, strerror(-r));
211                         break;
212                 case 'e':
213                         arg_entropy = true;
214                         break;
215                 case 'h':
216                         fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
217                         fprintf(stderr, " --rel,       -r          Record time relative to recording\n");
218                         fprintf(stderr, " --freq,      -f f        Sample frequency [%f]\n", arg_hz);
219                         fprintf(stderr, " --samples,   -n N        Stop sampling at [%d] samples\n", arg_samples_len);
220                         fprintf(stderr, " --scale-x,   -x N        Scale the graph horizontally [%f] \n", arg_scale_x);
221                         fprintf(stderr, " --scale-y,   -y N        Scale the graph vertically [%f] \n", arg_scale_y);
222                         fprintf(stderr, " --pss,       -p          Enable PSS graph (CPU intensive)\n");
223                         fprintf(stderr, " --entropy,   -e          Enable the entropy_avail graph\n");
224                         fprintf(stderr, " --output,    -o [PATH]   Path to output files [%s]\n", arg_output_path);
225                         fprintf(stderr, " --init,      -i [PATH]   Path to init executable [%s]\n", arg_init_path);
226                         fprintf(stderr, " --no-filter, -F          Disable filtering of processes from the graph\n");
227                         fprintf(stderr, "                          that are of less importance or short-lived\n");
228                         fprintf(stderr, " --cmdline,   -C          Display the full command line with arguments\n");
229                         fprintf(stderr, "                          of processes, instead of only the process name\n");
230                         fprintf(stderr, " --help,      -h          Display this message\n");
231                         fprintf(stderr, "See bootchart.conf for more information.\n");
232                         exit (EXIT_SUCCESS);
233                         break;
234                 default:
235                         break;
236                 }
237         }
238
239         if (arg_samples_len > MAXSAMPLES) {
240                 fprintf(stderr, "Error: samples exceeds maximum\n");
241                 exit(EXIT_FAILURE);
242         }
243
244         if (arg_hz <= 0.0) {
245                 fprintf(stderr, "Error: Frequency needs to be > 0\n");
246                 exit(EXIT_FAILURE);
247         }
248
249         /*
250          * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
251          * fork:
252          * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
253          * - child logs data
254          */
255         if (getpid() == 1) {
256                 if (fork()) {
257                         /* parent */
258                         execl(arg_init_path, arg_init_path, NULL);
259                 }
260         }
261         argv[0][0] = '@';
262
263         /* start with empty ps LL */
264         ps_first = calloc(1, sizeof(struct ps_struct));
265         if (!ps_first) {
266                 perror("calloc(ps_struct)");
267                 exit(EXIT_FAILURE);
268         }
269
270         /* handle TERM/INT nicely */
271         memset(&sig, 0, sizeof(struct sigaction));
272         sig.sa_handler = signal_handler;
273         sigaction(SIGHUP, &sig, NULL);
274
275         interval = (1.0 / arg_hz) * 1000000000.0;
276
277         log_uptime();
278
279         /* main program loop */
280         while (!exiting) {
281                 int res;
282                 double sample_stop;
283                 struct timespec req;
284                 time_t newint_s;
285                 long newint_ns;
286                 double elapsed;
287                 double timeleft;
288
289                 sampletime[samples] = gettime_ns();
290
291                 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
292                         t = time(NULL);
293                         strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
294                         snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
295                         of = fopen(output_file, "w");
296                 }
297
298                 if (sysfd < 0)
299                         sysfd = open("/sys", O_RDONLY);
300
301                 if (!build)
302                         parse_env_file("/etc/os-release", NEWLINE,
303                                        "PRETTY_NAME", &build,
304                                        NULL);
305
306                 /* wait for /proc to become available, discarding samples */
307                 if (!(graph_start > 0.0))
308                         log_uptime();
309                 else
310                         log_sample(samples);
311
312                 sample_stop = gettime_ns();
313
314                 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
315                 timeleft = interval - elapsed;
316
317                 newint_s = (time_t)(timeleft / 1000000000.0);
318                 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
319
320                 /*
321                  * check if we have not consumed our entire timeslice. If we
322                  * do, don't sleep and take a new sample right away.
323                  * we'll lose all the missed samples and overrun our total
324                  * time
325                  */
326                 if ((newint_ns > 0) || (newint_s > 0)) {
327                         req.tv_sec = newint_s;
328                         req.tv_nsec = newint_ns;
329
330                         res = nanosleep(&req, NULL);
331                         if (res) {
332                                 if (errno == EINTR) {
333                                         /* caught signal, probably HUP! */
334                                         break;
335                                 }
336                                 perror("nanosleep()");
337                                 exit (EXIT_FAILURE);
338                         }
339                 } else {
340                         overrun++;
341                         /* calculate how many samples we lost and scrap them */
342                         arg_samples_len = arg_samples_len + ((int)(newint_ns / interval));
343                 }
344
345                 samples++;
346
347                 if (samples > arg_samples_len)
348                         break;
349
350         }
351
352         /* do some cleanup, close fd's */
353         ps = ps_first;
354         while (ps->next_ps) {
355                 ps = ps->next_ps;
356                 if (ps->schedstat)
357                         close(ps->schedstat);
358                 if (ps->sched)
359                         close(ps->sched);
360                 if (ps->smaps)
361                         fclose(ps->smaps);
362         }
363
364         if (!of) {
365                 t = time(NULL);
366                 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
367                 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
368                 of = fopen(output_file, "w");
369         }
370
371         if (!of) {
372                 fprintf(stderr, "opening output file '%s': %m\n", output_file);
373                 exit (EXIT_FAILURE);
374         }
375
376         svg_do(build);
377
378         fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
379
380         if (of)
381                 fclose(of);
382
383         closedir(proc);
384         if (sysfd >= 0)
385                 close(sysfd);
386
387         /* nitpic cleanups */
388         ps = ps_first;
389         while (ps->next_ps) {
390                 struct ps_struct *old = ps;
391                 ps = ps->next_ps;
392                 free(old->sample);
393                 free(old);
394         }
395         free(ps->sample);
396         free(ps);
397
398         /* don't complain when overrun once, happens most commonly on 1st sample */
399         if (overrun > 1)
400                 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
401
402         return 0;
403 }