]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/bootchart/store.c
Merge pull request #412 from fsateler/sysv-invalid-names-v2
[thirdparty/systemd.git] / src / bootchart / store.c
CommitLineData
6d031c0b
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
cd3bccaa 3/***
6d031c0b 4 This file is part of systemd.
cd3bccaa 5
3c527fd1 6 Copyright (C) 2009-2013 Intel Corporation
cd3bccaa
AK
7
8 Authors:
9 Auke Kok <auke-jan.h.kok@intel.com>
10
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.
15
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.
20
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/>.
23 ***/
83fdc450 24
83fdc450
AK
25#include <unistd.h>
26#include <stdlib.h>
27#include <limits.h>
83fdc450
AK
28#include <stdio.h>
29#include <string.h>
30#include <dirent.h>
31#include <fcntl.h>
32#include <time.h>
33
53f5329f 34#include "util.h"
ece74070 35#include "time-util.h"
c309a713 36#include "strxcpyx.h"
6d031c0b
LP
37#include "store.h"
38#include "bootchart.h"
49e5b2a9 39#include "cgroup-util.h"
83fdc450
AK
40
41/*
42 * Alloc a static 4k buffer for stdio - primarily used to increase
43 * PSS buffering from the default 1k stdin buffer to reduce
44 * read() overhead.
45 */
46static char smaps_buf[4096];
8dfb6e71 47static int skip = 0;
83fdc450 48
6d031c0b 49double gettime_ns(void) {
2c408fbf 50 struct timespec n;
83fdc450 51
2c408fbf 52 clock_gettime(CLOCK_MONOTONIC, &n);
83fdc450 53
ece74070 54 return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
83fdc450
AK
55}
56
6d031c0b 57static char *bufgetline(char *buf) {
28989b63 58 char *c;
83fdc450 59
28989b63
TA
60 if (!buf)
61 return NULL;
83fdc450 62
28989b63
TA
63 c = strchr(buf, '\n');
64 if (c)
65 c++;
af672f03 66
28989b63 67 return c;
83fdc450
AK
68}
69
f9178132 70static int pid_cmdline_strscpy(int procfd, char *buffer, size_t buf_len, int pid) {
6d031c0b 71 char filename[PATH_MAX];
af672f03 72 _cleanup_close_ int fd = -1;
6d031c0b 73 ssize_t n;
e90f9fa4 74
6d031c0b 75 sprintf(filename, "%d/cmdline", pid);
af672f03 76 fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
6d031c0b
LP
77 if (fd < 0)
78 return -errno;
e90f9fa4 79
6d031c0b 80 n = read(fd, buffer, buf_len-1);
e90f9fa4
HH
81 if (n > 0) {
82 int i;
83 for (i = 0; i < n; i++)
84 if (buffer[i] == '\0')
85 buffer[i] = ' ';
86 buffer[n] = '\0';
87 }
af672f03 88
6d031c0b 89 return 0;
e90f9fa4 90}
83fdc450 91
1f2ecb03
DM
92int log_sample(DIR *proc,
93 int sample,
94 struct ps_struct *ps_first,
95 struct list_sample_data **ptr,
96 int *pscount,
97 int *cpus) {
98
9964a9eb
AS
99 static int vmstat = -1;
100 static int schedstat = -1;
522cd7f1 101 char buf[4096];
28989b63
TA
102 char key[256];
103 char val[256];
104 char rt[256];
105 char wt[256];
106 char *m;
107 int c;
108 int p;
109 int mod;
c87664fe 110 static int e_fd = -1;
28989b63
TA
111 ssize_t s;
112 ssize_t n;
113 struct dirent *ent;
f2f85884 114 int fd;
8dfb6e71
NC
115 struct list_sample_data *sampledata;
116 struct ps_sched_struct *ps_prev = NULL;
f9178132 117 int procfd;
caa43397 118 int taskfd = -1;
8dfb6e71 119
8dfb6e71 120 sampledata = *ptr;
f2f85884 121
f9178132
DM
122 procfd = dirfd(proc);
123 if (procfd < 0)
124 return -errno;
28989b63 125
9964a9eb 126 if (vmstat < 0) {
28989b63 127 /* block stuff */
af672f03
DM
128 vmstat = openat(procfd, "vmstat", O_RDONLY|O_CLOEXEC);
129 if (vmstat < 0)
03995863 130 return log_error_errno(errno, "Failed to open /proc/vmstat: %m");
28989b63
TA
131 }
132
133 n = pread(vmstat, buf, sizeof(buf) - 1, 0);
134 if (n <= 0) {
34a4071e
DM
135 vmstat = safe_close(vmstat);
136 if (n < 0)
137 return -errno;
138 return -ENODATA;
28989b63 139 }
af672f03 140
28989b63
TA
141 buf[n] = '\0';
142
143 m = buf;
144 while (m) {
145 if (sscanf(m, "%s %s", key, val) < 2)
146 goto vmstat_next;
53f5329f 147 if (streq(key, "pgpgin"))
8dfb6e71 148 sampledata->blockstat.bi = atoi(val);
53f5329f 149 if (streq(key, "pgpgout")) {
8dfb6e71 150 sampledata->blockstat.bo = atoi(val);
28989b63
TA
151 break;
152 }
83fdc450 153vmstat_next:
28989b63
TA
154 m = bufgetline(m);
155 if (!m)
156 break;
157 }
158
9964a9eb 159 if (schedstat < 0) {
28989b63 160 /* overall CPU utilization */
af672f03
DM
161 schedstat = openat(procfd, "schedstat", O_RDONLY|O_CLOEXEC);
162 if (schedstat < 0)
34a4071e 163 return log_error_errno(errno, "Failed to open /proc/schedstat (requires CONFIG_SCHEDSTATS=y in kernel config): %m");
28989b63
TA
164 }
165
166 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
167 if (n <= 0) {
34a4071e
DM
168 schedstat = safe_close(schedstat);
169 if (n < 0)
170 return -errno;
171 return -ENODATA;
28989b63 172 }
af672f03 173
28989b63
TA
174 buf[n] = '\0';
175
176 m = buf;
177 while (m) {
9bcf7507
TA
178 int r;
179
28989b63
TA
180 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
181 goto schedstat_next;
182
183 if (strstr(key, "cpu")) {
9bcf7507 184 r = safe_atoi((const char*)(key+3), &c);
c119700c 185 if (r < 0 || c > MAXCPUS -1)
28989b63
TA
186 /* Oops, we only have room for MAXCPUS data */
187 break;
8dfb6e71
NC
188 sampledata->runtime[c] = atoll(rt);
189 sampledata->waittime[c] = atoll(wt);
28989b63 190
1f2ecb03
DM
191 if (c == *cpus)
192 *cpus = c + 1;
28989b63 193 }
83fdc450 194schedstat_next:
28989b63
TA
195 m = bufgetline(m);
196 if (!m)
197 break;
198 }
199
6d031c0b 200 if (arg_entropy) {
c87664fe 201 if (e_fd < 0) {
af672f03
DM
202 e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY|O_CLOEXEC);
203 if (e_fd < 0)
34a4071e 204 return log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m");
28989b63
TA
205 }
206
c87664fe
AS
207 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
208 if (n <= 0) {
af672f03 209 e_fd = safe_close(e_fd);
c87664fe
AS
210 } else {
211 buf[n] = '\0';
212 sampledata->entropy_avail = atoi(buf);
28989b63
TA
213 }
214 }
215
28989b63
TA
216 while ((ent = readdir(proc)) != NULL) {
217 char filename[PATH_MAX];
218 int pid;
219 struct ps_struct *ps;
220
221 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
222 continue;
223
224 pid = atoi(ent->d_name);
225
226 if (pid >= MAXPIDS)
227 continue;
228
229 ps = ps_first;
230 while (ps->next_ps) {
231 ps = ps->next_ps;
232 if (ps->pid == pid)
233 break;
234 }
235
236 /* end of our LL? then append a new record */
237 if (ps->pid != pid) {
7fd1b19b 238 _cleanup_fclose_ FILE *st = NULL;
28989b63
TA
239 char t[32];
240 struct ps_struct *parent;
e10f3c43 241 int r;
28989b63 242
955d98c9 243 ps->next_ps = new0(struct ps_struct, 1);
34a4071e
DM
244 if (!ps->next_ps)
245 return log_oom();
246
28989b63
TA
247 ps = ps->next_ps;
248 ps->pid = pid;
9964a9eb
AS
249 ps->sched = -1;
250 ps->schedstat = -1;
28989b63 251
955d98c9 252 ps->sample = new0(struct ps_sched_struct, 1);
34a4071e
DM
253 if (!ps->sample)
254 return log_oom();
255
8dfb6e71 256 ps->sample->sampledata = sampledata;
28989b63 257
1f2ecb03 258 (*pscount)++;
28989b63
TA
259
260 /* mark our first sample */
306e6650 261 ps->first = ps->last = ps->sample;
8dfb6e71
NC
262 ps->sample->runtime = atoll(rt);
263 ps->sample->waittime = atoll(wt);
28989b63
TA
264
265 /* get name, start time */
9964a9eb 266 if (ps->sched < 0) {
f2f85884 267 sprintf(filename, "%d/sched", pid);
af672f03
DM
268 ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
269 if (ps->sched < 0)
28989b63
TA
270 continue;
271 }
272
273 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
274 if (s <= 0) {
af672f03 275 ps->sched = safe_close(ps->sched);
28989b63
TA
276 continue;
277 }
ef2648c1 278 buf[s] = '\0';
28989b63
TA
279
280 if (!sscanf(buf, "%s %*s %*s", key))
281 continue;
282
c309a713 283 strscpy(ps->name, sizeof(ps->name), key);
e90f9fa4
HH
284
285 /* cmdline */
6d031c0b 286 if (arg_show_cmdline)
f9178132 287 pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid);
e90f9fa4 288
28989b63
TA
289 /* discard line 2 */
290 m = bufgetline(buf);
291 if (!m)
292 continue;
293
294 m = bufgetline(m);
295 if (!m)
296 continue;
297
298 if (!sscanf(m, "%*s %*s %s", t))
299 continue;
300
e10f3c43
TG
301 r = safe_atod(t, &ps->starttime);
302 if (r < 0)
303 continue;
304
305 ps->starttime /= 1000.0;
28989b63 306
49e5b2a9
WC
307 if (arg_show_cgroup)
308 /* if this fails, that's OK */
309 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
310 ps->pid, &ps->cgroup);
311
28989b63 312 /* ppid */
f2f85884 313 sprintf(filename, "%d/stat", pid);
af672f03
DM
314 fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
315 if (fd < 0)
58ec01b3 316 continue;
af672f03
DM
317
318 st = fdopen(fd, "re");
58ec01b3
AS
319 if (!st) {
320 close(fd);
28989b63 321 continue;
58ec01b3 322 }
af672f03
DM
323
324 if (!fscanf(st, "%*s %*s %*s %i", &p))
28989b63 325 continue;
af672f03 326
28989b63
TA
327 ps->ppid = p;
328
329 /*
330 * setup child pointers
331 *
332 * these are used to paint the tree coherently later
333 * each parent has a LL of children, and a LL of siblings
334 */
335 if (pid == 1)
336 continue; /* nothing to do for init atm */
337
338 /* kthreadd has ppid=0, which breaks our tree ordering */
339 if (ps->ppid == 0)
340 ps->ppid = 1;
341
342 parent = ps_first;
343 while ((parent->next_ps && parent->pid != ps->ppid))
344 parent = parent->next_ps;
345
226b735a 346 if (parent->pid != ps->ppid) {
28989b63
TA
347 /* orphan */
348 ps->ppid = 1;
349 parent = ps_first->next_ps;
350 }
351
352 ps->parent = parent;
353
354 if (!parent->children) {
355 /* it's the first child */
356 parent->children = ps;
357 } else {
358 /* walk all children and append */
359 struct ps_struct *children;
360 children = parent->children;
361 while (children->next)
362 children = children->next;
af672f03 363
28989b63
TA
364 children->next = ps;
365 }
366 }
367
368 /* else -> found pid, append data in ps */
369
370 /* below here is all continuous logging parts - we get here on every
371 * iteration */
372
373 /* rt, wt */
9964a9eb 374 if (ps->schedstat < 0) {
f2f85884 375 sprintf(filename, "%d/schedstat", pid);
af672f03
DM
376 ps->schedstat = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
377 if (ps->schedstat < 0)
28989b63
TA
378 continue;
379 }
af672f03 380
ef2648c1
LN
381 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
382 if (s <= 0) {
28989b63
TA
383 /* clean up our file descriptors - assume that the process exited */
384 close(ps->schedstat);
9964a9eb 385 ps->schedstat = -1;
af672f03 386 ps->sched = safe_close(ps->sched);
28989b63
TA
387 continue;
388 }
af672f03 389
ef2648c1
LN
390 buf[s] = '\0';
391
28989b63
TA
392 if (!sscanf(buf, "%s %s %*s", rt, wt))
393 continue;
394
955d98c9 395 ps->sample->next = new0(struct ps_sched_struct, 1);
34a4071e
DM
396 if (!ps->sample->next)
397 return log_oom();
398
8dfb6e71
NC
399 ps->sample->next->prev = ps->sample;
400 ps->sample = ps->sample->next;
401 ps->last = ps->sample;
402 ps->sample->runtime = atoll(rt);
403 ps->sample->waittime = atoll(wt);
404 ps->sample->sampledata = sampledata;
405 ps->sample->ps_new = ps;
af672f03 406 if (ps_prev)
8dfb6e71 407 ps_prev->cross = ps->sample;
af672f03 408
8dfb6e71
NC
409 ps_prev = ps->sample;
410 ps->total = (ps->last->runtime - ps->first->runtime)
411 / 1000000000.0;
28989b63 412
caa43397
GM
413 /* Take into account CPU runtime/waittime spent in non-main threads of the process
414 * by parsing "/proc/[pid]/task/[tid]/schedstat" for all [tid] != [pid]
415 * See https://github.com/systemd/systemd/issues/139
416 */
417
418 /* Browse directory "/proc/[pid]/task" to know the thread ids of process [pid] */
419 snprintf(filename, sizeof(filename), PID_FMT "/task", pid);
420 taskfd = openat(procfd, filename, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
421 if (taskfd >= 0) {
422 _cleanup_closedir_ DIR *taskdir = NULL;
423
424 taskdir = fdopendir(taskfd);
425 if (!taskdir) {
426 safe_close(taskfd);
427 return -errno;
428 }
429 FOREACH_DIRENT(ent, taskdir, break) {
430 int r;
431 int tid = -1;
432 _cleanup_close_ int tid_schedstat = -1;
433 long long delta_rt;
434 long long delta_wt;
435
436 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
437 continue;
438
439 /* Skip main thread as it was already accounted */
440 r = safe_atoi(ent->d_name, &tid);
441 if (r < 0 || tid == pid)
442 continue;
443
444 /* Parse "/proc/[pid]/task/[tid]/schedstat" */
445 snprintf(filename, sizeof(filename), PID_FMT "/schedstat", tid);
446 tid_schedstat = openat(taskfd, filename, O_RDONLY|O_CLOEXEC);
447
448 if (tid_schedstat == -1)
449 continue;
450
451 s = pread(tid_schedstat, buf, sizeof(buf) - 1, 0);
452 if (s <= 0)
453 continue;
454 buf[s] = '\0';
455
456 if (!sscanf(buf, "%s %s %*s", rt, wt))
457 continue;
458
459 r = safe_atolli(rt, &delta_rt);
460 if (r < 0)
461 continue;
462 r = safe_atolli(rt, &delta_wt);
463 if (r < 0)
464 continue;
465 ps->sample->runtime += delta_rt;
466 ps->sample->waittime += delta_wt;
467 }
468 }
469
6d031c0b 470 if (!arg_pss)
28989b63 471 goto catch_rename;
8dfb6e71 472
28989b63
TA
473 /* Pss */
474 if (!ps->smaps) {
f2f85884 475 sprintf(filename, "%d/smaps", pid);
af672f03
DM
476 fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
477 if (fd < 0)
58ec01b3 478 continue;
af672f03 479 ps->smaps = fdopen(fd, "re");
58ec01b3
AS
480 if (!ps->smaps) {
481 close(fd);
28989b63 482 continue;
58ec01b3 483 }
28989b63 484 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
af672f03 485 } else {
8dfb6e71
NC
486 rewind(ps->smaps);
487 }
af672f03 488
8dfb6e71
NC
489 /* test to see if we need to skip another field */
490 if (skip == 0) {
491 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
492 continue;
493 }
494 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
495 continue;
496 }
497 if (buf[392] == 'V') {
498 skip = 2;
499 }
500 else {
501 skip = 1;
502 }
28989b63
TA
503 rewind(ps->smaps);
504 }
af672f03 505
28989b63
TA
506 while (1) {
507 int pss_kb;
508
8dfb6e71
NC
509 /* skip one line, this contains the object mapped. */
510 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
28989b63 511 break;
8dfb6e71 512 }
28989b63 513 /* then there's a 28 char 14 line block */
8dfb6e71 514 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
28989b63 515 break;
8dfb6e71 516 }
28989b63 517 pss_kb = atoi(&buf[61]);
8dfb6e71 518 ps->sample->pss += pss_kb;
28989b63 519
8dfb6e71
NC
520 /* skip one more line if this is a newer kernel */
521 if (skip == 2) {
522 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
523 break;
524 }
525 }
af672f03 526
8dfb6e71
NC
527 if (ps->sample->pss > ps->pss_max)
528 ps->pss_max = ps->sample->pss;
83fdc450
AK
529
530catch_rename:
28989b63 531 /* catch process rename, try to randomize time */
6d031c0b 532 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
1f2ecb03 533 if (((sample - ps->pid) + pid) % (int)(mod) == 0) {
28989b63
TA
534
535 /* re-fetch name */
536 /* get name, start time */
eaf15609 537 if (ps->sched < 0) {
f2f85884 538 sprintf(filename, "%d/sched", pid);
af672f03
DM
539 ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC);
540 if (ps->sched < 0)
28989b63
TA
541 continue;
542 }
af672f03 543
ef2648c1
LN
544 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
545 if (s <= 0) {
28989b63 546 /* clean up file descriptors */
af672f03
DM
547 ps->sched = safe_close(ps->sched);
548 ps->schedstat = safe_close(ps->schedstat);
28989b63
TA
549 continue;
550 }
af672f03 551
ef2648c1 552 buf[s] = '\0';
28989b63
TA
553
554 if (!sscanf(buf, "%s %*s %*s", key))
555 continue;
556
c309a713 557 strscpy(ps->name, sizeof(ps->name), key);
e90f9fa4
HH
558
559 /* cmdline */
6d031c0b 560 if (arg_show_cmdline)
f9178132 561 pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid);
28989b63
TA
562 }
563 }
34a4071e
DM
564
565 return 0;
83fdc450 566}