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