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