chiark / gitweb /
bootchart: use conf-parser & CamelCase names in .conf
[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 #include <sys/time.h>
24 #include <sys/types.h>
25 #include <sys/resource.h>
26 #include <sys/stat.h>
27 #include <stdio.h>
28 #include <signal.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <getopt.h>
34 #include <limits.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <stdbool.h>
38
39
40 #include "bootchart.h"
41 #include "util.h"
42 #include "fileio.h"
43 #include "macro.h"
44 #include "conf-parser.h"
45 #include "strxcpyx.h"
46
47 double graph_start;
48 double log_start;
49 double sampletime[MAXSAMPLES];
50 struct ps_struct *ps_first;
51 struct block_stat_struct blockstat[MAXSAMPLES];
52 int entropy_avail[MAXSAMPLES];
53 struct cpu_stat_struct cpustat[MAXCPUS];
54 int pscount;
55 int cpus;
56 double interval;
57 FILE *of = NULL;
58 int overrun = 0;
59 static int exiting = 0;
60 int sysfd=-1;
61
62 /* graph defaults */
63 bool entropy = false;
64 bool initcall = true;
65 bool relative = false;
66 bool filter = true;
67 bool pss = false;
68 int samples;
69 int len = 500; /* we record len+1 (1 start sample) */
70 double hz = 25.0;   /* 20 seconds log time */
71 double scale_x = 100.0; /* 100px = 1sec */
72 double scale_y = 20.0;  /* 16px = 1 process bar */
73
74 char init_path[PATH_MAX] = "/sbin/init";
75 char output_path[PATH_MAX] = "/run/log";
76
77 static struct rlimit rlim;
78
79 static void signal_handler(int sig)
80 {
81         if (sig++)
82                 sig--;
83         exiting = 1;
84 }
85
86
87 int main(int argc, char *argv[])
88 {
89         _cleanup_free_ char *build = NULL;
90         struct sigaction sig;
91         struct ps_struct *ps;
92         char output_file[PATH_MAX];
93         char datestr[200];
94         time_t t = 0;
95         const char *fn;
96         _cleanup_fclose_ FILE *f;
97         int gind;
98         int i, r;
99         char *init = NULL, *output = NULL;
100
101         const ConfigTableItem items[] = {
102                 { "Bootchart", "Samples",          config_parse_int,    0, &len      },
103                 { "Bootchart", "Frequency",        config_parse_double, 0, &hz       },
104                 { "Bootchart", "Relative",         config_parse_bool,   0, &relative },
105                 { "Bootchart", "Filter",           config_parse_bool,   0, &filter   },
106                 { "Bootchart", "Output",           config_parse_path,   0, &output   },
107                 { "Bootchart", "Init",             config_parse_path,   0, &init     },
108                 { "Bootchart", "PlotMemoryUsage",  config_parse_bool,   0, &pss      },
109                 { "Bootchart", "PlotEntropyGraph", config_parse_bool,   0, &entropy  },
110                 { "Bootchart", "ScaleX",           config_parse_double, 0, &scale_x  },
111                 { "Bootchart", "ScaleY",           config_parse_double, 0, &scale_y  },
112                 { NULL, NULL, NULL, 0, NULL }
113         };
114
115         rlim.rlim_cur = 4096;
116         rlim.rlim_max = 4096;
117         (void) setrlimit(RLIMIT_NOFILE, &rlim);
118
119         fn = "/etc/systemd/bootchart.conf";
120         f = fopen(fn, "re");
121         if (f) {
122             r = config_parse(fn, f, NULL, config_item_table_lookup, (void*) items, true, NULL);
123             if (r < 0)
124                     log_warning("Failed to parse configuration file: %s", strerror(-r));
125
126             if (init != NULL)
127                     strscpy(init_path, sizeof(init_path), init);
128             if (output != NULL)
129                     strscpy(output_path, sizeof(output_path), output);
130         }
131
132         while (1) {
133                 static struct option opts[] = {
134                         {"rel", 0, NULL, 'r'},
135                         {"freq", 1, NULL, 'f'},
136                         {"samples", 1, NULL, 'n'},
137                         {"pss", 0, NULL, 'p'},
138                         {"output", 1, NULL, 'o'},
139                         {"init", 1, NULL, 'i'},
140                         {"filter", 0, NULL, 'F'},
141                         {"help", 0, NULL, 'h'},
142                         {"scale-x", 1, NULL, 'x'},
143                         {"scale-y", 1, NULL, 'y'},
144                         {"entropy", 0, NULL, 'e'},
145                         {NULL, 0, NULL, 0}
146                 };
147
148                 gind = 0;
149
150                 i = getopt_long(argc, argv, "erpf:n:o:i:Fhx:y:", opts, &gind);
151                 if (i == -1)
152                         break;
153                 switch (i) {
154                 case 'r':
155                         relative = true;
156                         break;
157                 case 'f':
158                         hz = atof(optarg);
159                         break;
160                 case 'F':
161                         filter = false;
162                         break;
163                 case 'n':
164                         len = atoi(optarg);
165                         break;
166                 case 'o':
167                         strncpy(output_path, optarg, PATH_MAX - 1);
168                         break;
169                 case 'i':
170                         strncpy(init_path, optarg, PATH_MAX - 1);
171                         break;
172                 case 'p':
173                         pss = true;
174                         break;
175                 case 'x':
176                         scale_x = atof(optarg);
177                         break;
178                 case 'y':
179                         scale_y = atof(optarg);
180                         break;
181                 case 'e':
182                         entropy = true;
183                         break;
184                 case 'h':
185                         fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
186                         fprintf(stderr, " --rel,     -r            Record time relative to recording\n");
187                         fprintf(stderr, " --freq,    -f N          Sample frequency [%f]\n", hz);
188                         fprintf(stderr, " --samples, -n N          Stop sampling at [%d] samples\n", len);
189                         fprintf(stderr, " --scale-x, -x N          Scale the graph horizontally [%f] \n", scale_x);
190                         fprintf(stderr, " --scale-y, -y N          Scale the graph vertically [%f] \n", scale_y);
191                         fprintf(stderr, " --pss,     -p            Enable PSS graph (CPU intensive)\n");
192                         fprintf(stderr, " --entropy, -e            Enable the entropy_avail graph\n");
193                         fprintf(stderr, " --output,  -o [PATH]     Path to output files [%s]\n", output_path);
194                         fprintf(stderr, " --init,    -i [PATH]     Path to init executable [%s]\n", init_path);
195                         fprintf(stderr, " --filter,  -F            Disable filtering of processes from the graph\n");
196                         fprintf(stderr, "                          that are of less importance or short-lived\n");
197                         fprintf(stderr, " --help,    -h            Display this message\n");
198                         fprintf(stderr, "See the installed README and bootchartd.conf.example for more information.\n");
199                         exit (EXIT_SUCCESS);
200                         break;
201                 default:
202                         break;
203                 }
204         }
205
206         if (len > MAXSAMPLES) {
207                 fprintf(stderr, "Error: samples exceeds maximum\n");
208                 exit(EXIT_FAILURE);
209         }
210
211         if (hz <= 0.0) {
212                 fprintf(stderr, "Error: Frequency needs to be > 0\n");
213                 exit(EXIT_FAILURE);
214         }
215
216         /*
217          * If the kernel executed us through init=/sbin/bootchartd, then
218          * fork:
219          * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
220          * - child logs data
221          */
222         if (getpid() == 1) {
223                 if (fork()) {
224                         /* parent */
225                         execl(init_path, init_path, NULL);
226                 }
227         }
228         argv[0][0] = '@';
229
230         /* start with empty ps LL */
231         ps_first = calloc(1, sizeof(struct ps_struct));
232         if (!ps_first) {
233                 perror("calloc(ps_struct)");
234                 exit(EXIT_FAILURE);
235         }
236
237         /* handle TERM/INT nicely */
238         memset(&sig, 0, sizeof(struct sigaction));
239         sig.sa_handler = signal_handler;
240         sigaction(SIGHUP, &sig, NULL);
241
242         interval = (1.0 / hz) * 1000000000.0;
243
244         log_uptime();
245
246         /* main program loop */
247         while (!exiting) {
248                 int res;
249                 double sample_stop;
250                 struct timespec req;
251                 time_t newint_s;
252                 long newint_ns;
253                 double elapsed;
254                 double timeleft;
255
256                 sampletime[samples] = gettime_ns();
257
258                 if (!of && (access(output_path, R_OK|W_OK|X_OK) == 0)) {
259                         t = time(NULL);
260                         strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
261                         snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
262                         of = fopen(output_file, "w");
263                 }
264
265                 if (sysfd < 0) {
266                         sysfd = open("/sys", O_RDONLY);
267                 }
268
269                 if (!build) {
270                         parse_env_file("/etc/os-release", NEWLINE,
271                                        "PRETTY_NAME", &build,
272                                        NULL);
273                 }
274
275                 /* wait for /proc to become available, discarding samples */
276                 if (!(graph_start > 0.0))
277                         log_uptime();
278                 else
279                         log_sample(samples);
280
281                 sample_stop = gettime_ns();
282
283                 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
284                 timeleft = interval - elapsed;
285
286                 newint_s = (time_t)(timeleft / 1000000000.0);
287                 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
288
289                 /*
290                  * check if we have not consumed our entire timeslice. If we
291                  * do, don't sleep and take a new sample right away.
292                  * we'll lose all the missed samples and overrun our total
293                  * time
294                  */
295                 if ((newint_ns > 0) || (newint_s > 0)) {
296                         req.tv_sec = newint_s;
297                         req.tv_nsec = newint_ns;
298
299                         res = nanosleep(&req, NULL);
300                         if (res) {
301                                 if (errno == EINTR) {
302                                         /* caught signal, probably HUP! */
303                                         break;
304                                 }
305                                 perror("nanosleep()");
306                                 exit (EXIT_FAILURE);
307                         }
308                 } else {
309                         overrun++;
310                         /* calculate how many samples we lost and scrap them */
311                         len = len + ((int)(newint_ns / interval));
312                 }
313
314                 samples++;
315
316                 if (samples > len)
317                         break;
318
319         }
320
321         /* do some cleanup, close fd's */
322         ps = ps_first;
323         while (ps->next_ps) {
324                 ps = ps->next_ps;
325                 if (ps->schedstat)
326                         close(ps->schedstat);
327                 if (ps->sched)
328                         close(ps->sched);
329                 if (ps->smaps)
330                         fclose(ps->smaps);
331         }
332
333         if (!of) {
334                 t = time(NULL);
335                 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
336                 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", output_path, datestr);
337                 of = fopen(output_file, "w");
338         }
339
340         if (!of) {
341                 perror("open output_file");
342                 exit (EXIT_FAILURE);
343         }
344
345         svg_do(build);
346
347         fprintf(stderr, "bootchartd: Wrote %s\n", output_file);
348         fclose(of);
349
350         closedir(proc);
351         close(sysfd);
352
353         /* nitpic cleanups */
354         ps = ps_first;
355         while (ps->next_ps) {
356                 struct ps_struct *old = ps;
357                 ps = ps->next_ps;
358                 free(old->sample);
359                 free(old);
360         }
361         free(ps->sample);
362         free(ps);
363
364         /* don't complain when overrun once, happens most commonly on 1st sample */
365         if (overrun > 1)
366                 fprintf(stderr, "bootchartd: Warning: sample time overrun %i times\n", overrun);
367
368         return 0;
369 }