chiark / gitweb /
Fix for SIGSEGV in systemd-bootchart on short-living processes
[elogind.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 }