chiark / gitweb /
Revert "bootchart: use _cleanup_close_"
[elogind.git] / src / bootchart / bootchart.c
1 /***
2   bootchart.c - This file is part of systemd-bootchart
3
4   Copyright (C) 2009-2013 Intel Coproration
5
6   Authors:
7     Auke Kok <auke-jan.h.kok@intel.com>
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21  ***/
22
23 /***
24
25   Many thanks to those who contributed ideas and code:
26   - Ziga Mahkovec - Original bootchart author
27   - Anders Norgaard - PyBootchartgui
28   - Michael Meeks - bootchart2
29   - Scott James Remnant - Ubuntu C-based logger
30   - Arjan van der Ven - for the idea to merge bootgraph.pl functionality
31
32  ***/
33
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #include <sys/resource.h>
37 #include <sys/stat.h>
38 #include <stdio.h>
39 #include <signal.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <time.h>
44 #include <getopt.h>
45 #include <limits.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <stdbool.h>
49
50
51 #include "bootchart.h"
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
59 double graph_start;
60 double log_start;
61 double sampletime[MAXSAMPLES];
62 struct ps_struct *ps_first;
63 struct block_stat_struct blockstat[MAXSAMPLES];
64 int entropy_avail[MAXSAMPLES];
65 struct cpu_stat_struct cpustat[MAXCPUS];
66 int pscount;
67 int cpus;
68 double interval;
69 FILE *of = NULL;
70 int overrun = 0;
71 static int exiting = 0;
72 int sysfd=-1;
73
74 /* graph defaults */
75 bool entropy = false;
76 bool initcall = true;
77 bool relative = false;
78 bool filter = true;
79 bool show_cmdline = false;
80 bool pss = false;
81 int samples;
82 int samples_len = 500; /* we record len+1 (1 start sample) */
83 double hz = 25.0;   /* 20 seconds log time */
84 double scale_x = 100.0; /* 100px = 1sec */
85 double scale_y = 20.0;  /* 16px = 1 process bar */
86
87 char init_path[PATH_MAX] = "/sbin/init";
88 char output_path[PATH_MAX] = "/run/log";
89
90 static struct rlimit rlim;
91
92 static void signal_handler(int sig)
93 {
94         if (sig++)
95                 sig--;
96         exiting = 1;
97 }
98
99
100 int main(int argc, char *argv[])
101 {
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, &samples_len },
116                 { "Bootchart", "Frequency",        config_parse_double, 0, &hz          },
117                 { "Bootchart", "Relative",         config_parse_bool,   0, &relative    },
118                 { "Bootchart", "Filter",           config_parse_bool,   0, &filter      },
119                 { "Bootchart", "Output",           config_parse_path,   0, &output      },
120                 { "Bootchart", "Init",             config_parse_path,   0, &init        },
121                 { "Bootchart", "PlotMemoryUsage",  config_parse_bool,   0, &pss         },
122                 { "Bootchart", "PlotEntropyGraph", config_parse_bool,   0, &entropy     },
123                 { "Bootchart", "ScaleX",           config_parse_double, 0, &scale_x     },
124                 { "Bootchart", "ScaleY",           config_parse_double, 0, &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(init_path, sizeof(init_path), init);
141             if (output != NULL)
142                     strscpy(output_path, sizeof(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                         relative = true;
170                         break;
171                 case 'f':
172                         r = safe_atod(optarg, &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                         filter = false;
179                         break;
180                 case 'C':
181                         show_cmdline = true;
182                         break;
183                 case 'n':
184                         r = safe_atoi(optarg, &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(output_path, sizeof(output_path), optarg);
192                         break;
193                 case 'i':
194                         path_kill_slashes(optarg);
195                         strscpy(init_path, sizeof(init_path), optarg);
196                         break;
197                 case 'p':
198                         pss = true;
199                         break;
200                 case 'x':
201                         r = safe_atod(optarg, &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, &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                         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", hz);
219                         fprintf(stderr, " --samples,   -n N        Stop sampling at [%d] samples\n", samples_len);
220                         fprintf(stderr, " --scale-x,   -x N        Scale the graph horizontally [%f] \n", scale_x);
221                         fprintf(stderr, " --scale-y,   -y N        Scale the graph vertically [%f] \n", 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", output_path);
225                         fprintf(stderr, " --init,      -i [PATH]   Path to init executable [%s]\n", 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 (samples_len > MAXSAMPLES) {
240                 fprintf(stderr, "Error: samples exceeds maximum\n");
241                 exit(EXIT_FAILURE);
242         }
243
244         if (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(init_path, 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 / 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(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", output_path, datestr);
295                         of = fopen(output_file, "w");
296                 }
297
298                 if (sysfd < 0) {
299                         sysfd = open("/sys", O_RDONLY);
300                 }
301
302                 if (!build) {
303                         parse_env_file("/etc/os-release", NEWLINE,
304                                        "PRETTY_NAME", &build,
305                                        NULL);
306                 }
307
308                 /* wait for /proc to become available, discarding samples */
309                 if (!(graph_start > 0.0))
310                         log_uptime();
311                 else
312                         log_sample(samples);
313
314                 sample_stop = gettime_ns();
315
316                 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
317                 timeleft = interval - elapsed;
318
319                 newint_s = (time_t)(timeleft / 1000000000.0);
320                 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
321
322                 /*
323                  * check if we have not consumed our entire timeslice. If we
324                  * do, don't sleep and take a new sample right away.
325                  * we'll lose all the missed samples and overrun our total
326                  * time
327                  */
328                 if ((newint_ns > 0) || (newint_s > 0)) {
329                         req.tv_sec = newint_s;
330                         req.tv_nsec = newint_ns;
331
332                         res = nanosleep(&req, NULL);
333                         if (res) {
334                                 if (errno == EINTR) {
335                                         /* caught signal, probably HUP! */
336                                         break;
337                                 }
338                                 perror("nanosleep()");
339                                 exit (EXIT_FAILURE);
340                         }
341                 } else {
342                         overrun++;
343                         /* calculate how many samples we lost and scrap them */
344                         samples_len = samples_len + ((int)(newint_ns / interval));
345                 }
346
347                 samples++;
348
349                 if (samples > samples_len)
350                         break;
351
352         }
353
354         /* do some cleanup, close fd's */
355         ps = ps_first;
356         while (ps->next_ps) {
357                 ps = ps->next_ps;
358                 if (ps->schedstat)
359                         close(ps->schedstat);
360                 if (ps->sched)
361                         close(ps->sched);
362                 if (ps->smaps)
363                         fclose(ps->smaps);
364         }
365
366         if (!of) {
367                 t = time(NULL);
368                 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
369                 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
370                 of = fopen(output_file, "w");
371         }
372
373         if (!of) {
374                 fprintf(stderr, "opening output file '%s': %m\n", output_file);
375                 exit (EXIT_FAILURE);
376         }
377
378         svg_do(build);
379
380         fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
381         fclose(of);
382
383         closedir(proc);
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 }