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/>.
28 #include <sys/types.h>
39 #include "bootchart.h"
40 #include "cgroup-util.h"
43 * Alloc a static 4k buffer for stdio - primarily used to increase
44 * PSS buffering from the default 1k stdin buffer to reduce
47 static char smaps_buf[4096];
52 double gettime_ns(void) {
55 clock_gettime(CLOCK_MONOTONIC, &n);
57 return (n.tv_sec + (n.tv_nsec / 1000000000.0));
60 static double gettime_up(void) {
63 clock_gettime(CLOCK_BOOTTIME, &n);
64 return (n.tv_sec + (n.tv_nsec / 1000000000.0));
67 void log_uptime(void) {
69 graph_start = log_start = gettime_ns();
71 double uptime = gettime_up();
73 log_start = gettime_ns();
74 graph_start = log_start - uptime;
78 static char *bufgetline(char *buf) {
84 c = strchr(buf, '\n');
90 static int pid_cmdline_strscpy(char *buffer, size_t buf_len, int pid) {
91 char filename[PATH_MAX];
92 _cleanup_close_ int fd=-1;
95 sprintf(filename, "%d/cmdline", pid);
96 fd = openat(procfd, filename, O_RDONLY);
100 n = read(fd, buffer, buf_len-1);
103 for (i = 0; i < n; i++)
104 if (buffer[i] == '\0')
111 void log_sample(int sample, struct list_sample_data **ptr) {
113 static int schedstat;
128 struct list_sample_data *sampledata;
129 struct ps_sched_struct *ps_prev = NULL;
133 /* all the per-process stuff goes here */
135 /* find all processes */
136 proc = opendir("/proc");
139 procfd = dirfd(proc);
146 vmstat = openat(procfd, "vmstat", O_RDONLY);
148 log_error("Failed to open /proc/vmstat: %m");
153 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("Failed to open /proc/schedstat: %m");
185 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
194 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
197 if (strstr(key, "cpu")) {
198 c = atoi((const char*)(key+3));
200 /* Oops, we only have room for MAXCPUS data */
202 sampledata->runtime[c] = atoll(rt);
203 sampledata->waittime[c] = atoll(wt);
216 e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
220 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
223 sampledata->entropy_avail = atoi(buf);
228 while ((ent = readdir(proc)) != NULL) {
229 char filename[PATH_MAX];
231 struct ps_struct *ps;
233 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
236 pid = atoi(ent->d_name);
242 while (ps->next_ps) {
248 /* end of our LL? then append a new record */
249 if (ps->pid != pid) {
250 _cleanup_fclose_ FILE *st = NULL;
252 struct ps_struct *parent;
254 ps->next_ps = new0(struct ps_struct, 1);
262 ps->sample = new0(struct ps_sched_struct, 1);
267 ps->sample->sampledata = sampledata;
271 /* mark our first sample */
272 ps->first = ps->last = ps->sample;
273 ps->sample->runtime = atoll(rt);
274 ps->sample->waittime = atoll(wt);
276 /* get name, start time */
278 sprintf(filename, "%d/sched", pid);
279 ps->sched = openat(procfd, filename, O_RDONLY);
284 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
291 if (!sscanf(buf, "%s %*s %*s", key))
294 strscpy(ps->name, sizeof(ps->name), key);
297 if (arg_show_cmdline)
298 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
309 if (!sscanf(m, "%*s %*s %s", t))
312 ps->starttime = strtod(t, NULL) / 1000.0;
315 /* if this fails, that's OK */
316 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
317 ps->pid, &ps->cgroup);
320 sprintf(filename, "%d/stat", pid);
321 fd = openat(procfd, filename, O_RDONLY);
322 st = fdopen(fd, "r");
325 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
331 * setup child pointers
333 * these are used to paint the tree coherently later
334 * each parent has a LL of children, and a LL of siblings
337 continue; /* nothing to do for init atm */
339 /* kthreadd has ppid=0, which breaks our tree ordering */
344 while ((parent->next_ps && parent->pid != ps->ppid))
345 parent = parent->next_ps;
347 if (parent->pid != ps->ppid) {
350 parent = ps_first->next_ps;
355 if (!parent->children) {
356 /* it's the first child */
357 parent->children = ps;
359 /* walk all children and append */
360 struct ps_struct *children;
361 children = parent->children;
362 while (children->next)
363 children = children->next;
368 /* else -> found pid, append data in ps */
370 /* below here is all continuous logging parts - we get here on every
374 if (!ps->schedstat) {
375 sprintf(filename, "%d/schedstat", pid);
376 ps->schedstat = openat(procfd, filename, O_RDONLY);
377 if (ps->schedstat == -1)
380 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
382 /* clean up our file descriptors - assume that the process exited */
383 close(ps->schedstat);
387 // fclose(ps->smaps);
392 if (!sscanf(buf, "%s %s %*s", rt, wt))
395 ps->sample->next = new0(struct ps_sched_struct, 1);
400 ps->sample->next->prev = ps->sample;
401 ps->sample = ps->sample->next;
402 ps->last = ps->sample;
403 ps->sample->runtime = atoll(rt);
404 ps->sample->waittime = atoll(wt);
405 ps->sample->sampledata = sampledata;
406 ps->sample->ps_new = ps;
408 ps_prev->cross = ps->sample;
410 ps_prev = ps->sample;
411 ps->total = (ps->last->runtime - ps->first->runtime)
419 sprintf(filename, "%d/smaps", pid);
420 fd = openat(procfd, filename, O_RDONLY);
421 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 (((samples - 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 */
486 close(ps->schedstat);
488 // fclose(ps->smaps);
493 if (!sscanf(buf, "%s %*s %*s", key))
496 strscpy(ps->name, sizeof(ps->name), key);
499 if (arg_show_cmdline)
500 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);