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