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