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