chiark / gitweb /
b2652c8d936fc0145cf274502308de102e5815be
[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 Coproration
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 <sys/types.h>
29 #include <sys/stat.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <dirent.h>
33 #include <fcntl.h>
34 #include <time.h>
35
36 #include "util.h"
37 #include "strxcpyx.h"
38 #include "store.h"
39 #include "bootchart.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 DIR *proc;
48 int procfd = -1;
49
50 double gettime_ns(void) {
51         struct timespec n;
52
53         clock_gettime(CLOCK_MONOTONIC, &n);
54
55         return (n.tv_sec + (n.tv_nsec / 1000000000.0));
56 }
57
58 void log_uptime(void) {
59         FILE _cleanup_fclose_ *f = NULL;
60         char str[32];
61         double uptime;
62
63         f = fopen("/proc/uptime", "r");
64
65         if (!f)
66                 return;
67         if (!fscanf(f, "%s %*s", str))
68                 return;
69
70         uptime = strtod(str, NULL);
71
72         log_start = gettime_ns();
73
74         /* start graph at kernel boot time */
75         if (arg_relative)
76                 graph_start = log_start;
77         else
78                 graph_start = log_start - uptime;
79 }
80
81 static char *bufgetline(char *buf) {
82         char *c;
83
84         if (!buf)
85                 return NULL;
86
87         c = strchr(buf, '\n');
88         if (c)
89                 c++;
90         return c;
91 }
92
93 static int pid_cmdline_strscpy(char *buffer, size_t buf_len, int pid) {
94         char filename[PATH_MAX];
95         int _cleanup_close_ fd=-1;
96         ssize_t n;
97
98         sprintf(filename, "%d/cmdline", pid);
99         fd = openat(procfd, filename, O_RDONLY);
100         if (fd < 0)
101                 return -errno;
102
103         n = read(fd, buffer, buf_len-1);
104         if (n > 0) {
105                 int i;
106                 for (i = 0; i < n; i++)
107                         if (buffer[i] == '\0')
108                                 buffer[i] = ' ';
109                 buffer[n] = '\0';
110         }
111         return 0;
112 }
113
114 void log_sample(int sample) {
115         static int vmstat;
116         static int schedstat;
117         char buf[4096];
118         char key[256];
119         char val[256];
120         char rt[256];
121         char wt[256];
122         char *m;
123         int c;
124         int p;
125         int mod;
126         static int e_fd;
127         ssize_t s;
128         ssize_t n;
129         struct dirent *ent;
130         int fd;
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) {
144                 /* block stuff */
145                 vmstat = openat(procfd, "vmstat", O_RDONLY);
146                 if (vmstat == -1) {
147                         perror("open /proc/vmstat");
148                         exit (EXIT_FAILURE);
149                 }
150         }
151
152         n = pread(vmstat, buf, sizeof(buf) - 1, 0);
153         if (n <= 0) {
154                 close(vmstat);
155                 return;
156         }
157         buf[n] = '\0';
158
159         m = buf;
160         while (m) {
161                 if (sscanf(m, "%s %s", key, val) < 2)
162                         goto vmstat_next;
163                 if (streq(key, "pgpgin"))
164                         blockstat[sample].bi = atoi(val);
165                 if (streq(key, "pgpgout")) {
166                         blockstat[sample].bo = atoi(val);
167                         break;
168                 }
169 vmstat_next:
170                 m = bufgetline(m);
171                 if (!m)
172                         break;
173         }
174
175         if (!schedstat) {
176                 /* overall CPU utilization */
177                 schedstat = openat(procfd, "schedstat", O_RDONLY);
178                 if (schedstat == -1) {
179                         perror("open /proc/schedstat");
180                         exit (EXIT_FAILURE);
181                 }
182         }
183
184         n = pread(schedstat, buf, sizeof(buf) - 1, 0);
185         if (n <= 0) {
186                 close(schedstat);
187                 return;
188         }
189         buf[n] = '\0';
190
191         m = buf;
192         while (m) {
193                 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
194                         goto schedstat_next;
195
196                 if (strstr(key, "cpu")) {
197                         c = atoi((const char*)(key+3));
198                         if (c > MAXCPUS)
199                                 /* Oops, we only have room for MAXCPUS data */
200                                 break;
201                         cpustat[c].sample[sample].runtime = atoll(rt);
202                         cpustat[c].sample[sample].waittime = atoll(wt);
203
204                         if (c == cpus)
205                                 cpus = c + 1;
206                 }
207 schedstat_next:
208                 m = bufgetline(m);
209                 if (!m)
210                         break;
211         }
212
213         if (arg_entropy) {
214                 if (!e_fd) {
215                         e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
216                 }
217
218                 if (e_fd) {
219                         n = pread(e_fd, buf, sizeof(buf) - 1, 0);
220                         if (n > 0) {
221                                 buf[n] = '\0';
222                                 entropy_avail[sample] = atoi(buf);
223                         }
224                 }
225         }
226
227         while ((ent = readdir(proc)) != NULL) {
228                 char filename[PATH_MAX];
229                 int pid;
230                 struct ps_struct *ps;
231
232                 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
233                         continue;
234
235                 pid = atoi(ent->d_name);
236
237                 if (pid >= MAXPIDS)
238                         continue;
239
240                 ps = ps_first;
241                 while (ps->next_ps) {
242                         ps = ps->next_ps;
243                         if (ps->pid == pid)
244                                 break;
245                 }
246
247                 /* end of our LL? then append a new record */
248                 if (ps->pid != pid) {
249                         FILE _cleanup_fclose_ *st = NULL;
250                         char t[32];
251                         struct ps_struct *parent;
252
253                         ps->next_ps = calloc(1, sizeof(struct ps_struct));
254                         if (!ps->next_ps) {
255                                 perror("calloc(ps_struct)");
256                                 exit (EXIT_FAILURE);
257                         }
258                         ps = ps->next_ps;
259                         ps->pid = pid;
260
261                         ps->sample = calloc(arg_samples_len + 1, sizeof(struct ps_sched_struct));
262                         if (!ps->sample) {
263                                 perror("calloc(ps_struct)");
264                                 exit (EXIT_FAILURE);
265                         }
266
267                         pscount++;
268
269                         /* mark our first sample */
270                         ps->first = sample;
271
272                         /* get name, start time */
273                         if (!ps->sched) {
274                                 sprintf(filename, "%d/sched", pid);
275                                 ps->sched = openat(procfd, filename, O_RDONLY);
276                                 if (ps->sched == -1)
277                                         continue;
278                         }
279
280                         s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
281                         if (s <= 0) {
282                                 close(ps->sched);
283                                 continue;
284                         }
285                         buf[s] = '\0';
286
287                         if (!sscanf(buf, "%s %*s %*s", key))
288                                 continue;
289
290                         strscpy(ps->name, sizeof(ps->name), key);
291
292                         /* cmdline */
293                         if (arg_show_cmdline)
294                                 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
295
296                         /* discard line 2 */
297                         m = bufgetline(buf);
298                         if (!m)
299                                 continue;
300
301                         m = bufgetline(m);
302                         if (!m)
303                                 continue;
304
305                         if (!sscanf(m, "%*s %*s %s", t))
306                                 continue;
307
308                         ps->starttime = strtod(t, NULL) / 1000.0;
309
310                         /* ppid */
311                         sprintf(filename, "%d/stat", pid);
312                         fd = openat(procfd, filename, O_RDONLY);
313                         st = fdopen(fd, "r");
314                         if (!st)
315                                 continue;
316                         if (!fscanf(st, "%*s %*s %*s %i", &p)) {
317                                 continue;
318                         }
319                         ps->ppid = p;
320
321                         /*
322                          * setup child pointers
323                          *
324                          * these are used to paint the tree coherently later
325                          * each parent has a LL of children, and a LL of siblings
326                          */
327                         if (pid == 1)
328                                 continue; /* nothing to do for init atm */
329
330                         /* kthreadd has ppid=0, which breaks our tree ordering */
331                         if (ps->ppid == 0)
332                                 ps->ppid = 1;
333
334                         parent = ps_first;
335                         while ((parent->next_ps && parent->pid != ps->ppid))
336                                 parent = parent->next_ps;
337
338                         if ((!parent) || (parent->pid != ps->ppid)) {
339                                 /* orphan */
340                                 ps->ppid = 1;
341                                 parent = ps_first->next_ps;
342                         }
343
344                         ps->parent = parent;
345
346                         if (!parent->children) {
347                                 /* it's the first child */
348                                 parent->children = ps;
349                         } else {
350                                 /* walk all children and append */
351                                 struct ps_struct *children;
352                                 children = parent->children;
353                                 while (children->next)
354                                         children = children->next;
355                                 children->next = ps;
356                         }
357                 }
358
359                 /* else -> found pid, append data in ps */
360
361                 /* below here is all continuous logging parts - we get here on every
362                  * iteration */
363
364                 /* rt, wt */
365                 if (!ps->schedstat) {
366                         sprintf(filename, "%d/schedstat", pid);
367                         ps->schedstat = openat(procfd, filename, O_RDONLY);
368                         if (ps->schedstat == -1)
369                                 continue;
370                 }
371                 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
372                 if (s <= 0) {
373                         /* clean up our file descriptors - assume that the process exited */
374                         close(ps->schedstat);
375                         if (ps->sched)
376                                 close(ps->sched);
377                         //if (ps->smaps)
378                         //        fclose(ps->smaps);
379                         continue;
380                 }
381                 buf[s] = '\0';
382
383                 if (!sscanf(buf, "%s %s %*s", rt, wt))
384                         continue;
385
386                 ps->last = sample;
387                 ps->sample[sample].runtime = atoll(rt);
388                 ps->sample[sample].waittime = atoll(wt);
389
390                 ps->total = (ps->sample[ps->last].runtime
391                                  - ps->sample[ps->first].runtime)
392                                  / 1000000000.0;
393
394                 if (!arg_pss)
395                         goto catch_rename;
396                 /* Pss */
397                 if (!ps->smaps) {
398                         sprintf(filename, "%d/smaps", pid);
399                         fd = openat(procfd, filename, O_RDONLY);
400                         ps->smaps = fdopen(fd, "r");
401                         if (!ps->smaps)
402                                 continue;
403                         setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
404                 } else {
405                         rewind(ps->smaps);
406                 }
407
408                 while (1) {
409                         int pss_kb;
410
411                         /* skip one line, this contains the object mapped */
412                         if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
413                                 break;
414                         /* then there's a 28 char 14 line block */
415                         if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14)
416                                 break;
417
418                         pss_kb = atoi(&buf[61]);
419                         ps->sample[sample].pss += pss_kb;
420                 }
421
422                 if (ps->sample[sample].pss > ps->pss_max)
423                         ps->pss_max = ps->sample[sample].pss;
424
425 catch_rename:
426                 /* catch process rename, try to randomize time */
427                 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
428                 if (((samples - ps->first) + pid) % (int)(mod) == 0) {
429
430                         /* re-fetch name */
431                         /* get name, start time */
432                         if (!ps->sched) {
433                                 sprintf(filename, "%d/sched", pid);
434                                 ps->sched = openat(procfd, filename, O_RDONLY);
435                                 if (ps->sched == -1)
436                                         continue;
437                         }
438                         s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
439                         if (s <= 0) {
440                                 /* clean up file descriptors */
441                                 close(ps->sched);
442                                 if (ps->schedstat)
443                                         close(ps->schedstat);
444                                 //if (ps->smaps)
445                                 //        fclose(ps->smaps);
446                                 continue;
447                         }
448                         buf[s] = '\0';
449
450                         if (!sscanf(buf, "%s %*s %*s", key))
451                                 continue;
452
453                         strscpy(ps->name, sizeof(ps->name), key);
454
455                         /* cmdline */
456                         if (arg_show_cmdline)
457                                 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
458                 }
459         }
460 }