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"
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 / 1000000000.0));
59 void log_uptime(void) {
60 _cleanup_fclose_ FILE *f = NULL;
64 f = fopen("/proc/uptime", "r");
68 if (!fscanf(f, "%s %*s", str))
71 uptime = strtod(str, NULL);
73 log_start = gettime_ns();
75 /* start graph at kernel boot time */
77 graph_start = log_start;
79 graph_start = log_start - uptime;
82 static char *bufgetline(char *buf) {
88 c = strchr(buf, '\n');
94 static int pid_cmdline_strscpy(char *buffer, size_t buf_len, int pid) {
95 char filename[PATH_MAX];
96 _cleanup_close_ int fd=-1;
99 sprintf(filename, "%d/cmdline", pid);
100 fd = openat(procfd, filename, O_RDONLY);
104 n = read(fd, buffer, buf_len-1);
107 for (i = 0; i < n; i++)
108 if (buffer[i] == '\0')
115 void log_sample(int sample, struct list_sample_data **ptr) {
117 static int schedstat;
132 struct list_sample_data *sampledata;
133 struct ps_sched_struct *ps_prev = NULL;
139 /* all the per-process stuff goes here */
141 /* find all processes */
142 proc = opendir("/proc");
145 procfd = dirfd(proc);
152 vmstat = openat(procfd, "vmstat", O_RDONLY);
154 perror("open /proc/vmstat");
159 n = pread(vmstat, buf, sizeof(buf) - 1, 0);
168 if (sscanf(m, "%s %s", key, val) < 2)
170 if (streq(key, "pgpgin"))
171 sampledata->blockstat.bi = atoi(val);
172 if (streq(key, "pgpgout")) {
173 sampledata->blockstat.bo = atoi(val);
183 /* overall CPU utilization */
184 schedstat = openat(procfd, "schedstat", O_RDONLY);
185 if (schedstat == -1) {
186 perror("open /proc/schedstat");
191 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
200 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
203 if (strstr(key, "cpu")) {
204 c = atoi((const char*)(key+3));
206 /* Oops, we only have room for MAXCPUS data */
208 sampledata->runtime[c] = atoll(rt);
209 sampledata->waittime[c] = atoll(wt);
222 e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
226 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
229 sampledata->entropy_avail = atoi(buf);
234 while ((ent = readdir(proc)) != NULL) {
235 char filename[PATH_MAX];
237 struct ps_struct *ps;
239 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
242 pid = atoi(ent->d_name);
248 while (ps->next_ps) {
254 /* end of our LL? then append a new record */
255 if (ps->pid != pid) {
256 _cleanup_fclose_ FILE *st = NULL;
258 struct ps_struct *parent;
260 ps->next_ps = calloc(1, sizeof(struct ps_struct));
262 perror("calloc(ps_struct)");
268 ps->sample = calloc(1, sizeof(struct ps_sched_struct));
270 perror("calloc(ps_struct)");
273 ps->sample->sampledata = sampledata;
277 /* mark our first sample */
278 ps->first = 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);
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 ps->starttime = strtod(t, NULL) / 1000.0;
321 sprintf(filename, "%d/stat", pid);
322 fd = openat(procfd, filename, O_RDONLY);
323 st = fdopen(fd, "r");
326 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
332 * setup child pointers
334 * these are used to paint the tree coherently later
335 * each parent has a LL of children, and a LL of siblings
338 continue; /* nothing to do for init atm */
340 /* kthreadd has ppid=0, which breaks our tree ordering */
345 while ((parent->next_ps && parent->pid != ps->ppid))
346 parent = parent->next_ps;
348 if ((!parent) || (parent->pid != ps->ppid)) {
351 parent = ps_first->next_ps;
356 if (!parent->children) {
357 /* it's the first child */
358 parent->children = ps;
360 /* walk all children and append */
361 struct ps_struct *children;
362 children = parent->children;
363 while (children->next)
364 children = children->next;
369 /* else -> found pid, append data in ps */
371 /* below here is all continuous logging parts - we get here on every
375 if (!ps->schedstat) {
376 sprintf(filename, "%d/schedstat", pid);
377 ps->schedstat = openat(procfd, filename, O_RDONLY);
378 if (ps->schedstat == -1)
381 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
383 /* clean up our file descriptors - assume that the process exited */
384 close(ps->schedstat);
388 // fclose(ps->smaps);
393 if (!sscanf(buf, "%s %s %*s", rt, wt))
396 ps->sample->next = calloc(1, sizeof(struct ps_sched_struct));
398 perror("calloc(ps_struct)");
401 ps->sample->next->prev = ps->sample;
402 ps->sample = ps->sample->next;
403 ps->last = ps->sample;
404 ps->sample->runtime = atoll(rt);
405 ps->sample->waittime = atoll(wt);
406 ps->sample->sampledata = sampledata;
407 ps->sample->ps_new = ps;
409 ps_prev->cross = ps->sample;
411 ps_prev = ps->sample;
412 ps->total = (ps->last->runtime - ps->first->runtime)
420 sprintf(filename, "%d/smaps", pid);
421 fd = openat(procfd, filename, O_RDONLY);
422 ps->smaps = fdopen(fd, "r");
425 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
430 /* test to see if we need to skip another field */
432 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
435 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
438 if (buf[392] == 'V') {
449 /* skip one line, this contains the object mapped. */
450 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
453 /* then there's a 28 char 14 line block */
454 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
457 pss_kb = atoi(&buf[61]);
458 ps->sample->pss += pss_kb;
460 /* skip one more line if this is a newer kernel */
462 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
466 if (ps->sample->pss > ps->pss_max)
467 ps->pss_max = ps->sample->pss;
470 /* catch process rename, try to randomize time */
471 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
472 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
475 /* get name, start time */
477 sprintf(filename, "%d/sched", pid);
478 ps->sched = openat(procfd, filename, O_RDONLY);
482 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
484 /* clean up file descriptors */
487 close(ps->schedstat);
489 // fclose(ps->smaps);
494 if (!sscanf(buf, "%s %*s %*s", key))
497 strscpy(ps->name, sizeof(ps->name), key);
500 if (arg_show_cmdline)
501 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);