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