chiark / gitweb /
man: move bootchart README to manpage, docbooksify
[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                         safe_atod(optarg, &hz);
171                         break;
172                 case 'F':
173                         filter = false;
174                         break;
175                 case 'n':
176                         safe_atoi(optarg, &len);
177                         break;
178                 case 'o':
179                         path_kill_slashes(optarg);
180                         strscpy(output_path, sizeof(output_path), optarg);
181                         break;
182                 case 'i':
183                         path_kill_slashes(optarg);
184                         strscpy(init_path, sizeof(init_path), optarg);
185                         break;
186                 case 'p':
187                         pss = true;
188                         break;
189                 case 'x':
190                         safe_atod(optarg, &scale_x);
191                         break;
192                 case 'y':
193                         safe_atod(optarg, &scale_y);
194                         break;
195                 case 'e':
196                         entropy = true;
197                         break;
198                 case 'h':
199                         fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
200                         fprintf(stderr, " --rel,     -r            Record time relative to recording\n");
201                         fprintf(stderr, " --freq,    -f N          Sample frequency [%f]\n", hz);
202                         fprintf(stderr, " --samples, -n N          Stop sampling at [%d] samples\n", len);
203                         fprintf(stderr, " --scale-x, -x N          Scale the graph horizontally [%f] \n", scale_x);
204                         fprintf(stderr, " --scale-y, -y N          Scale the graph vertically [%f] \n", scale_y);
205                         fprintf(stderr, " --pss,     -p            Enable PSS graph (CPU intensive)\n");
206                         fprintf(stderr, " --entropy, -e            Enable the entropy_avail graph\n");
207                         fprintf(stderr, " --output,  -o [PATH]     Path to output files [%s]\n", output_path);
208                         fprintf(stderr, " --init,    -i [PATH]     Path to init executable [%s]\n", init_path);
209                         fprintf(stderr, " --filter,  -F            Disable filtering of processes from the graph\n");
210                         fprintf(stderr, "                          that are of less importance or short-lived\n");
211                         fprintf(stderr, " --help,    -h            Display this message\n");
212                         fprintf(stderr, "See the installed README and bootchartd.conf.example for more information.\n");
213                         exit (EXIT_SUCCESS);
214                         break;
215                 default:
216                         break;
217                 }
218         }
219
220         if (len > MAXSAMPLES) {
221                 fprintf(stderr, "Error: samples exceeds maximum\n");
222                 exit(EXIT_FAILURE);
223         }
224
225         if (hz <= 0.0) {
226                 fprintf(stderr, "Error: Frequency needs to be > 0\n");
227                 exit(EXIT_FAILURE);
228         }
229
230         /*
231          * If the kernel executed us through init=/sbin/bootchartd, then
232          * fork:
233          * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
234          * - child logs data
235          */
236         if (getpid() == 1) {
237                 if (fork()) {
238                         /* parent */
239                         execl(init_path, init_path, NULL);
240                 }
241         }
242         argv[0][0] = '@';
243
244         /* start with empty ps LL */
245         ps_first = calloc(1, sizeof(struct ps_struct));
246         if (!ps_first) {
247                 perror("calloc(ps_struct)");
248                 exit(EXIT_FAILURE);
249         }
250
251         /* handle TERM/INT nicely */
252         memset(&sig, 0, sizeof(struct sigaction));
253         sig.sa_handler = signal_handler;
254         sigaction(SIGHUP, &sig, NULL);
255
256         interval = (1.0 / hz) * 1000000000.0;
257
258         log_uptime();
259
260         /* main program loop */
261         while (!exiting) {
262                 int res;
263                 double sample_stop;
264                 struct timespec req;
265                 time_t newint_s;
266                 long newint_ns;
267                 double elapsed;
268                 double timeleft;
269
270                 sampletime[samples] = gettime_ns();
271
272                 if (!of && (access(output_path, R_OK|W_OK|X_OK) == 0)) {
273                         t = time(NULL);
274                         strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
275                         snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
276                         of = fopen(output_file, "w");
277                 }
278
279                 if (sysfd < 0) {
280                         sysfd = open("/sys", O_RDONLY);
281                 }
282
283                 if (!build) {
284                         parse_env_file("/etc/os-release", NEWLINE,
285                                        "PRETTY_NAME", &build,
286                                        NULL);
287                 }
288
289                 /* wait for /proc to become available, discarding samples */
290                 if (!(graph_start > 0.0))
291                         log_uptime();
292                 else
293                         log_sample(samples);
294
295                 sample_stop = gettime_ns();
296
297                 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
298                 timeleft = interval - elapsed;
299
300                 newint_s = (time_t)(timeleft / 1000000000.0);
301                 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
302
303                 /*
304                  * check if we have not consumed our entire timeslice. If we
305                  * do, don't sleep and take a new sample right away.
306                  * we'll lose all the missed samples and overrun our total
307                  * time
308                  */
309                 if ((newint_ns > 0) || (newint_s > 0)) {
310                         req.tv_sec = newint_s;
311                         req.tv_nsec = newint_ns;
312
313                         res = nanosleep(&req, NULL);
314                         if (res) {
315                                 if (errno == EINTR) {
316                                         /* caught signal, probably HUP! */
317                                         break;
318                                 }
319                                 perror("nanosleep()");
320                                 exit (EXIT_FAILURE);
321                         }
322                 } else {
323                         overrun++;
324                         /* calculate how many samples we lost and scrap them */
325                         len = len + ((int)(newint_ns / interval));
326                 }
327
328                 samples++;
329
330                 if (samples > len)
331                         break;
332
333         }
334
335         /* do some cleanup, close fd's */
336         ps = ps_first;
337         while (ps->next_ps) {
338                 ps = ps->next_ps;
339                 if (ps->schedstat)
340                         close(ps->schedstat);
341                 if (ps->sched)
342                         close(ps->sched);
343                 if (ps->smaps)
344                         fclose(ps->smaps);
345         }
346
347         if (!of) {
348                 t = time(NULL);
349                 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
350                 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
351                 of = fopen(output_file, "w");
352         }
353
354         if (!of) {
355                 perror("open output_file");
356                 exit (EXIT_FAILURE);
357         }
358
359         svg_do(build);
360
361         fprintf(stderr, "bootchartd: Wrote %s\n", output_file);
362         fclose(of);
363
364         closedir(proc);
365         close(sysfd);
366
367         /* nitpic cleanups */
368         ps = ps_first;
369         while (ps->next_ps) {
370                 struct ps_struct *old = ps;
371                 ps = ps->next_ps;
372                 free(old->sample);
373                 free(old);
374         }
375         free(ps->sample);
376         free(ps);
377
378         /* don't complain when overrun once, happens most commonly on 1st sample */
379         if (overrun > 1)
380                 fprintf(stderr, "bootchartd: Warning: sample time overrun %i times\n", overrun);
381
382         return 0;
383 }