chiark / gitweb /
Do not print invalid UTF-8 in error messages
[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
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 / 1000000000.0));
57 }
58
59 void log_uptime(void) {
60         _cleanup_fclose_ FILE *f = NULL;
61         char str[32];
62         double uptime;
63
64         f = fopen("/proc/uptime", "re");
65
66         if (!f)
67                 return;
68         if (!fscanf(f, "%s %*s", str))
69                 return;
70
71         uptime = strtod(str, NULL);
72
73         log_start = gettime_ns();
74
75         /* start graph at kernel boot time */
76         if (arg_relative)
77                 graph_start = log_start;
78         else
79                 graph_start = log_start - uptime;
80 }
81
82 static char *bufgetline(char *buf) {
83         char *c;
84
85         if (!buf)
86                 return NULL;
87
88         c = strchr(buf, '\n');
89         if (c)
90                 c++;
91         return c;
92 }
93
94 static int pid_cmdline_strscpy(char *buffer, size_t buf_len, int pid) {
95         char filename[PATH_MAX];
96         _cleanup_close_ int fd=-1;
97         ssize_t n;
98
99         sprintf(filename, "%d/cmdline", pid);
100         fd = openat(procfd, filename, O_RDONLY);
101         if (fd < 0)
102                 return -errno;
103
104         n = read(fd, buffer, buf_len-1);
105         if (n > 0) {
106                 int i;
107                 for (i = 0; i < n; i++)
108                         if (buffer[i] == '\0')
109                                 buffer[i] = ' ';
110                 buffer[n] = '\0';
111         }
112         return 0;
113 }
114
115 void log_sample(int sample, struct list_sample_data **ptr) {
116         static int vmstat;
117         static int schedstat;
118         char buf[4096];
119         char key[256];
120         char val[256];
121         char rt[256];
122         char wt[256];
123         char *m;
124         int c;
125         int p;
126         int mod;
127         static int e_fd;
128         ssize_t s;
129         ssize_t n;
130         struct dirent *ent;
131         int fd;
132         struct list_sample_data *sampledata;
133         struct ps_sched_struct *ps_prev = NULL;
134
135         sampledata = *ptr;
136
137         /* all the per-process stuff goes here */
138         if (!proc) {
139                 /* find all processes */
140                 proc = opendir("/proc");
141                 if (!proc)
142                         return;
143                 procfd = dirfd(proc);
144         } else {
145                 rewinddir(proc);
146         }
147
148         if (!vmstat) {
149                 /* block stuff */
150                 vmstat = openat(procfd, "vmstat", O_RDONLY);
151                 if (vmstat == -1) {
152                         log_error("Failed to open /proc/vmstat: %m");
153                         exit(EXIT_FAILURE);
154                 }
155         }
156
157         n = pread(vmstat, buf, sizeof(buf) - 1, 0);
158         if (n <= 0) {
159                 close(vmstat);
160                 return;
161         }
162         buf[n] = '\0';
163
164         m = buf;
165         while (m) {
166                 if (sscanf(m, "%s %s", key, val) < 2)
167                         goto vmstat_next;
168                 if (streq(key, "pgpgin"))
169                         sampledata->blockstat.bi = atoi(val);
170                 if (streq(key, "pgpgout")) {
171                         sampledata->blockstat.bo = atoi(val);
172                         break;
173                 }
174 vmstat_next:
175                 m = bufgetline(m);
176                 if (!m)
177                         break;
178         }
179
180         if (!schedstat) {
181                 /* overall CPU utilization */
182                 schedstat = openat(procfd, "schedstat", O_RDONLY);
183                 if (schedstat == -1) {
184                         log_error("Failed to open /proc/schedstat: %m");
185                         exit(EXIT_FAILURE);
186                 }
187         }
188
189         n = pread(schedstat, buf, sizeof(buf) - 1, 0);
190         if (n <= 0) {
191                 close(schedstat);
192                 return;
193         }
194         buf[n] = '\0';
195
196         m = buf;
197         while (m) {
198                 if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3)
199                         goto schedstat_next;
200
201                 if (strstr(key, "cpu")) {
202                         c = atoi((const char*)(key+3));
203                         if (c > MAXCPUS)
204                                 /* Oops, we only have room for MAXCPUS data */
205                                 break;
206                         sampledata->runtime[c] = atoll(rt);
207                         sampledata->waittime[c] = atoll(wt);
208
209                         if (c == cpus)
210                                 cpus = c + 1;
211                 }
212 schedstat_next:
213                 m = bufgetline(m);
214                 if (!m)
215                         break;
216         }
217
218         if (arg_entropy) {
219                 if (!e_fd) {
220                         e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY);
221                 }
222
223                 if (e_fd) {
224                         n = pread(e_fd, buf, sizeof(buf) - 1, 0);
225                         if (n > 0) {
226                                 buf[n] = '\0';
227                                 sampledata->entropy_avail = atoi(buf);
228                         }
229                 }
230         }
231
232         while ((ent = readdir(proc)) != NULL) {
233                 char filename[PATH_MAX];
234                 int pid;
235                 struct ps_struct *ps;
236
237                 if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9'))
238                         continue;
239
240                 pid = atoi(ent->d_name);
241
242                 if (pid >= MAXPIDS)
243                         continue;
244
245                 ps = ps_first;
246                 while (ps->next_ps) {
247                         ps = ps->next_ps;
248                         if (ps->pid == pid)
249                                 break;
250                 }
251
252                 /* end of our LL? then append a new record */
253                 if (ps->pid != pid) {
254                         _cleanup_fclose_ FILE *st = NULL;
255                         char t[32];
256                         struct ps_struct *parent;
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                         ps->starttime = strtod(t, NULL) / 1000.0;
317
318                         /* ppid */
319                         sprintf(filename, "%d/stat", pid);
320                         fd = openat(procfd, filename, O_RDONLY);
321                         st = fdopen(fd, "r");
322                         if (!st)
323                                 continue;
324                         if (!fscanf(st, "%*s %*s %*s %i", &p)) {
325                                 continue;
326                         }
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->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->sample->next = new0(struct ps_sched_struct, 1);
395                 if (!ps->sample) {
396                         log_oom();
397                         exit(EXIT_FAILURE);
398                 }
399                 ps->sample->next->prev = ps->sample;
400                 ps->sample = ps->sample->next;
401                 ps->last = ps->sample;
402                 ps->sample->runtime = atoll(rt);
403                 ps->sample->waittime = atoll(wt);
404                 ps->sample->sampledata = sampledata;
405                 ps->sample->ps_new = ps;
406                 if (ps_prev) {
407                         ps_prev->cross = ps->sample;
408                 }
409                 ps_prev = ps->sample;
410                 ps->total = (ps->last->runtime - ps->first->runtime)
411                             / 1000000000.0;
412
413                 if (!arg_pss)
414                         goto catch_rename;
415
416                 /* Pss */
417                 if (!ps->smaps) {
418                         sprintf(filename, "%d/smaps", pid);
419                         fd = openat(procfd, filename, O_RDONLY);
420                         ps->smaps = fdopen(fd, "r");
421                         if (!ps->smaps)
422                                 continue;
423                         setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf));
424                 }
425                 else {
426                         rewind(ps->smaps);
427                 }
428                 /* test to see if we need to skip another field */
429                 if (skip == 0) {
430                         if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
431                                 continue;
432                         }
433                         if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) {
434                                 continue;
435                         }
436                         if (buf[392] == 'V') {
437                                 skip = 2;
438                         }
439                         else {
440                                 skip = 1;
441                         }
442                         rewind(ps->smaps);
443                 }
444                 while (1) {
445                         int pss_kb;
446
447                         /* skip one line, this contains the object mapped. */
448                         if (fgets(buf, sizeof(buf), ps->smaps) == NULL) {
449                                 break;
450                         }
451                         /* then there's a 28 char 14 line block */
452                         if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) {
453                                 break;
454                         }
455                         pss_kb = atoi(&buf[61]);
456                         ps->sample->pss += pss_kb;
457
458                         /* skip one more line if this is a newer kernel */
459                         if (skip == 2) {
460                                if (fgets(buf, sizeof(buf), ps->smaps) == NULL)
461                                        break;
462                         }
463                 }
464                 if (ps->sample->pss > ps->pss_max)
465                         ps->pss_max = ps->sample->pss;
466
467 catch_rename:
468                 /* catch process rename, try to randomize time */
469                 mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0);
470                 if (((samples - ps->pid) + pid) % (int)(mod) == 0) {
471
472                         /* re-fetch name */
473                         /* get name, start time */
474                         if (!ps->sched) {
475                                 sprintf(filename, "%d/sched", pid);
476                                 ps->sched = openat(procfd, filename, O_RDONLY);
477                                 if (ps->sched == -1)
478                                         continue;
479                         }
480                         s = pread(ps->sched, buf, sizeof(buf) - 1, 0);
481                         if (s <= 0) {
482                                 /* clean up file descriptors */
483                                 close(ps->sched);
484                                 if (ps->schedstat)
485                                         close(ps->schedstat);
486                                 //if (ps->smaps)
487                                 //        fclose(ps->smaps);
488                                 continue;
489                         }
490                         buf[s] = '\0';
491
492                         if (!sscanf(buf, "%s %*s %*s", key))
493                                 continue;
494
495                         strscpy(ps->name, sizeof(ps->name), key);
496
497                         /* cmdline */
498                         if (arg_show_cmdline)
499                                 pid_cmdline_strscpy(ps->name, sizeof(ps->name), pid);
500                 }
501         }
502 }