chiark / gitweb /
bootchart: more useful error message for common error
[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;
112         static int schedstat;
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;
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) {
144                 /* block stuff */
145                 vmstat = openat(procfd, "vmstat", O_RDONLY);
146                 if (vmstat == -1) {
147                         log_error_errno(errno, "Failed to open /proc/vmstat: %m");
148                         exit(EXIT_FAILURE);
149                 }
150         }
151
152         n = pread(vmstat, buf, sizeof(buf) - 1, 0);
153         if (n <= 0) {
154                 close(vmstat);
155                 return;
156         }
157         buf[n] = '\0';
158
159         m = buf;
160         while (m) {
161                 if (sscanf(m, "%s %s", key, val) < 2)
162                         goto vmstat_next;
163                 if (streq(key, "pgpgin"))
164                         sampledata->blockstat.bi = atoi(val);
165                 if (streq(key, "pgpgout")) {
166                         sampledata->blockstat.bo = atoi(val);
167                         break;
168                 }
169 vmstat_next:
170                 m = bufgetline(m);
171                 if (!m)
172                         break;
173         }
174
175         if (!schedstat) {
176                 /* overall CPU utilization */
177                 schedstat = openat(procfd, "schedstat", O_RDONLY);
178                 if (schedstat == -1) {
179                         log_error_errno(errno, "Failed to open /proc/schedstat (requires CONFIG_SCHEDSTATS=y in kernel config): %m");
180                         exit(EXIT_FAILURE);
181                 }
182         }
183
184         n = pread(schedstat, buf, sizeof(buf) - 1, 0);
185         if (n <= 0) {
186                 close(schedstat);
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) {
217                         e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
218                 }
219
220                 if (e_fd) {
221                         n = pread(e_fd, buf, sizeof(buf) - 1, 0);
222                         if (n > 0) {
223                                 buf[n] = '\0';
224                                 sampledata->entropy_avail = atoi(buf);
225                         }
226                 }
227         }
228
229         while ((ent = readdir(proc)) != NULL) {
230                 char filename[PATH_MAX];
231                 int pid;
232                 struct ps_struct *ps;
233
234                 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
235                         continue;
236
237                 pid = atoi(ent->d_name);
238
239                 if (pid >= MAXPIDS)
240                         continue;
241
242                 ps = ps_first;
243                 while (ps->next_ps) {
244                         ps = ps->next_ps;
245                         if (ps->pid == pid)
246                                 break;
247                 }
248
249                 /* end of our LL? then append a new record */
250                 if (ps->pid != pid) {
251                         _cleanup_fclose_ FILE *st = NULL;
252                         char t[32];
253                         struct ps_struct *parent;
254                         int r;
255
256                         ps->next_ps = new0(struct ps_struct, 1);
257                         if (!ps->next_ps) {
258                                 log_oom();
259                                 exit (EXIT_FAILURE);
260                         }
261                         ps = ps->next_ps;
262                         ps->pid = pid;
263
264                         ps->sample = new0(struct ps_sched_struct, 1);
265                         if (!ps->sample) {
266                                 log_oom();
267                                 exit (EXIT_FAILURE);
268                         }
269                         ps->sample->sampledata = sampledata;
270
271                         pscount++;
272
273                         /* mark our first sample */
274                         ps->first = ps->last = ps->sample;
275                         ps->sample->runtime = atoll(rt);
276                         ps->sample->waittime = atoll(wt);
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                         strscpy(ps->name, sizeof(ps->name), key);
297
298                         /* cmdline */
299                         if (arg_show_cmdline)
300                                 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
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                         r = safe_atod(t, &ps->starttime);
315                         if (r < 0)
316                                 continue;
317
318                         ps->starttime /= 1000.0;
319
320                         if (arg_show_cgroup)
321                                 /* if this fails, that's OK */
322                                 cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER,
323                                                 ps->pid, &ps->cgroup);
324
325                         /* ppid */
326                         sprintf(filename, "%d/stat", pid);
327                         fd = openat(procfd, filename, O_RDONLY);
328                         st = fdopen(fd, "r");
329                         if (!st)
330                                 continue;
331                         if (!fscanf(st, "%*s %*s %*s %i", &p)) {
332                                 continue;
333                         }
334                         ps->ppid = p;
335
336                         /*
337                          * setup child pointers
338                          *
339                          * these are used to paint the tree coherently later
340                          * each parent has a LL of children, and a LL of siblings
341                          */
342                         if (pid == 1)
343                                 continue; /* nothing to do for init atm */
344
345                         /* kthreadd has ppid=0, which breaks our tree ordering */
346                         if (ps->ppid == 0)
347                                 ps->ppid = 1;
348
349                         parent = ps_first;
350                         while ((parent->next_ps && parent->pid != ps->ppid))
351                                 parent = parent->next_ps;
352
353                         if (parent->pid != ps->ppid) {
354                                 /* orphan */
355                                 ps->ppid = 1;
356                                 parent = ps_first->next_ps;
357                         }
358
359                         ps->parent = parent;
360
361                         if (!parent->children) {
362                                 /* it's the first child */
363                                 parent->children = ps;
364                         } else {
365                                 /* walk all children and append */
366                                 struct ps_struct *children;
367                                 children = parent->children;
368                                 while (children->next)
369                                         children = children->next;
370                                 children->next = ps;
371                         }
372                 }
373
374                 /* else -> found pid, append data in ps */
375
376                 /* below here is all continuous logging parts - we get here on every
377                  * iteration */
378
379                 /* rt, wt */
380                 if (!ps->schedstat) {
381                         sprintf(filename, "%d/schedstat", pid);
382                         ps->schedstat = openat(procfd, filename, O_RDONLY);
383                         if (ps->schedstat == -1)
384                                 continue;
385                 }
386                 s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0);
387                 if (s <= 0) {
388                         /* clean up our file descriptors - assume that the process exited */
389                         close(ps->schedstat);
390                         if (ps->sched)
391                                 close(ps->sched);
392                         //if (ps->smaps)
393                         //        fclose(ps->smaps);
394                         continue;
395                 }
396                 buf[s] = '\0';
397
398                 if (!sscanf(buf, "%s %s %*s", rt, wt))
399                         continue;
400
401                 ps->sample->next = new0(struct ps_sched_struct, 1);
402                 if (!ps->sample->next) {
403                         log_oom();
404                         exit(EXIT_FAILURE);
405                 }
406                 ps->sample->next->prev = ps->sample;
407                 ps->sample = ps->sample->next;
408                 ps->last = ps->sample;
409                 ps->sample->runtime = atoll(rt);
410                 ps->sample->waittime = atoll(wt);
411                 ps->sample->sampledata = sampledata;
412                 ps->sample->ps_new = ps;
413                 if (ps_prev) {
414                         ps_prev->cross = ps->sample;
415                 }
416                 ps_prev = ps->sample;
417                 ps->total = (ps->last->runtime - ps->first->runtime)
418                             / 1000000000.0;
419
420                 if (!arg_pss)
421                         goto catch_rename;
422
423                 /* Pss */
424                 if (!ps->smaps) {
425                         sprintf(filename, "%d/smaps", pid);
426                         fd = openat(procfd, filename, O_RDONLY);
427                         ps->smaps = fdopen(fd, "r");
428                         if (!ps->smaps)
429                                 continue;
430                         setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
431                 }
432                 else {
433                         rewind(ps->smaps);
434                 }
435                 /* test to see if we need to skip another field */
436                 if (skip == 0) {
437                         if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
438                                 continue;
439                         }
440                         if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
441                                 continue;
442                         }
443                         if (buf[392] == 'V') {
444                                 skip = 2;
445                         }
446                         else {
447                                 skip = 1;
448                         }
449                         rewind(ps->smaps);
450                 }
451                 while (1) {
452                         int pss_kb;
453
454                         /* skip one line, this contains the object mapped. */
455                         if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
456                                 break;
457                         }
458                         /* then there's a 28 char 14 line block */
459                         if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
460                                 break;
461                         }
462                         pss_kb = atoi(&buf[61]);
463                         ps->sample->pss += pss_kb;
464
465                         /* skip one more line if this is a newer kernel */
466                         if (skip == 2) {
467                                if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
468                                        break;
469                         }
470                 }
471                 if (ps->sample->pss > ps->pss_max)
472                         ps->pss_max = ps->sample->pss;
473
474 catch_rename:
475                 /* catch process rename, try to randomize time */
476                 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
477                 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
478
479                         /* re-fetch name */
480                         /* get name, start time */
481                         if (!ps->sched) {
482                                 sprintf(filename, "%d/sched", pid);
483                                 ps->sched = openat(procfd, filename, O_RDONLY);
484                                 if (ps->sched == -1)
485                                         continue;
486                         }
487                         s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
488                         if (s <= 0) {
489                                 /* clean up file descriptors */
490                                 close(ps->sched);
491                                 if (ps->schedstat)
492                                         close(ps->schedstat);
493                                 //if (ps->smaps)
494                                 //        fclose(ps->smaps);
495                                 continue;
496                         }
497                         buf[s] = '\0';
498
499                         if (!sscanf(buf, "%s %*s %*s", key))
500                                 continue;
501
502                         strscpy(ps->name, sizeof(ps->name), key);
503
504                         /* cmdline */
505                         if (arg_show_cmdline)
506                                 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
507                 }
508         }
509 }