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