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