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