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