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);
335 st = fdopen(fd, "r");
340 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
346 * setup child pointers
348 * these are used to paint the tree coherently later
349 * each parent has a LL of children, and a LL of siblings
352 continue; /* nothing to do for init atm */
354 /* kthreadd has ppid=0, which breaks our tree ordering */
359 while ((parent->next_ps && parent->pid != ps->ppid))
360 parent = parent->next_ps;
362 if (parent->pid != ps->ppid) {
365 parent = ps_first->next_ps;
370 if (!parent->children) {
371 /* it's the first child */
372 parent->children = ps;
374 /* walk all children and append */
375 struct ps_struct *children;
376 children = parent->children;
377 while (children->next)
378 children = children->next;
383 /* else -> found pid, append data in ps */
385 /* below here is all continuous logging parts - we get here on every
389 if (ps->schedstat < 0) {
390 sprintf(filename, "%d/schedstat", pid);
391 ps->schedstat = openat(procfd, filename, O_RDONLY);
392 if (ps->schedstat == -1)
395 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
397 /* clean up our file descriptors - assume that the process exited */
398 close(ps->schedstat);
405 // fclose(ps->smaps);
410 if (!sscanf(buf, "%s %s %*s", rt, wt))
413 ps->sample->next = new0(struct ps_sched_struct, 1);
414 if (!ps->sample->next) {
418 ps->sample->next->prev = ps->sample;
419 ps->sample = ps->sample->next;
420 ps->last = ps->sample;
421 ps->sample->runtime = atoll(rt);
422 ps->sample->waittime = atoll(wt);
423 ps->sample->sampledata = sampledata;
424 ps->sample->ps_new = ps;
426 ps_prev->cross = ps->sample;
428 ps_prev = ps->sample;
429 ps->total = (ps->last->runtime - ps->first->runtime)
437 sprintf(filename, "%d/smaps", pid);
438 fd = openat(procfd, filename, O_RDONLY);
441 ps->smaps = fdopen(fd, "r");
446 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
451 /* test to see if we need to skip another field */
453 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
456 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
459 if (buf[392] == 'V') {
470 /* skip one line, this contains the object mapped. */
471 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
474 /* then there's a 28 char 14 line block */
475 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
478 pss_kb = atoi(&buf[61]);
479 ps->sample->pss += pss_kb;
481 /* skip one more line if this is a newer kernel */
483 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
487 if (ps->sample->pss > ps->pss_max)
488 ps->pss_max = ps->sample->pss;
491 /* catch process rename, try to randomize time */
492 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
493 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
496 /* get name, start time */
498 sprintf(filename, "%d/sched", pid);
499 ps->sched = openat(procfd, filename, O_RDONLY);
503 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
505 /* clean up file descriptors */
509 close(ps->schedstat);
513 // fclose(ps->smaps);
518 if (!sscanf(buf, "%s %*s %*s", key))
521 strscpy(ps->name, sizeof(ps->name), key);
524 if (arg_show_cmdline)
525 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);