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