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