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 int 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 return log_error_errno(errno, "Failed to open /proc/vmstat: %m");
150 n = pread(vmstat, buf, sizeof(buf) - 1, 0);
152 vmstat = safe_close(vmstat);
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);
179 return log_error_errno(errno, "Failed to open /proc/schedstat (requires CONFIG_SCHEDSTATS=y in kernel config): %m");
182 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
184 schedstat = safe_close(schedstat);
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);
219 return log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m");
222 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
228 sampledata->entropy_avail = atoi(buf);
232 while ((ent = readdir(proc)) != NULL) {
233 char filename[PATH_MAX];
235 struct ps_struct *ps;
237 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
240 pid = atoi(ent->d_name);
246 while (ps->next_ps) {
252 /* end of our LL? then append a new record */
253 if (ps->pid != pid) {
254 _cleanup_fclose_ FILE *st = NULL;
256 struct ps_struct *parent;
259 ps->next_ps = new0(struct ps_struct, 1);
268 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);
297 if (!sscanf(buf, "%s %*s %*s", key))
300 strscpy(ps->name, sizeof(ps->name), key);
303 if (arg_show_cmdline)
304 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
315 if (!sscanf(m, "%*s %*s %s", t))
318 r = safe_atod(t, &ps->starttime);
322 ps->starttime /= 1000.0;
325 /* if this fails, that's OK */
326 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
327 ps->pid, &ps->cgroup);
330 sprintf(filename, "%d/stat", pid);
331 fd = openat(procfd, filename, O_RDONLY);
334 st = fdopen(fd, "r");
339 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
345 * setup child pointers
347 * these are used to paint the tree coherently later
348 * each parent has a LL of children, and a LL of siblings
351 continue; /* nothing to do for init atm */
353 /* kthreadd has ppid=0, which breaks our tree ordering */
358 while ((parent->next_ps && parent->pid != ps->ppid))
359 parent = parent->next_ps;
361 if (parent->pid != ps->ppid) {
364 parent = ps_first->next_ps;
369 if (!parent->children) {
370 /* it's the first child */
371 parent->children = ps;
373 /* walk all children and append */
374 struct ps_struct *children;
375 children = parent->children;
376 while (children->next)
377 children = children->next;
382 /* else -> found pid, append data in ps */
384 /* below here is all continuous logging parts - we get here on every
388 if (ps->schedstat < 0) {
389 sprintf(filename, "%d/schedstat", pid);
390 ps->schedstat = openat(procfd, filename, O_RDONLY);
391 if (ps->schedstat == -1)
394 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
396 /* clean up our file descriptors - assume that the process exited */
397 close(ps->schedstat);
404 // fclose(ps->smaps);
409 if (!sscanf(buf, "%s %s %*s", rt, wt))
412 ps->sample->next = new0(struct ps_sched_struct, 1);
413 if (!ps->sample->next)
416 ps->sample->next->prev = ps->sample;
417 ps->sample = ps->sample->next;
418 ps->last = ps->sample;
419 ps->sample->runtime = atoll(rt);
420 ps->sample->waittime = atoll(wt);
421 ps->sample->sampledata = sampledata;
422 ps->sample->ps_new = ps;
424 ps_prev->cross = ps->sample;
426 ps_prev = ps->sample;
427 ps->total = (ps->last->runtime - ps->first->runtime)
435 sprintf(filename, "%d/smaps", pid);
436 fd = openat(procfd, filename, O_RDONLY);
439 ps->smaps = fdopen(fd, "r");
444 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
449 /* test to see if we need to skip another field */
451 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
454 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
457 if (buf[392] == 'V') {
468 /* skip one line, this contains the object mapped. */
469 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
472 /* then there's a 28 char 14 line block */
473 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
476 pss_kb = atoi(&buf[61]);
477 ps->sample->pss += pss_kb;
479 /* skip one more line if this is a newer kernel */
481 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
485 if (ps->sample->pss > ps->pss_max)
486 ps->pss_max = ps->sample->pss;
489 /* catch process rename, try to randomize time */
490 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
491 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
494 /* get name, start time */
496 sprintf(filename, "%d/sched", pid);
497 ps->sched = openat(procfd, filename, O_RDONLY);
501 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
503 /* clean up file descriptors */
507 close(ps->schedstat);
511 // fclose(ps->smaps);
516 if (!sscanf(buf, "%s %*s %*s", key))
519 strscpy(ps->name, sizeof(ps->name), key);
522 if (arg_show_cmdline)
523 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);