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