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');
69 static int pid_cmdline_strscpy(int procfd, char *buffer, size_t buf_len, int pid) {
70 char filename[PATH_MAX];
71 _cleanup_close_ int fd=-1;
74 sprintf(filename, "%d/cmdline", pid);
75 fd = openat(procfd, filename, O_RDONLY);
79 n = read(fd, buffer, buf_len-1);
82 for (i = 0; i < n; i++)
83 if (buffer[i] == '\0')
90 int log_sample(DIR *proc,
92 struct ps_struct *ps_first,
93 struct list_sample_data **ptr,
97 static int vmstat = -1;
98 static int schedstat = -1;
108 static int e_fd = -1;
113 struct list_sample_data *sampledata;
114 struct ps_sched_struct *ps_prev = NULL;
119 procfd = dirfd(proc);
125 vmstat = openat(procfd, "vmstat", O_RDONLY);
127 return log_error_errno(errno, "Failed to open /proc/vmstat: %m");
130 n = pread(vmstat, buf, sizeof(buf) - 1, 0);
132 vmstat = safe_close(vmstat);
141 if (sscanf(m, "%s %s", key, val) < 2)
143 if (streq(key, "pgpgin"))
144 sampledata->blockstat.bi = atoi(val);
145 if (streq(key, "pgpgout")) {
146 sampledata->blockstat.bo = atoi(val);
156 /* overall CPU utilization */
157 schedstat = openat(procfd, "schedstat", O_RDONLY);
159 return log_error_errno(errno, "Failed to open /proc/schedstat (requires CONFIG_SCHEDSTATS=y in kernel config): %m");
162 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
164 schedstat = safe_close(schedstat);
175 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
178 if (strstr(key, "cpu")) {
179 r = safe_atoi((const char*)(key+3), &c);
180 if (r < 0 || c > MAXCPUS -1)
181 /* Oops, we only have room for MAXCPUS data */
183 sampledata->runtime[c] = atoll(rt);
184 sampledata->waittime[c] = atoll(wt);
197 e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
199 return log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m");
202 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
208 sampledata->entropy_avail = atoi(buf);
212 while ((ent = readdir(proc)) != NULL) {
213 char filename[PATH_MAX];
215 struct ps_struct *ps;
217 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
220 pid = atoi(ent->d_name);
226 while (ps->next_ps) {
232 /* end of our LL? then append a new record */
233 if (ps->pid != pid) {
234 _cleanup_fclose_ FILE *st = NULL;
236 struct ps_struct *parent;
239 ps->next_ps = new0(struct ps_struct, 1);
248 ps->sample = new0(struct ps_sched_struct, 1);
252 ps->sample->sampledata = sampledata;
256 /* mark our first sample */
257 ps->first = ps->last = ps->sample;
258 ps->sample->runtime = atoll(rt);
259 ps->sample->waittime = atoll(wt);
261 /* get name, start time */
263 sprintf(filename, "%d/sched", pid);
264 ps->sched = openat(procfd, filename, O_RDONLY);
269 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
277 if (!sscanf(buf, "%s %*s %*s", key))
280 strscpy(ps->name, sizeof(ps->name), key);
283 if (arg_show_cmdline)
284 pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid);
295 if (!sscanf(m, "%*s %*s %s", t))
298 r = safe_atod(t, &ps->starttime);
302 ps->starttime /= 1000.0;
305 /* if this fails, that's OK */
306 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
307 ps->pid, &ps->cgroup);
310 sprintf(filename, "%d/stat", pid);
311 fd = openat(procfd, filename, O_RDONLY);
314 st = fdopen(fd, "r");
319 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
325 * setup child pointers
327 * these are used to paint the tree coherently later
328 * each parent has a LL of children, and a LL of siblings
331 continue; /* nothing to do for init atm */
333 /* kthreadd has ppid=0, which breaks our tree ordering */
338 while ((parent->next_ps && parent->pid != ps->ppid))
339 parent = parent->next_ps;
341 if (parent->pid != ps->ppid) {
344 parent = ps_first->next_ps;
349 if (!parent->children) {
350 /* it's the first child */
351 parent->children = ps;
353 /* walk all children and append */
354 struct ps_struct *children;
355 children = parent->children;
356 while (children->next)
357 children = children->next;
362 /* else -> found pid, append data in ps */
364 /* below here is all continuous logging parts - we get here on every
368 if (ps->schedstat < 0) {
369 sprintf(filename, "%d/schedstat", pid);
370 ps->schedstat = openat(procfd, filename, O_RDONLY);
371 if (ps->schedstat == -1)
374 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
376 /* clean up our file descriptors - assume that the process exited */
377 close(ps->schedstat);
384 // fclose(ps->smaps);
389 if (!sscanf(buf, "%s %s %*s", rt, wt))
392 ps->sample->next = new0(struct ps_sched_struct, 1);
393 if (!ps->sample->next)
396 ps->sample->next->prev = ps->sample;
397 ps->sample = ps->sample->next;
398 ps->last = ps->sample;
399 ps->sample->runtime = atoll(rt);
400 ps->sample->waittime = atoll(wt);
401 ps->sample->sampledata = sampledata;
402 ps->sample->ps_new = ps;
404 ps_prev->cross = ps->sample;
406 ps_prev = ps->sample;
407 ps->total = (ps->last->runtime - ps->first->runtime)
415 sprintf(filename, "%d/smaps", pid);
416 fd = openat(procfd, filename, O_RDONLY);
419 ps->smaps = fdopen(fd, "r");
424 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
429 /* test to see if we need to skip another field */
431 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
434 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
437 if (buf[392] == 'V') {
448 /* skip one line, this contains the object mapped. */
449 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
452 /* then there's a 28 char 14 line block */
453 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
456 pss_kb = atoi(&buf[61]);
457 ps->sample->pss += pss_kb;
459 /* skip one more line if this is a newer kernel */
461 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
465 if (ps->sample->pss > ps->pss_max)
466 ps->pss_max = ps->sample->pss;
469 /* catch process rename, try to randomize time */
470 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
471 if (((sample - ps->pid) + pid) % (int)(mod) == 0) {
474 /* get name, start time */
476 sprintf(filename, "%d/sched", pid);
477 ps->sched = openat(procfd, filename, O_RDONLY);
481 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
483 /* clean up file descriptors */
487 close(ps->schedstat);
491 // fclose(ps->smaps);
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);