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