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