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) {
112 static int schedstat;
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);
161 if (sscanf(m, "%s %s", key, val) < 2)
163 if (streq(key, "pgpgin"))
164 sampledata->blockstat.bi = atoi(val);
165 if (streq(key, "pgpgout")) {
166 sampledata->blockstat.bo = atoi(val);
176 /* overall CPU utilization */
177 schedstat = openat(procfd, "schedstat", O_RDONLY);
178 if (schedstat == -1) {
179 log_error_errno(errno, "Failed to open /proc/schedstat: %m");
184 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
195 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
198 if (strstr(key, "cpu")) {
199 r = safe_atoi((const char*)(key+3), &c);
200 if (r < 0 || c > MAXCPUS -1)
201 /* Oops, we only have room for MAXCPUS data */
203 sampledata->runtime[c] = atoll(rt);
204 sampledata->waittime[c] = atoll(wt);
217 e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
221 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
224 sampledata->entropy_avail = atoi(buf);
229 while ((ent = readdir(proc)) != NULL) {
230 char filename[PATH_MAX];
232 struct ps_struct *ps;
234 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
237 pid = atoi(ent->d_name);
243 while (ps->next_ps) {
249 /* end of our LL? then append a new record */
250 if (ps->pid != pid) {
251 _cleanup_fclose_ FILE *st = NULL;
253 struct ps_struct *parent;
256 ps->next_ps = new0(struct ps_struct, 1);
264 ps->sample = new0(struct ps_sched_struct, 1);
269 ps->sample->sampledata = sampledata;
273 /* mark our first sample */
274 ps->first = ps->last = ps->sample;
275 ps->sample->runtime = atoll(rt);
276 ps->sample->waittime = atoll(wt);
278 /* get name, start time */
280 sprintf(filename, "%d/sched", pid);
281 ps->sched = openat(procfd, filename, O_RDONLY);
286 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
293 if (!sscanf(buf, "%s %*s %*s", key))
296 strscpy(ps->name, sizeof(ps->name), key);
299 if (arg_show_cmdline)
300 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
311 if (!sscanf(m, "%*s %*s %s", t))
314 r = safe_atod(t, &ps->starttime);
318 ps->starttime /= 1000.0;
321 /* if this fails, that's OK */
322 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
323 ps->pid, &ps->cgroup);
326 sprintf(filename, "%d/stat", pid);
327 fd = openat(procfd, filename, O_RDONLY);
328 st = fdopen(fd, "r");
331 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
337 * setup child pointers
339 * these are used to paint the tree coherently later
340 * each parent has a LL of children, and a LL of siblings
343 continue; /* nothing to do for init atm */
345 /* kthreadd has ppid=0, which breaks our tree ordering */
350 while ((parent->next_ps && parent->pid != ps->ppid))
351 parent = parent->next_ps;
353 if (parent->pid != ps->ppid) {
356 parent = ps_first->next_ps;
361 if (!parent->children) {
362 /* it's the first child */
363 parent->children = ps;
365 /* walk all children and append */
366 struct ps_struct *children;
367 children = parent->children;
368 while (children->next)
369 children = children->next;
374 /* else -> found pid, append data in ps */
376 /* below here is all continuous logging parts - we get here on every
380 if (!ps->schedstat) {
381 sprintf(filename, "%d/schedstat", pid);
382 ps->schedstat = openat(procfd, filename, O_RDONLY);
383 if (ps->schedstat == -1)
386 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
388 /* clean up our file descriptors - assume that the process exited */
389 close(ps->schedstat);
393 // fclose(ps->smaps);
398 if (!sscanf(buf, "%s %s %*s", rt, wt))
401 ps->sample->next = new0(struct ps_sched_struct, 1);
402 if (!ps->sample->next) {
406 ps->sample->next->prev = ps->sample;
407 ps->sample = ps->sample->next;
408 ps->last = ps->sample;
409 ps->sample->runtime = atoll(rt);
410 ps->sample->waittime = atoll(wt);
411 ps->sample->sampledata = sampledata;
412 ps->sample->ps_new = ps;
414 ps_prev->cross = ps->sample;
416 ps_prev = ps->sample;
417 ps->total = (ps->last->runtime - ps->first->runtime)
425 sprintf(filename, "%d/smaps", pid);
426 fd = openat(procfd, filename, O_RDONLY);
427 ps->smaps = fdopen(fd, "r");
430 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
435 /* test to see if we need to skip another field */
437 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
440 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
443 if (buf[392] == 'V') {
454 /* skip one line, this contains the object mapped. */
455 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
458 /* then there's a 28 char 14 line block */
459 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
462 pss_kb = atoi(&buf[61]);
463 ps->sample->pss += pss_kb;
465 /* skip one more line if this is a newer kernel */
467 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
471 if (ps->sample->pss > ps->pss_max)
472 ps->pss_max = ps->sample->pss;
475 /* catch process rename, try to randomize time */
476 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
477 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
480 /* get name, start time */
482 sprintf(filename, "%d/sched", pid);
483 ps->sched = openat(procfd, filename, O_RDONLY);
487 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
489 /* clean up file descriptors */
492 close(ps->schedstat);
494 // fclose(ps->smaps);
499 if (!sscanf(buf, "%s %*s %*s", key))
502 strscpy(ps->name, sizeof(ps->name), key);
505 if (arg_show_cmdline)
506 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);