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