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];
51 double gettime_ns(void) {
54 clock_gettime(CLOCK_MONOTONIC, &n);
56 return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
59 static double gettime_up(void) {
62 clock_gettime(CLOCK_BOOTTIME, &n);
63 return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
66 void log_uptime(void) {
68 graph_start = log_start = gettime_ns();
70 double uptime = gettime_up();
72 log_start = gettime_ns();
73 graph_start = log_start - uptime;
77 static char *bufgetline(char *buf) {
83 c = strchr(buf, '\n');
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;
94 sprintf(filename, "%d/cmdline", pid);
95 fd = openat(procfd, filename, O_RDONLY);
99 n = read(fd, buffer, buf_len-1);
102 for (i = 0; i < n; i++)
103 if (buffer[i] == '\0')
110 void log_sample(int sample, struct list_sample_data **ptr) {
111 static int vmstat = -1;
112 static int schedstat = -1;
127 struct list_sample_data *sampledata;
128 struct ps_sched_struct *ps_prev = NULL;
132 /* all the per-process stuff goes here */
134 /* find all processes */
135 proc = opendir("/proc");
138 procfd = dirfd(proc);
145 vmstat = openat(procfd, "vmstat", O_RDONLY);
147 log_error_errno(errno, "Failed to open /proc/vmstat: %m");
152 n = pread(vmstat, buf, sizeof(buf) - 1, 0);
162 if (sscanf(m, "%s %s", key, val) < 2)
164 if (streq(key, "pgpgin"))
165 sampledata->blockstat.bi = atoi(val);
166 if (streq(key, "pgpgout")) {
167 sampledata->blockstat.bo = atoi(val);
177 /* overall CPU utilization */
178 schedstat = openat(procfd, "schedstat", O_RDONLY);
179 if (schedstat == -1) {
180 log_error_errno(errno, "Failed to open /proc/schedstat (requires CONFIG_SCHEDSTATS=y in kernel config): %m");
185 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
197 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
200 if (strstr(key, "cpu")) {
201 r = safe_atoi((const char*)(key+3), &c);
202 if (r < 0 || c > MAXCPUS -1)
203 /* Oops, we only have room for MAXCPUS data */
205 sampledata->runtime[c] = atoll(rt);
206 sampledata->waittime[c] = atoll(wt);
219 e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
223 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
226 sampledata->entropy_avail = atoi(buf);
231 while ((ent = readdir(proc)) != NULL) {
232 char filename[PATH_MAX];
234 struct ps_struct *ps;
236 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
239 pid = atoi(ent->d_name);
245 while (ps->next_ps) {
251 /* end of our LL? then append a new record */
252 if (ps->pid != pid) {
253 _cleanup_fclose_ FILE *st = NULL;
255 struct ps_struct *parent;
258 ps->next_ps = new0(struct ps_struct, 1);
268 ps->sample = new0(struct ps_sched_struct, 1);
273 ps->sample->sampledata = sampledata;
277 /* mark our first sample */
278 ps->first = ps->last = ps->sample;
279 ps->sample->runtime = atoll(rt);
280 ps->sample->waittime = atoll(wt);
282 /* get name, start time */
284 sprintf(filename, "%d/sched", pid);
285 ps->sched = openat(procfd, filename, O_RDONLY);
290 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
298 if (!sscanf(buf, "%s %*s %*s", key))
301 strscpy(ps->name, sizeof(ps->name), key);
304 if (arg_show_cmdline)
305 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
316 if (!sscanf(m, "%*s %*s %s", t))
319 r = safe_atod(t, &ps->starttime);
323 ps->starttime /= 1000.0;
326 /* if this fails, that's OK */
327 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
328 ps->pid, &ps->cgroup);
331 sprintf(filename, "%d/stat", pid);
332 fd = openat(procfd, filename, O_RDONLY);
333 st = fdopen(fd, "r");
336 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
342 * setup child pointers
344 * these are used to paint the tree coherently later
345 * each parent has a LL of children, and a LL of siblings
348 continue; /* nothing to do for init atm */
350 /* kthreadd has ppid=0, which breaks our tree ordering */
355 while ((parent->next_ps && parent->pid != ps->ppid))
356 parent = parent->next_ps;
358 if (parent->pid != ps->ppid) {
361 parent = ps_first->next_ps;
366 if (!parent->children) {
367 /* it's the first child */
368 parent->children = ps;
370 /* walk all children and append */
371 struct ps_struct *children;
372 children = parent->children;
373 while (children->next)
374 children = children->next;
379 /* else -> found pid, append data in ps */
381 /* below here is all continuous logging parts - we get here on every
385 if (ps->schedstat < 0) {
386 sprintf(filename, "%d/schedstat", pid);
387 ps->schedstat = openat(procfd, filename, O_RDONLY);
388 if (ps->schedstat == -1)
391 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
393 /* clean up our file descriptors - assume that the process exited */
394 close(ps->schedstat);
401 // fclose(ps->smaps);
406 if (!sscanf(buf, "%s %s %*s", rt, wt))
409 ps->sample->next = new0(struct ps_sched_struct, 1);
410 if (!ps->sample->next) {
414 ps->sample->next->prev = ps->sample;
415 ps->sample = ps->sample->next;
416 ps->last = ps->sample;
417 ps->sample->runtime = atoll(rt);
418 ps->sample->waittime = atoll(wt);
419 ps->sample->sampledata = sampledata;
420 ps->sample->ps_new = ps;
422 ps_prev->cross = ps->sample;
424 ps_prev = ps->sample;
425 ps->total = (ps->last->runtime - ps->first->runtime)
433 sprintf(filename, "%d/smaps", pid);
434 fd = openat(procfd, filename, O_RDONLY);
435 ps->smaps = fdopen(fd, "r");
438 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
443 /* test to see if we need to skip another field */
445 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
448 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
451 if (buf[392] == 'V') {
462 /* skip one line, this contains the object mapped. */
463 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
466 /* then there's a 28 char 14 line block */
467 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
470 pss_kb = atoi(&buf[61]);
471 ps->sample->pss += pss_kb;
473 /* skip one more line if this is a newer kernel */
475 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
479 if (ps->sample->pss > ps->pss_max)
480 ps->pss_max = ps->sample->pss;
483 /* catch process rename, try to randomize time */
484 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
485 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
488 /* get name, start time */
490 sprintf(filename, "%d/sched", pid);
491 ps->sched = openat(procfd, filename, O_RDONLY);
495 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
497 /* clean up file descriptors */
501 close(ps->schedstat);
505 // fclose(ps->smaps);
510 if (!sscanf(buf, "%s %*s %*s", key))
513 strscpy(ps->name, sizeof(ps->name), key);
516 if (arg_show_cmdline)
517 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);