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