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