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;
122 static int e_fd = -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);
221 log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m");
226 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
232 sampledata->entropy_avail = atoi(buf);
236 while ((ent = readdir(proc)) != NULL) {
237 char filename[PATH_MAX];
239 struct ps_struct *ps;
241 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
244 pid = atoi(ent->d_name);
250 while (ps->next_ps) {
256 /* end of our LL? then append a new record */
257 if (ps->pid != pid) {
258 _cleanup_fclose_ FILE *st = NULL;
260 struct ps_struct *parent;
263 ps->next_ps = new0(struct ps_struct, 1);
273 ps->sample = new0(struct ps_sched_struct, 1);
278 ps->sample->sampledata = sampledata;
282 /* mark our first sample */
283 ps->first = ps->last = ps->sample;
284 ps->sample->runtime = atoll(rt);
285 ps->sample->waittime = atoll(wt);
287 /* get name, start time */
289 sprintf(filename, "%d/sched", pid);
290 ps->sched = openat(procfd, filename, O_RDONLY);
295 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
303 if (!sscanf(buf, "%s %*s %*s", key))
306 strscpy(ps->name, sizeof(ps->name), key);
309 if (arg_show_cmdline)
310 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
321 if (!sscanf(m, "%*s %*s %s", t))
324 r = safe_atod(t, &ps->starttime);
328 ps->starttime /= 1000.0;
331 /* if this fails, that's OK */
332 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
333 ps->pid, &ps->cgroup);
336 sprintf(filename, "%d/stat", pid);
337 fd = openat(procfd, filename, O_RDONLY);
340 st = fdopen(fd, "r");
345 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
351 * setup child pointers
353 * these are used to paint the tree coherently later
354 * each parent has a LL of children, and a LL of siblings
357 continue; /* nothing to do for init atm */
359 /* kthreadd has ppid=0, which breaks our tree ordering */
364 while ((parent->next_ps && parent->pid != ps->ppid))
365 parent = parent->next_ps;
367 if (parent->pid != ps->ppid) {
370 parent = ps_first->next_ps;
375 if (!parent->children) {
376 /* it's the first child */
377 parent->children = ps;
379 /* walk all children and append */
380 struct ps_struct *children;
381 children = parent->children;
382 while (children->next)
383 children = children->next;
388 /* else -> found pid, append data in ps */
390 /* below here is all continuous logging parts - we get here on every
394 if (ps->schedstat < 0) {
395 sprintf(filename, "%d/schedstat", pid);
396 ps->schedstat = openat(procfd, filename, O_RDONLY);
397 if (ps->schedstat == -1)
400 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
402 /* clean up our file descriptors - assume that the process exited */
403 close(ps->schedstat);
410 // fclose(ps->smaps);
415 if (!sscanf(buf, "%s %s %*s", rt, wt))
418 ps->sample->next = new0(struct ps_sched_struct, 1);
419 if (!ps->sample->next) {
423 ps->sample->next->prev = ps->sample;
424 ps->sample = ps->sample->next;
425 ps->last = ps->sample;
426 ps->sample->runtime = atoll(rt);
427 ps->sample->waittime = atoll(wt);
428 ps->sample->sampledata = sampledata;
429 ps->sample->ps_new = ps;
431 ps_prev->cross = ps->sample;
433 ps_prev = ps->sample;
434 ps->total = (ps->last->runtime - ps->first->runtime)
442 sprintf(filename, "%d/smaps", pid);
443 fd = openat(procfd, filename, O_RDONLY);
446 ps->smaps = fdopen(fd, "r");
451 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
456 /* test to see if we need to skip another field */
458 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
461 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
464 if (buf[392] == 'V') {
475 /* skip one line, this contains the object mapped. */
476 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
479 /* then there's a 28 char 14 line block */
480 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
483 pss_kb = atoi(&buf[61]);
484 ps->sample->pss += pss_kb;
486 /* skip one more line if this is a newer kernel */
488 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
492 if (ps->sample->pss > ps->pss_max)
493 ps->pss_max = ps->sample->pss;
496 /* catch process rename, try to randomize time */
497 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
498 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
501 /* get name, start time */
503 sprintf(filename, "%d/sched", pid);
504 ps->sched = openat(procfd, filename, O_RDONLY);
508 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
510 /* clean up file descriptors */
514 close(ps->schedstat);
518 // fclose(ps->smaps);
523 if (!sscanf(buf, "%s %*s %*s", key))
526 strscpy(ps->name, sizeof(ps->name), key);
529 if (arg_show_cmdline)
530 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);