]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/bootchart/store.c
bootchart: clean up control flow logic
[thirdparty/systemd.git] / src / bootchart / store.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright (C) 2009-2013 Intel Corporation
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 ***/
24
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <limits.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <dirent.h>
31 #include <fcntl.h>
32 #include <time.h>
33
34 #include "util.h"
35 #include "time-util.h"
36 #include "strxcpyx.h"
37 #include "store.h"
38 #include "bootchart.h"
39 #include "cgroup-util.h"
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 */
46 static char smaps_buf[4096];
47 static int skip = 0;
48 DIR *proc;
49 int procfd = -1;
50
51 double gettime_ns(void) {
52 struct timespec n;
53
54 clock_gettime(CLOCK_MONOTONIC, &n);
55
56 return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
57 }
58
59 static double gettime_up(void) {
60 struct timespec n;
61
62 clock_gettime(CLOCK_BOOTTIME, &n);
63 return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC));
64 }
65
66 void log_uptime(void) {
67 if (arg_relative)
68 graph_start = log_start = gettime_ns();
69 else {
70 double uptime = gettime_up();
71
72 log_start = gettime_ns();
73 graph_start = log_start - uptime;
74 }
75 }
76
77 static char *bufgetline(char *buf) {
78 char *c;
79
80 if (!buf)
81 return NULL;
82
83 c = strchr(buf, '\n');
84 if (c)
85 c++;
86 return c;
87 }
88
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;
92 ssize_t n;
93
94 sprintf(filename, "%d/cmdline", pid);
95 fd = openat(procfd, filename, O_RDONLY);
96 if (fd < 0)
97 return -errno;
98
99 n = read(fd, buffer, buf_len-1);
100 if (n > 0) {
101 int i;
102 for (i = 0; i < n; i++)
103 if (buffer[i] == '\0')
104 buffer[i] = ' ';
105 buffer[n] = '\0';
106 }
107 return 0;
108 }
109
110 int log_sample(int sample, struct list_sample_data **ptr) {
111 static int vmstat = -1;
112 static int schedstat = -1;
113 char buf[4096];
114 char key[256];
115 char val[256];
116 char rt[256];
117 char wt[256];
118 char *m;
119 int c;
120 int p;
121 int mod;
122 static int e_fd = -1;
123 ssize_t s;
124 ssize_t n;
125 struct dirent *ent;
126 int fd;
127 struct list_sample_data *sampledata;
128 struct ps_sched_struct *ps_prev = NULL;
129
130 sampledata = *ptr;
131
132 /* all the per-process stuff goes here */
133 if (!proc) {
134 /* find all processes */
135 proc = opendir("/proc");
136 if (!proc)
137 return -errno;
138 procfd = dirfd(proc);
139 } else {
140 rewinddir(proc);
141 }
142
143 if (vmstat < 0) {
144 /* block stuff */
145 vmstat = openat(procfd, "vmstat", O_RDONLY);
146 if (vmstat == -1)
147 return log_error_errno(errno, "Failed to open /proc/vmstat: %m");
148 }
149
150 n = pread(vmstat, buf, sizeof(buf) - 1, 0);
151 if (n <= 0) {
152 vmstat = safe_close(vmstat);
153 if (n < 0)
154 return -errno;
155 return -ENODATA;
156 }
157 buf[n] = '\0';
158
159 m = buf;
160 while (m) {
161 if (sscanf(m, "%s %s", key, val) < 2)
162 goto vmstat_next;
163 if (streq(key, "pgpgin"))
164 sampledata->blockstat.bi = atoi(val);
165 if (streq(key, "pgpgout")) {
166 sampledata->blockstat.bo = atoi(val);
167 break;
168 }
169 vmstat_next:
170 m = bufgetline(m);
171 if (!m)
172 break;
173 }
174
175 if (schedstat < 0) {
176 /* overall CPU utilization */
177 schedstat = openat(procfd, "schedstat", O_RDONLY);
178 if (schedstat == -1)
179 return log_error_errno(errno, "Failed to open /proc/schedstat (requires CONFIG_SCHEDSTATS=y in kernel config): %m");
180 }
181
182 n = pread(schedstat, buf, sizeof(buf) - 1, 0);
183 if (n <= 0) {
184 schedstat = safe_close(schedstat);
185 if (n < 0)
186 return -errno;
187 return -ENODATA;
188 }
189 buf[n] = '\0';
190
191 m = buf;
192 while (m) {
193 int r;
194
195 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
196 goto schedstat_next;
197
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 */
202 break;
203 sampledata->runtime[c] = atoll(rt);
204 sampledata->waittime[c] = atoll(wt);
205
206 if (c == cpus)
207 cpus = c + 1;
208 }
209 schedstat_next:
210 m = bufgetline(m);
211 if (!m)
212 break;
213 }
214
215 if (arg_entropy) {
216 if (e_fd < 0) {
217 e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
218 if (e_fd == -1)
219 return log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m");
220 }
221
222 n = pread(e_fd, buf, sizeof(buf) - 1, 0);
223 if (n <= 0) {
224 close(e_fd);
225 e_fd = -1;
226 } else {
227 buf[n] = '\0';
228 sampledata->entropy_avail = atoi(buf);
229 }
230 }
231
232 while ((ent = readdir(proc)) != NULL) {
233 char filename[PATH_MAX];
234 int pid;
235 struct ps_struct *ps;
236
237 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
238 continue;
239
240 pid = atoi(ent->d_name);
241
242 if (pid >= MAXPIDS)
243 continue;
244
245 ps = ps_first;
246 while (ps->next_ps) {
247 ps = ps->next_ps;
248 if (ps->pid == pid)
249 break;
250 }
251
252 /* end of our LL? then append a new record */
253 if (ps->pid != pid) {
254 _cleanup_fclose_ FILE *st = NULL;
255 char t[32];
256 struct ps_struct *parent;
257 int r;
258
259 ps->next_ps = new0(struct ps_struct, 1);
260 if (!ps->next_ps)
261 return log_oom();
262
263 ps = ps->next_ps;
264 ps->pid = pid;
265 ps->sched = -1;
266 ps->schedstat = -1;
267
268 ps->sample = new0(struct ps_sched_struct, 1);
269 if (!ps->sample)
270 return log_oom();
271
272 ps->sample->sampledata = sampledata;
273
274 pscount++;
275
276 /* mark our first sample */
277 ps->first = ps->last = ps->sample;
278 ps->sample->runtime = atoll(rt);
279 ps->sample->waittime = atoll(wt);
280
281 /* get name, start time */
282 if (ps->sched < 0) {
283 sprintf(filename, "%d/sched", pid);
284 ps->sched = openat(procfd, filename, O_RDONLY);
285 if (ps->sched == -1)
286 continue;
287 }
288
289 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
290 if (s <= 0) {
291 close(ps->sched);
292 ps->sched = -1;
293 continue;
294 }
295 buf[s] = '\0';
296
297 if (!sscanf(buf, "%s %*s %*s", key))
298 continue;
299
300 strscpy(ps->name, sizeof(ps->name), key);
301
302 /* cmdline */
303 if (arg_show_cmdline)
304 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
305
306 /* discard line 2 */
307 m = bufgetline(buf);
308 if (!m)
309 continue;
310
311 m = bufgetline(m);
312 if (!m)
313 continue;
314
315 if (!sscanf(m, "%*s %*s %s", t))
316 continue;
317
318 r = safe_atod(t, &ps->starttime);
319 if (r < 0)
320 continue;
321
322 ps->starttime /= 1000.0;
323
324 if (arg_show_cgroup)
325 /* if this fails, that's OK */
326 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
327 ps->pid, &ps->cgroup);
328
329 /* ppid */
330 sprintf(filename, "%d/stat", pid);
331 fd = openat(procfd, filename, O_RDONLY);
332 if (fd == -1)
333 continue;
334 st = fdopen(fd, "r");
335 if (!st) {
336 close(fd);
337 continue;
338 }
339 if (!fscanf(st, "%*s %*s %*s %i", &p)) {
340 continue;
341 }
342 ps->ppid = p;
343
344 /*
345 * setup child pointers
346 *
347 * these are used to paint the tree coherently later
348 * each parent has a LL of children, and a LL of siblings
349 */
350 if (pid == 1)
351 continue; /* nothing to do for init atm */
352
353 /* kthreadd has ppid=0, which breaks our tree ordering */
354 if (ps->ppid == 0)
355 ps->ppid = 1;
356
357 parent = ps_first;
358 while ((parent->next_ps && parent->pid != ps->ppid))
359 parent = parent->next_ps;
360
361 if (parent->pid != ps->ppid) {
362 /* orphan */
363 ps->ppid = 1;
364 parent = ps_first->next_ps;
365 }
366
367 ps->parent = parent;
368
369 if (!parent->children) {
370 /* it's the first child */
371 parent->children = ps;
372 } else {
373 /* walk all children and append */
374 struct ps_struct *children;
375 children = parent->children;
376 while (children->next)
377 children = children->next;
378 children->next = ps;
379 }
380 }
381
382 /* else -> found pid, append data in ps */
383
384 /* below here is all continuous logging parts - we get here on every
385 * iteration */
386
387 /* rt, wt */
388 if (ps->schedstat < 0) {
389 sprintf(filename, "%d/schedstat", pid);
390 ps->schedstat = openat(procfd, filename, O_RDONLY);
391 if (ps->schedstat == -1)
392 continue;
393 }
394 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
395 if (s <= 0) {
396 /* clean up our file descriptors - assume that the process exited */
397 close(ps->schedstat);
398 ps->schedstat = -1;
399 if (ps->sched) {
400 close(ps->sched);
401 ps->sched = -1;
402 }
403 //if (ps->smaps)
404 // fclose(ps->smaps);
405 continue;
406 }
407 buf[s] = '\0';
408
409 if (!sscanf(buf, "%s %s %*s", rt, wt))
410 continue;
411
412 ps->sample->next = new0(struct ps_sched_struct, 1);
413 if (!ps->sample->next)
414 return log_oom();
415
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;
423 if (ps_prev) {
424 ps_prev->cross = ps->sample;
425 }
426 ps_prev = ps->sample;
427 ps->total = (ps->last->runtime - ps->first->runtime)
428 / 1000000000.0;
429
430 if (!arg_pss)
431 goto catch_rename;
432
433 /* Pss */
434 if (!ps->smaps) {
435 sprintf(filename, "%d/smaps", pid);
436 fd = openat(procfd, filename, O_RDONLY);
437 if (fd == -1)
438 continue;
439 ps->smaps = fdopen(fd, "r");
440 if (!ps->smaps) {
441 close(fd);
442 continue;
443 }
444 setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
445 }
446 else {
447 rewind(ps->smaps);
448 }
449 /* test to see if we need to skip another field */
450 if (skip == 0) {
451 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
452 continue;
453 }
454 if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
455 continue;
456 }
457 if (buf[392] == 'V') {
458 skip = 2;
459 }
460 else {
461 skip = 1;
462 }
463 rewind(ps->smaps);
464 }
465 while (1) {
466 int pss_kb;
467
468 /* skip one line, this contains the object mapped. */
469 if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
470 break;
471 }
472 /* then there's a 28 char 14 line block */
473 if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
474 break;
475 }
476 pss_kb = atoi(&buf[61]);
477 ps->sample->pss += pss_kb;
478
479 /* skip one more line if this is a newer kernel */
480 if (skip == 2) {
481 if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
482 break;
483 }
484 }
485 if (ps->sample->pss > ps->pss_max)
486 ps->pss_max = ps->sample->pss;
487
488 catch_rename:
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) {
492
493 /* re-fetch name */
494 /* get name, start time */
495 if (!ps->sched) {
496 sprintf(filename, "%d/sched", pid);
497 ps->sched = openat(procfd, filename, O_RDONLY);
498 if (ps->sched == -1)
499 continue;
500 }
501 s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
502 if (s <= 0) {
503 /* clean up file descriptors */
504 close(ps->sched);
505 ps->sched = -1;
506 if (ps->schedstat) {
507 close(ps->schedstat);
508 ps->schedstat = -1;
509 }
510 //if (ps->smaps)
511 // fclose(ps->smaps);
512 continue;
513 }
514 buf[s] = '\0';
515
516 if (!sscanf(buf, "%s %*s %*s", key))
517 continue;
518
519 strscpy(ps->name, sizeof(ps->name), key);
520
521 /* cmdline */
522 if (arg_show_cmdline)
523 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
524 }
525 }
526
527 return 0;
528 }