1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2009-2013 Intel Corporation
9 Auke Kok <auke-jan.h.kok@intel.com>
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.
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.
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/>.
35 #include "time-util.h"
38 #include "bootchart.h"
39 #include "cgroup-util.h"
42 * Alloc a static 4k buffer for stdio - primarily used to increase
43 * PSS buffering from the default 1k stdin buffer to reduce
46 static char smaps_buf[4096];
49 double gettime_ns(void) {
52 clock_gettime(CLOCK_MONOTONIC, &n);
54 return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
57 static char *bufgetline(char *buf) {
63 c = strchr(buf, '\n');
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;
75 sprintf(filename, "%d/cmdline", pid);
76 fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
80 n = read(fd, buffer, buf_len-1);
83 for (i = 0; i < n; i++)
84 if (buffer[i] == '\0')
92 int log_sample(DIR *proc,
94 struct ps_struct *ps_first,
95 struct list_sample_data **ptr,
99 static int vmstat = -1;
100 static int schedstat = -1;
110 static int e_fd = -1;
115 struct list_sample_data *sampledata;
116 struct ps_sched_struct *ps_prev = NULL;
121 procfd = dirfd(proc);
127 vmstat = openat(procfd, "vmstat", O_RDONLY|O_CLOEXEC);
129 return log_error_errno(errno, "Failed to open /proc/vmstat: %m");
132 n = pread(vmstat, buf, sizeof(buf) - 1, 0);
134 vmstat = safe_close(vmstat);
144 if (sscanf(m, "%s %s", key, val) < 2)
146 if (streq(key, "pgpgin"))
147 sampledata->blockstat.bi = atoi(val);
148 if (streq(key, "pgpgout")) {
149 sampledata->blockstat.bo = atoi(val);
159 /* overall CPU utilization */
160 schedstat = openat(procfd, "schedstat", O_RDONLY|O_CLOEXEC);
162 return log_error_errno(errno, "Failed to open /proc/schedstat (requires CONFIG_SCHEDSTATS=y in kernel config): %m");
165 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
167 schedstat = safe_close(schedstat);
179 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
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 */
187 sampledata->runtime[c] = atoll(rt);
188 sampledata->waittime[c] = atoll(wt);
201 e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY|O_CLOEXEC);
203 return log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m");
206 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
208 e_fd = safe_close(e_fd);
211 sampledata->entropy_avail = atoi(buf);
215 while ((ent = readdir(proc)) != NULL) {
216 char filename[PATH_MAX];
218 struct ps_struct *ps;
220 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
223 pid = atoi(ent->d_name);
229 while (ps->next_ps) {
235 /* end of our LL? then append a new record */
236 if (ps->pid != pid) {
237 _cleanup_fclose_ FILE *st = NULL;
239 struct ps_struct *parent;
242 ps->next_ps = new0(struct ps_struct, 1);
251 ps->sample = new0(struct ps_sched_struct, 1);
255 ps->sample->sampledata = sampledata;
259 /* mark our first sample */
260 ps->first = ps->last = ps->sample;
261 ps->sample->runtime = atoll(rt);
262 ps->sample->waittime = atoll(wt);
264 /* get name, start time */
266 sprintf(filename, "%d/sched", pid);
267 ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
272 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
274 ps->sched = safe_close(ps->sched);
279 if (!sscanf(buf, "%s %*s %*s", key))
282 strscpy(ps->name, sizeof(ps->name), key);
285 if (arg_show_cmdline)
286 pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid);
297 if (!sscanf(m, "%*s %*s %s", t))
300 r = safe_atod(t, &ps->starttime);
304 ps->starttime /= 1000.0;
307 /* if this fails, that's OK */
308 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
309 ps->pid, &ps->cgroup);
312 sprintf(filename, "%d/stat", pid);
313 fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
317 st = fdopen(fd, "re");
323 if (!fscanf(st, "%*s %*s %*s %i", &p))
329 * setup child pointers
331 * these are used to paint the tree coherently later
332 * each parent has a LL of children, and a LL of siblings
335 continue; /* nothing to do for init atm */
337 /* kthreadd has ppid=0, which breaks our tree ordering */
342 while ((parent->next_ps && parent->pid != ps->ppid))
343 parent = parent->next_ps;
345 if (parent->pid != ps->ppid) {
348 parent = ps_first->next_ps;
353 if (!parent->children) {
354 /* it's the first child */
355 parent->children = ps;
357 /* walk all children and append */
358 struct ps_struct *children;
359 children = parent->children;
360 while (children->next)
361 children = children->next;
367 /* else -> found pid, append data in ps */
369 /* below here is all continuous logging parts - we get here on every
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)
380 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
382 /* clean up our file descriptors - assume that the process exited */
383 close(ps->schedstat);
385 ps->sched = safe_close(ps->sched);
391 if (!sscanf(buf, "%s %s %*s", rt, wt))
394 ps->sample->next = new0(struct ps_sched_struct, 1);
395 if (!ps->sample->next)
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;
406 ps_prev->cross = ps->sample;
408 ps_prev = ps->sample;
409 ps->total = (ps->last->runtime - ps->first->runtime)
417 sprintf(filename, "%d/smaps", pid);
418 fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
421 ps->smaps = fdopen(fd, "re");
426 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
431 /* test to see if we need to skip another field */
433 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
436 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
439 if (buf[392] == 'V') {
451 /* skip one line, this contains the object mapped. */
452 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
455 /* then there's a 28 char 14 line block */
456 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
459 pss_kb = atoi(&buf[61]);
460 ps->sample->pss += pss_kb;
462 /* skip one more line if this is a newer kernel */
464 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
469 if (ps->sample->pss > ps->pss_max)
470 ps->pss_max = ps->sample->pss;
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) {
478 /* get name, start time */
480 sprintf(filename, "%d/sched", pid);
481 ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
486 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
488 /* clean up file descriptors */
489 ps->sched = safe_close(ps->sched);
490 ps->schedstat = safe_close(ps->schedstat);
496 if (!sscanf(buf, "%s %*s %*s", key))
499 strscpy(ps->name, sizeof(ps->name), key);
502 if (arg_show_cmdline)
503 pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid);