chiark / gitweb /
systemd-bootchart: Repair Entropy Graph
[elogind.git] / src / bootchart / store.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright (C) 2009-2013 Intel Corporation
7
8   Authors:
9     Auke Kok <auke-jan.h.kok@intel.com>
10
11   systemd is free software; you can redistribute it and/or modify it
12   under the terms of the GNU Lesser General Public License as published by
13   the Free Software Foundation; either version 2.1 of the License, or
14   (at your option) any later version.
15
16   systemd is distributed in the hope that it will be useful, but
17   WITHOUT ANY WARRANTY; without even the implied warranty of
18   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19   Lesser General Public License for more details.
20
21   You should have received a copy of the GNU Lesser General Public License
22   along with systemd; If not, see <http://www.gnu.org/licenses/>.
23  ***/
24
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <limits.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <dirent.h>
31 #include <fcntl.h>
32 #include <time.h>
33
34 #include "util.h"
35 #include "time-util.h"
36 #include "strxcpyx.h"
37 #include "store.h"
38 #include "bootchart.h"
39 #include "cgroup-util.h"
40
41 /*
42  * Alloc a static 4k buffer for stdio - primarily used to increase
43  * PSS buffering from the default 1k stdin buffer to reduce
44  * read() overhead.
45  */
46 static char smaps_buf[4096];
47 static int skip = 0;
48 DIR *proc;
49 int procfd = -1;
50
51 double gettime_ns(void) {
52         struct timespec n;
53
54         clock_gettime(CLOCK_MONOTONIC, &n);
55
56         return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
57 }
58
59 static double gettime_up(void) {
60         struct timespec n;
61
62         clock_gettime(CLOCK_BOOTTIME, &n);
63         return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
64 }
65
66 void log_uptime(void) {
67         if (arg_relative)
68                 graph_start = log_start = gettime_ns();
69         else {
70                 double uptime = gettime_up();
71
72                 log_start = gettime_ns();
73                 graph_start = log_start - uptime;
74         }
75 }
76
77 static char *bufgetline(char *buf) {
78         char *c;
79
80         if (!buf)
81                 return NULL;
82
83         c = strchr(buf, '\n');
84         if (c)
85                 c++;
86         return c;
87 }
88
89 static int pid_cmdline_strscpy(char *buffer, size_t buf_len, int pid) {
90         char filename[PATH_MAX];
91         _cleanup_close_ int fd=-1;
92         ssize_t n;
93
94         sprintf(filename, "%d/cmdline", pid);
95         fd = openat(procfd, filename, O_RDONLY);
96         if (fd < 0)
97                 return -errno;
98
99         n = read(fd, buffer, buf_len-1);
100         if (n > 0) {
101                 int i;
102                 for (i = 0; i < n; i++)
103                         if (buffer[i] == '\0')
104                                 buffer[i] = ' ';
105                 buffer[n] = '\0';
106         }
107         return 0;
108 }
109
110 void log_sample(int sample, struct list_sample_data **ptr) {
111         static int vmstat = -1;
112         static int schedstat = -1;
113         char buf[4096];
114         char key[256];
115         char val[256];
116         char rt[256];
117         char wt[256];
118         char *m;
119         int c;
120         int p;
121         int mod;
122         static int e_fd = -1;
123         ssize_t s;
124         ssize_t n;
125         struct dirent *ent;
126         int fd;
127         struct list_sample_data *sampledata;
128         struct ps_sched_struct *ps_prev = NULL;
129
130         sampledata = *ptr;
131
132         /* all the per-process stuff goes here */
133         if (!proc) {
134                 /* find all processes */
135                 proc = opendir("/proc");
136                 if (!proc)
137                         return;
138                 procfd = dirfd(proc);
139         } else {
140                 rewinddir(proc);
141         }
142
143         if (vmstat < 0) {
144                 /* block stuff */
145                 vmstat = openat(procfd, "vmstat", O_RDONLY);
146                 if (vmstat == -1) {
147                         log_error_errno(errno, "Failed to open /proc/vmstat: %m");
148                         exit(EXIT_FAILURE);
149                 }
150         }
151
152         n = pread(vmstat, buf, sizeof(buf) - 1, 0);
153         if (n <= 0) {
154                 close(vmstat);
155                 vmstat = -1;
156                 return;
157         }
158         buf[n] = '\0';
159
160         m = buf;
161         while (m) {
162                 if (sscanf(m, "%s %s", key, val) < 2)
163                         goto vmstat_next;
164                 if (streq(key, "pgpgin"))
165                         sampledata->blockstat.bi = atoi(val);
166                 if (streq(key, "pgpgout")) {
167                         sampledata->blockstat.bo = atoi(val);
168                         break;
169                 }
170 vmstat_next:
171                 m = bufgetline(m);
172                 if (!m)
173                         break;
174         }
175
176         if (schedstat < 0) {
177                 /* overall CPU utilization */
178                 schedstat = openat(procfd, "schedstat", O_RDONLY);
179                 if (schedstat == -1) {
180                         log_error_errno(errno, "Failed to open /proc/schedstat (requires CONFIG_SCHEDSTATS=y in kernel config): %m");
181                         exit(EXIT_FAILURE);
182                 }
183         }
184
185         n = pread(schedstat, buf, sizeof(buf) - 1, 0);
186         if (n <= 0) {
187                 close(schedstat);
188                 schedstat = -1;
189                 return;
190         }
191         buf[n] = '\0';
192
193         m = buf;
194         while (m) {
195                 int r;
196
197                 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
198                         goto schedstat_next;
199
200                 if (strstr(key, "cpu")) {
201                         r = safe_atoi((const char*)(key+3), &c);
202                         if (r < 0 || c > MAXCPUS -1)
203                                 /* Oops, we only have room for MAXCPUS data */
204                                 break;
205                         sampledata->runtime[c] = atoll(rt);
206                         sampledata->waittime[c] = atoll(wt);
207
208                         if (c == cpus)
209                                 cpus = c + 1;
210                 }
211 schedstat_next:
212                 m = bufgetline(m);
213                 if (!m)
214                         break;
215         }
216
217         if (arg_entropy) {
218                 if (e_fd < 0) {
219                         e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
220                         if (e_fd == -1) {
221                                 log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m");
222                                 exit(EXIT_FAILURE);
223                         }
224                 }
225
226                 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
227                 if (n <= 0) {
228                         close(e_fd);
229                         e_fd = -1;
230                 } else {
231                         buf[n] = '\0';
232                         sampledata->entropy_avail = atoi(buf);
233                 }
234         }
235
236         while ((ent = readdir(proc)) != NULL) {
237                 char filename[PATH_MAX];
238                 int pid;
239                 struct ps_struct *ps;
240
241                 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
242                         continue;
243
244                 pid = atoi(ent->d_name);
245
246                 if (pid >= MAXPIDS)
247                         continue;
248
249                 ps = ps_first;
250                 while (ps->next_ps) {
251                         ps = ps->next_ps;
252                         if (ps->pid == pid)
253                                 break;
254                 }
255
256                 /* end of our LL? then append a new record */
257                 if (ps->pid != pid) {
258                         _cleanup_fclose_ FILE *st = NULL;
259                         char t[32];
260                         struct ps_struct *parent;
261                         int r;
262
263                         ps->next_ps = new0(struct ps_struct, 1);
264                         if (!ps->next_ps) {
265                                 log_oom();
266                                 exit (EXIT_FAILURE);
267                         }
268                         ps = ps->next_ps;
269                         ps->pid = pid;
270                         ps->sched = -1;
271                         ps->schedstat = -1;
272
273                         ps->sample = new0(struct ps_sched_struct, 1);
274                         if (!ps->sample) {
275                                 log_oom();
276                                 exit (EXIT_FAILURE);
277                         }
278                         ps->sample->sampledata = sampledata;
279
280                         pscount++;
281
282                         /* mark our first sample */
283                         ps->first = ps->last = ps->sample;
284                         ps->sample->runtime = atoll(rt);
285                         ps->sample->waittime = atoll(wt);
286
287                         /* get name, start time */
288                         if (ps->sched < 0) {
289                                 sprintf(filename, "%d/sched", pid);
290                                 ps->sched = openat(procfd, filename, O_RDONLY);
291                                 if (ps->sched == -1)
292                                         continue;
293                         }
294
295                         s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
296                         if (s <= 0) {
297                                 close(ps->sched);
298                                 ps->sched = -1;
299                                 continue;
300                         }
301                         buf[s] = '\0';
302
303                         if (!sscanf(buf, "%s %*s %*s", key))
304                                 continue;
305
306                         strscpy(ps->name, sizeof(ps->name), key);
307
308                         /* cmdline */
309                         if (arg_show_cmdline)
310                                 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
311
312                         /* discard line 2 */
313                         m = bufgetline(buf);
314                         if (!m)
315                                 continue;
316
317                         m = bufgetline(m);
318                         if (!m)
319                                 continue;
320
321                         if (!sscanf(m, "%*s %*s %s", t))
322                                 continue;
323
324                         r = safe_atod(t, &ps->starttime);
325                         if (r < 0)
326                                 continue;
327
328                         ps->starttime /= 1000.0;
329
330                         if (arg_show_cgroup)
331                                 /* if this fails, that's OK */
332                                 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
333                                                 ps->pid, &ps->cgroup);
334
335                         /* ppid */
336                         sprintf(filename, "%d/stat", pid);
337                         fd = openat(procfd, filename, O_RDONLY);
338                         if (fd == -1)
339                                 continue;
340                         st = fdopen(fd, "r");
341                         if (!st) {
342                                 close(fd);
343                                 continue;
344                         }
345                         if (!fscanf(st, "%*s %*s %*s %i", &p)) {
346                                 continue;
347                         }
348                         ps->ppid = p;
349
350                         /*
351                          * setup child pointers
352                          *
353                          * these are used to paint the tree coherently later
354                          * each parent has a LL of children, and a LL of siblings
355                          */
356                         if (pid == 1)
357                                 continue; /* nothing to do for init atm */
358
359                         /* kthreadd has ppid=0, which breaks our tree ordering */
360                         if (ps->ppid == 0)
361                                 ps->ppid = 1;
362
363                         parent = ps_first;
364                         while ((parent->next_ps && parent->pid != ps->ppid))
365                                 parent = parent->next_ps;
366
367                         if (parent->pid != ps->ppid) {
368                                 /* orphan */
369                                 ps->ppid = 1;
370                                 parent = ps_first->next_ps;
371                         }
372
373                         ps->parent = parent;
374
375                         if (!parent->children) {
376                                 /* it's the first child */
377                                 parent->children = ps;
378                         } else {
379                                 /* walk all children and append */
380                                 struct ps_struct *children;
381                                 children = parent->children;
382                                 while (children->next)
383                                         children = children->next;
384                                 children->next = ps;
385                         }
386                 }
387
388                 /* else -> found pid, append data in ps */
389
390                 /* below here is all continuous logging parts - we get here on every
391                  * iteration */
392
393                 /* rt, wt */
394                 if (ps->schedstat < 0) {
395                         sprintf(filename, "%d/schedstat", pid);
396                         ps->schedstat = openat(procfd, filename, O_RDONLY);
397                         if (ps->schedstat == -1)
398                                 continue;
399                 }
400                 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
401                 if (s <= 0) {
402                         /* clean up our file descriptors - assume that the process exited */
403                         close(ps->schedstat);
404                         ps->schedstat = -1;
405                         if (ps->sched) {
406                                 close(ps->sched);
407                                 ps->sched = -1;
408                         }
409                         //if (ps->smaps)
410                         //        fclose(ps->smaps);
411                         continue;
412                 }
413                 buf[s] = '\0';
414
415                 if (!sscanf(buf, "%s %s %*s", rt, wt))
416                         continue;
417
418                 ps->sample->next = new0(struct ps_sched_struct, 1);
419                 if (!ps->sample->next) {
420                         log_oom();
421                         exit(EXIT_FAILURE);
422                 }
423                 ps->sample->next->prev = ps->sample;
424                 ps->sample = ps->sample->next;
425                 ps->last = ps->sample;
426                 ps->sample->runtime = atoll(rt);
427                 ps->sample->waittime = atoll(wt);
428                 ps->sample->sampledata = sampledata;
429                 ps->sample->ps_new = ps;
430                 if (ps_prev) {
431                         ps_prev->cross = ps->sample;
432                 }
433                 ps_prev = ps->sample;
434                 ps->total = (ps->last->runtime - ps->first->runtime)
435                             / 1000000000.0;
436
437                 if (!arg_pss)
438                         goto catch_rename;
439
440                 /* Pss */
441                 if (!ps->smaps) {
442                         sprintf(filename, "%d/smaps", pid);
443                         fd = openat(procfd, filename, O_RDONLY);
444                         if (fd == -1)
445                                 continue;
446                         ps->smaps = fdopen(fd, "r");
447                         if (!ps->smaps) {
448                                 close(fd);
449                                 continue;
450                         }
451                         setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
452                 }
453                 else {
454                         rewind(ps->smaps);
455                 }
456                 /* test to see if we need to skip another field */
457                 if (skip == 0) {
458                         if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
459                                 continue;
460                         }
461                         if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
462                                 continue;
463                         }
464                         if (buf[392] == 'V') {
465                                 skip = 2;
466                         }
467                         else {
468                                 skip = 1;
469                         }
470                         rewind(ps->smaps);
471                 }
472                 while (1) {
473                         int pss_kb;
474
475                         /* skip one line, this contains the object mapped. */
476                         if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
477                                 break;
478                         }
479                         /* then there's a 28 char 14 line block */
480                         if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
481                                 break;
482                         }
483                         pss_kb = atoi(&buf[61]);
484                         ps->sample->pss += pss_kb;
485
486                         /* skip one more line if this is a newer kernel */
487                         if (skip == 2) {
488                                if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
489                                        break;
490                         }
491                 }
492                 if (ps->sample->pss > ps->pss_max)
493                         ps->pss_max = ps->sample->pss;
494
495 catch_rename:
496                 /* catch process rename, try to randomize time */
497                 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
498                 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
499
500                         /* re-fetch name */
501                         /* get name, start time */
502                         if (!ps->sched) {
503                                 sprintf(filename, "%d/sched", pid);
504                                 ps->sched = openat(procfd, filename, O_RDONLY);
505                                 if (ps->sched == -1)
506                                         continue;
507                         }
508                         s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
509                         if (s <= 0) {
510                                 /* clean up file descriptors */
511                                 close(ps->sched);
512                                 ps->sched = -1;
513                                 if (ps->schedstat) {
514                                         close(ps->schedstat);
515                                         ps->schedstat = -1;
516                                 }
517                                 //if (ps->smaps)
518                                 //        fclose(ps->smaps);
519                                 continue;
520                         }
521                         buf[s] = '\0';
522
523                         if (!sscanf(buf, "%s %*s %*s", key))
524                                 continue;
525
526                         strscpy(ps->name, sizeof(ps->name), key);
527
528                         /* cmdline */
529                         if (arg_show_cmdline)
530                                 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
531                 }
532         }
533 }