chiark / gitweb /
e7c0b4942b5b7825bf592621664f7293ae1bd84c
[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, " --help,      -h          Display this message\n");
229                         fprintf(stderr, "See bootchart.conf for more information.\n");
230                         exit (EXIT_SUCCESS);
231                         break;
232                 default:
233                         break;
234                 }
235         }
236
237         if (samples_len > MAXSAMPLES) {
238                 fprintf(stderr, "Error: samples exceeds maximum\n");
239                 exit(EXIT_FAILURE);
240         }
241
242         if (hz <= 0.0) {
243                 fprintf(stderr, "Error: Frequency needs to be > 0\n");
244                 exit(EXIT_FAILURE);
245         }
246
247         /*
248          * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
249          * fork:
250          * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
251          * - child logs data
252          */
253         if (getpid() == 1) {
254                 if (fork()) {
255                         /* parent */
256                         execl(init_path, init_path, NULL);
257                 }
258         }
259         argv[0][0] = '@';
260
261         /* start with empty ps LL */
262         ps_first = calloc(1, sizeof(struct ps_struct));
263         if (!ps_first) {
264                 perror("calloc(ps_struct)");
265                 exit(EXIT_FAILURE);
266         }
267
268         /* handle TERM/INT nicely */
269         memset(&sig, 0, sizeof(struct sigaction));
270         sig.sa_handler = signal_handler;
271         sigaction(SIGHUP, &sig, NULL);
272
273         interval = (1.0 / hz) * 1000000000.0;
274
275         log_uptime();
276
277         /* main program loop */
278         while (!exiting) {
279                 int res;
280                 double sample_stop;
281                 struct timespec req;
282                 time_t newint_s;
283                 long newint_ns;
284                 double elapsed;
285                 double timeleft;
286
287                 sampletime[samples] = gettime_ns();
288
289                 if (!of && (access(output_path, R_OK|W_OK|X_OK) == 0)) {
290                         t = time(NULL);
291                         strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
292                         snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
293                         of = fopen(output_file, "w");
294                 }
295
296                 if (sysfd < 0) {
297                         sysfd = open("/sys", O_RDONLY);
298                 }
299
300                 if (!build) {
301                         parse_env_file("/etc/os-release", NEWLINE,
302                                        "PRETTY_NAME", &build,
303                                        NULL);
304                 }
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                         samples_len = samples_len + ((int)(newint_ns / interval));
343                 }
344
345                 samples++;
346
347                 if (samples > 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", 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         fclose(of);
380
381         closedir(proc);
382         close(sysfd);
383
384         /* nitpic cleanups */
385         ps = ps_first;
386         while (ps->next_ps) {
387                 struct ps_struct *old = ps;
388                 ps = ps->next_ps;
389                 free(old->sample);
390                 free(old);
391         }
392         free(ps->sample);
393         free(ps);
394
395         /* don't complain when overrun once, happens most commonly on 1st sample */
396         if (overrun > 1)
397                 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);
398
399         return 0;
400 }