chiark / gitweb /
1e3f4a9e8c9c1b359c212f6dda3a57e964bb6018
[elogind.git] / src / bootchart / log.c
1 /***
2   log.c - This file is part of systemd-bootchart
3
4   Copyright (C) 2009-2013 Intel Coproration
5
6   Authors:
7     Auke Kok <auke-jan.h.kok@intel.com>
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21  ***/
22
23 #define _GNU_SOURCE 1
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include <limits.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <dirent.h>
32 #include <fcntl.h>
33 #include <time.h>
34
35
36 #include "bootchart.h"
37 #include "util.h"
38
39 /*
40  * Alloc a static 4k buffer for stdio - primarily used to increase
41  * PSS buffering from the default 1k stdin buffer to reduce
42  * read() overhead.
43  */
44 static char smaps_buf[4096];
45 DIR *proc;
46 int procfd=-1;
47
48 double gettime_ns(void)
49 {
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
58 void log_uptime(void)
59 {
60         FILE *f;
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                 fclose(f);
70                 return;
71         }
72         fclose(f);
73         uptime = strtod(str, NULL);
74
75         log_start = gettime_ns();
76
77         /* start graph at kernel boot time */
78         if (relative)
79                 graph_start = log_start;
80         else
81                 graph_start = log_start - uptime;
82 }
83
84
85 static char *bufgetline(char *buf)
86 {
87         char *c;
88
89         if (!buf)
90                 return NULL;
91
92         c = strchr(buf, '\n');
93         if (c)
94                 c++;
95         return c;
96 }
97
98 static int pid_cmdline_strncpy(char *buffer, int pid, size_t buf_len) {
99         char filename[PATH_MAX];
100         int _cleanup_close_ fd=-1;
101         ssize_t n;
102
103         sprintf(filename, "%d/cmdline", pid);
104         fd = openat(procfd, filename, O_RDONLY);
105         if (fd < 0)
106                 return -errno;
107
108         n = read(fd, buffer, buf_len-1);
109         if (n > 0) {
110                 int i;
111                 for (i = 0; i < n; i++)
112                         if (buffer[i] == '\0')
113                                 buffer[i] = ' ';
114                 buffer[n] = '\0';
115         }
116         return 0;
117 }
118
119 void log_sample(int sample)
120 {
121         static int vmstat;
122         static int schedstat;
123         FILE *st;
124         char buf[4095];
125         char key[256];
126         char val[256];
127         char rt[256];
128         char wt[256];
129         char *m;
130         int c;
131         int p;
132         int mod;
133         static int e_fd;
134         ssize_t s;
135         ssize_t n;
136         struct dirent *ent;
137         int fd;
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                         blockstat[sample].bi = atoi(val);
172                 if (streq(key, "pgpgout")) {
173                         blockstat[sample].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                         cpustat[c].sample[sample].runtime = atoll(rt);
209                         cpustat[c].sample[sample].waittime = 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 (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                                 entropy_avail[sample] = 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                         char t[32];
257                         struct ps_struct *parent;
258
259                         ps->next_ps = calloc(1, sizeof(struct ps_struct));
260                         if (!ps->next_ps) {
261                                 perror("calloc(ps_struct)");
262                                 exit (EXIT_FAILURE);
263                         }
264                         ps = ps->next_ps;
265                         ps->pid = pid;
266
267                         ps->sample = calloc(len + 1, sizeof(struct ps_sched_struct));
268                         if (!ps->sample) {
269                                 perror("calloc(ps_struct)");
270                                 exit (EXIT_FAILURE);
271                         }
272
273                         pscount++;
274
275                         /* mark our first sample */
276                         ps->first = sample;
277
278                         /* get name, start time */
279                         if (!ps->sched) {
280                                 sprintf(filename, "%d/sched", pid);
281                                 ps->sched = openat(procfd, filename, O_RDONLY);
282                                 if (ps->sched == -1)
283                                         continue;
284                         }
285
286                         s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
287                         if (s <= 0) {
288                                 close(ps->sched);
289                                 continue;
290                         }
291                         buf[s] = '\0';
292
293                         if (!sscanf(buf, "%s %*s %*s", key))
294                                 continue;
295
296                         strncpy(ps->name, key, 256);
297
298                         /* cmdline */
299                         if (show_cmdline)
300                                 pid_cmdline_strncpy(ps->name, pid, 256);
301
302                         /* discard line 2 */
303                         m = bufgetline(buf);
304                         if (!m)
305                                 continue;
306
307                         m = bufgetline(m);
308                         if (!m)
309                                 continue;
310
311                         if (!sscanf(m, "%*s %*s %s", t))
312                                 continue;
313
314                         ps->starttime = strtod(t, NULL) / 1000.0;
315
316                         /* ppid */
317                         sprintf(filename, "%d/stat", pid);
318                         fd = openat(procfd, filename, O_RDONLY);
319                         st = fdopen(fd, "r");
320                         if (!st)
321                                 continue;
322                         if (!fscanf(st, "%*s %*s %*s %i", &p)) {
323                                 fclose(st);
324                                 continue;
325                         }
326                         fclose(st);
327                         ps->ppid = p;
328
329                         /*
330                          * setup child pointers
331                          *
332                          * these are used to paint the tree coherently later
333                          * each parent has a LL of children, and a LL of siblings
334                          */
335                         if (pid == 1)
336                                 continue; /* nothing to do for init atm */
337
338                         /* kthreadd has ppid=0, which breaks our tree ordering */
339                         if (ps->ppid == 0)
340                                 ps->ppid = 1;
341
342                         parent = ps_first;
343                         while ((parent->next_ps && parent->pid != ps->ppid))
344                                 parent = parent->next_ps;
345
346                         if ((!parent) || (parent->pid != ps->ppid)) {
347                                 /* orphan */
348                                 ps->ppid = 1;
349                                 parent = ps_first->next_ps;
350                         }
351
352                         ps->parent = parent;
353
354                         if (!parent->children) {
355                                 /* it's the first child */
356                                 parent->children = ps;
357                         } else {
358                                 /* walk all children and append */
359                                 struct ps_struct *children;
360                                 children = parent->children;
361                                 while (children->next)
362                                         children = children->next;
363                                 children->next = ps;
364                         }
365                 }
366
367                 /* else -> found pid, append data in ps */
368
369                 /* below here is all continuous logging parts - we get here on every
370                  * iteration */
371
372                 /* rt, wt */
373                 if (!ps->schedstat) {
374                         sprintf(filename, "%d/schedstat", pid);
375                         ps->schedstat = openat(procfd, filename, O_RDONLY);
376                         if (ps->schedstat == -1)
377                                 continue;
378                 }
379                 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
380                 if (s <= 0) {
381                         /* clean up our file descriptors - assume that the process exited */
382                         close(ps->schedstat);
383                         if (ps->sched)
384                                 close(ps->sched);
385                         //if (ps->smaps)
386                         //        fclose(ps->smaps);
387                         continue;
388                 }
389                 buf[s] = '\0';
390
391                 if (!sscanf(buf, "%s %s %*s", rt, wt))
392                         continue;
393
394                 ps->last = sample;
395                 ps->sample[sample].runtime = atoll(rt);
396                 ps->sample[sample].waittime = atoll(wt);
397
398                 ps->total = (ps->sample[ps->last].runtime
399                                  - ps->sample[ps->first].runtime)
400                                  / 1000000000.0;
401
402                 if (!pss)
403                         goto catch_rename;
404                 /* Pss */
405                 if (!ps->smaps) {
406                         sprintf(filename, "%d/smaps", pid);
407                         fd = openat(procfd, filename, O_RDONLY);
408                         ps->smaps = fdopen(fd, "r");
409                         if (!ps->smaps)
410                                 continue;
411                         setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
412                 } else {
413                         rewind(ps->smaps);
414                 }
415
416                 while (1) {
417                         int pss_kb;
418
419                         /* skip one line, this contains the object mapped */
420                         if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
421                                 break;
422                         /* then there's a 28 char 14 line block */
423                         if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14)
424                                 break;
425
426                         pss_kb = atoi(&buf[61]);
427                         ps->sample[sample].pss += pss_kb;
428                 }
429
430                 if (ps->sample[sample].pss > ps->pss_max)
431                         ps->pss_max = ps->sample[sample].pss;
432
433 catch_rename:
434                 /* catch process rename, try to randomize time */
435                 mod = (hz < 4.0) ? 4.0 : (hz / 4.0);
436                 if (((samples - ps->first) + pid) % (int)(mod) == 0) {
437
438                         /* re-fetch name */
439                         /* get name, start time */
440                         if (!ps->sched) {
441                                 sprintf(filename, "%d/sched", pid);
442                                 ps->sched = openat(procfd, filename, O_RDONLY);
443                                 if (ps->sched == -1)
444                                         continue;
445                         }
446                         s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
447                         if (s <= 0) {
448                                 /* clean up file descriptors */
449                                 close(ps->sched);
450                                 if (ps->schedstat)
451                                         close(ps->schedstat);
452                                 //if (ps->smaps)
453                                 //        fclose(ps->smaps);
454                                 continue;
455                         }
456                         buf[s] = '\0';
457
458                         if (!sscanf(buf, "%s %*s %*s", key))
459                                 continue;
460
461                         strncpy(ps->name, key, 256);
462
463                         /* cmdline */
464                         if (show_cmdline)
465                                 pid_cmdline_strncpy(ps->name, pid, 256);
466                 }
467         }
468 }