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 void log_uptime(void) {
61 _cleanup_fclose_ FILE *f = NULL;
65 f = fopen("/proc/uptime", "re");
69 if (!fscanf(f, "%s %*s", str))
72 uptime = strtod(str, NULL);
74 log_start = gettime_ns();
76 /* start graph at kernel boot time */
78 graph_start = log_start;
80 graph_start = log_start - uptime;
83 static char *bufgetline(char *buf) {
89 c = strchr(buf, '\n');
95 static int pid_cmdline_strscpy(char *buffer, size_t buf_len, int pid) {
96 char filename[PATH_MAX];
97 _cleanup_close_ int fd=-1;
100 sprintf(filename, "%d/cmdline", pid);
101 fd = openat(procfd, filename, O_RDONLY);
105 n = read(fd, buffer, buf_len-1);
108 for (i = 0; i < n; i++)
109 if (buffer[i] == '\0')
116 void log_sample(int sample, struct list_sample_data **ptr) {
118 static int schedstat;
133 struct list_sample_data *sampledata;
134 struct ps_sched_struct *ps_prev = NULL;
138 /* all the per-process stuff goes here */
140 /* find all processes */
141 proc = opendir("/proc");
144 procfd = dirfd(proc);
151 vmstat = openat(procfd, "vmstat", O_RDONLY);
153 log_error("Failed to open /proc/vmstat: %m");
158 n = pread(vmstat, buf, sizeof(buf) - 1, 0);
167 if (sscanf(m, "%s %s", key, val) < 2)
169 if (streq(key, "pgpgin"))
170 sampledata->blockstat.bi = atoi(val);
171 if (streq(key, "pgpgout")) {
172 sampledata->blockstat.bo = atoi(val);
182 /* overall CPU utilization */
183 schedstat = openat(procfd, "schedstat", O_RDONLY);
184 if (schedstat == -1) {
185 log_error("Failed to open /proc/schedstat: %m");
190 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
199 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
202 if (strstr(key, "cpu")) {
203 c = atoi((const char*)(key+3));
205 /* Oops, we only have room for MAXCPUS data */
207 sampledata->runtime[c] = atoll(rt);
208 sampledata->waittime[c] = atoll(wt);
221 e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
225 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
228 sampledata->entropy_avail = atoi(buf);
233 while ((ent = readdir(proc)) != NULL) {
234 char filename[PATH_MAX];
236 struct ps_struct *ps;
238 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
241 pid = atoi(ent->d_name);
247 while (ps->next_ps) {
253 /* end of our LL? then append a new record */
254 if (ps->pid != pid) {
255 _cleanup_fclose_ FILE *st = NULL;
257 struct ps_struct *parent;
259 ps->next_ps = new0(struct ps_struct, 1);
267 ps->sample = new0(struct ps_sched_struct, 1);
272 ps->sample->sampledata = sampledata;
276 /* mark our first sample */
277 ps->first = ps->last = ps->sample;
278 ps->sample->runtime = atoll(rt);
279 ps->sample->waittime = atoll(wt);
281 /* get name, start time */
283 sprintf(filename, "%d/sched", pid);
284 ps->sched = openat(procfd, filename, O_RDONLY);
289 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
296 if (!sscanf(buf, "%s %*s %*s", key))
299 strscpy(ps->name, sizeof(ps->name), key);
302 if (arg_show_cmdline)
303 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
314 if (!sscanf(m, "%*s %*s %s", t))
317 ps->starttime = strtod(t, NULL) / 1000.0;
320 /* if this fails, that's OK */
321 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
322 ps->pid, &ps->cgroup);
325 sprintf(filename, "%d/stat", pid);
326 fd = openat(procfd, filename, O_RDONLY);
327 st = fdopen(fd, "r");
330 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
336 * setup child pointers
338 * these are used to paint the tree coherently later
339 * each parent has a LL of children, and a LL of siblings
342 continue; /* nothing to do for init atm */
344 /* kthreadd has ppid=0, which breaks our tree ordering */
349 while ((parent->next_ps && parent->pid != ps->ppid))
350 parent = parent->next_ps;
352 if (parent->pid != ps->ppid) {
355 parent = ps_first->next_ps;
360 if (!parent->children) {
361 /* it's the first child */
362 parent->children = ps;
364 /* walk all children and append */
365 struct ps_struct *children;
366 children = parent->children;
367 while (children->next)
368 children = children->next;
373 /* else -> found pid, append data in ps */
375 /* below here is all continuous logging parts - we get here on every
379 if (!ps->schedstat) {
380 sprintf(filename, "%d/schedstat", pid);
381 ps->schedstat = openat(procfd, filename, O_RDONLY);
382 if (ps->schedstat == -1)
385 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
387 /* clean up our file descriptors - assume that the process exited */
388 close(ps->schedstat);
392 // fclose(ps->smaps);
397 if (!sscanf(buf, "%s %s %*s", rt, wt))
400 ps->sample->next = new0(struct ps_sched_struct, 1);
405 ps->sample->next->prev = ps->sample;
406 ps->sample = ps->sample->next;
407 ps->last = ps->sample;
408 ps->sample->runtime = atoll(rt);
409 ps->sample->waittime = atoll(wt);
410 ps->sample->sampledata = sampledata;
411 ps->sample->ps_new = ps;
413 ps_prev->cross = ps->sample;
415 ps_prev = ps->sample;
416 ps->total = (ps->last->runtime - ps->first->runtime)
424 sprintf(filename, "%d/smaps", pid);
425 fd = openat(procfd, filename, O_RDONLY);
426 ps->smaps = fdopen(fd, "r");
429 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
434 /* test to see if we need to skip another field */
436 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
439 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
442 if (buf[392] == 'V') {
453 /* skip one line, this contains the object mapped. */
454 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
457 /* then there's a 28 char 14 line block */
458 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
461 pss_kb = atoi(&buf[61]);
462 ps->sample->pss += pss_kb;
464 /* skip one more line if this is a newer kernel */
466 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
470 if (ps->sample->pss > ps->pss_max)
471 ps->pss_max = ps->sample->pss;
474 /* catch process rename, try to randomize time */
475 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
476 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
479 /* get name, start time */
481 sprintf(filename, "%d/sched", pid);
482 ps->sched = openat(procfd, filename, O_RDONLY);
486 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
488 /* clean up file descriptors */
491 close(ps->schedstat);
493 // fclose(ps->smaps);
498 if (!sscanf(buf, "%s %*s %*s", key))
501 strscpy(ps->name, sizeof(ps->name), key);
504 if (arg_show_cmdline)
505 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);