]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: Add functionality for acquiring process stat.
authorsergey.kitov <sergey.kitov@open-xchange.com>
Wed, 1 Dec 2021 11:34:22 +0000 (13:34 +0200)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Wed, 26 Jan 2022 09:11:52 +0000 (09:11 +0000)
src/lib/Makefile.am
src/lib/process-stat.c [new file with mode: 0644]
src/lib/process-stat.h [new file with mode: 0644]

index e8a4ccb83d2b7b45c5f0bae14a0e03c7b434b7fd..7aea6407bca63fd039bede1dc3b37c488b3f7b6d 100644 (file)
@@ -159,6 +159,7 @@ liblib_la_SOURCES = \
        pkcs5.c \
        primes.c \
        printf-format-fix.c \
+       process-stat.c \
        process-title.c \
        priorityq.c \
        randgen.c \
@@ -316,6 +317,7 @@ headers = \
        pkcs5.h \
        primes.h \
        printf-format-fix.h \
+       process-stat.h \
        process-title.h \
        priorityq.h \
        randgen.h \
diff --git a/src/lib/process-stat.c b/src/lib/process-stat.c
new file mode 100644 (file)
index 0000000..fae57c7
--- /dev/null
@@ -0,0 +1,267 @@
+/* Copyright (c) 2008-2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "process-stat.h"
+#include "time-util.h"
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <time.h>
+#include <sys/resource.h>
+#include <stdio.h>
+
+#define PROC_STAT_PATH "/proc/self/stat"
+#define PROC_STATUS_PATH "/proc/self/status"
+#define PROC_IO_PATH "/proc/self/io"
+
+static const uint64_t stat_undefined = 0xFFFFFFFFFFFFFFFF;
+
+struct key_val {
+       const char *key;
+       uint64_t *value;
+       unsigned int idx;
+};
+
+static int parse_field(const char *line, struct key_val *field)
+{
+       if (str_begins(line, field->key))
+               return str_to_uint64(line + strlen(field->key), field->value);
+       return -1;
+}
+
+static void buffer_parse(const char *buf, struct key_val *fields)
+{
+       const char *const *tmp;
+       tmp = t_strsplit(buf, "\n");
+       unsigned int tmp_count = str_array_length(tmp);
+       for (; fields->key != NULL; fields++) {
+               if (fields->idx >= tmp_count ||
+                   parse_field(tmp[fields->idx], fields) < 0)
+                       *fields->value = stat_undefined;
+       }
+}
+
+static int open_fd(const char *path, struct event *event)
+{
+       int fd;
+       uid_t uid;
+
+       fd = open(path, O_RDONLY);
+
+       if (fd == -1 && errno == EACCES) {
+               uid = geteuid();
+               /* kludge: if we're running with permissions temporarily
+                  dropped, get them temporarily back so we can open
+                  /proc/self/io. */
+               if (seteuid(0) == 0) {
+                       fd = open(path, O_RDONLY);
+                       if (seteuid(uid) < 0)
+                               i_fatal("seteuid(%s) failed", dec2str(uid));
+               }
+               errno = EACCES;
+       }
+       if (fd == -1) {
+               if (errno == ENOENT || errno == EACCES)
+                       e_debug(event, "open(%s) failed: %m", path);
+               else
+                       e_error(event, "open(%s) failed: %m", path);
+       }
+       return fd;
+}
+
+static int
+read_file(int fd, const char *path, char *buf_r, size_t buf_size, struct event *event)
+{
+       ssize_t ret;
+       ret = read(fd, buf_r, buf_size);
+       if (ret <= 0) {
+               if (ret == -1)
+                       e_error(event, "read(%s) failed: %m", path);
+               else
+                       e_error(event, "read(%s) returned EOF", path);
+       } else if (ret == (ssize_t)buf_size) {
+               e_error(event, "%s is larger than expected", path);
+               buf_r[buf_size - 1] = '\0';
+       } else {
+               buf_r[ret] = '\0';
+       }
+       i_close_fd(&fd);
+       return ret <= 0 ? -1 : 0;
+}
+
+static int parse_key_val_file(const char *path,
+                             struct key_val *fields,
+                             struct event *event)
+{
+       char buf[2048];
+       int fd;
+
+       fd = open_fd(path, event);
+       if (fd == -1 || read_file(fd, path, buf, sizeof(buf), event) < 0) {
+               for (; fields->key != NULL; fields++)
+                       *fields->value = stat_undefined;
+               return -1;
+       }
+       buffer_parse(buf, fields);
+       return 0;
+}
+
+static int parse_proc_io(struct process_stat *stat_r, struct event *event)
+{
+       struct key_val fields[] = {
+               { "rchar: ", &stat_r->rchar, 0 },
+               { "wchar: ", &stat_r->wchar, 1 },
+               { "syscr: ", &stat_r->syscr, 2 },
+               { "syscw: ", &stat_r->syscw, 3 },
+               { NULL, NULL, 0 },
+       };
+       if (stat_r->proc_io_failed ||
+           parse_key_val_file(PROC_IO_PATH, fields, event) < 0) {
+               stat_r->proc_io_failed = TRUE;
+               return -1;
+       }
+       return 0;
+}
+
+static int parse_proc_status(struct process_stat *stat_r, struct event *event)
+{
+       struct key_val fields [] = {
+               { "voluntary_ctxt_switches:\t", &stat_r->vol_cs, 53 },
+               { "nonvoluntary_ctxt_switches:\t", &stat_r->invol_cs, 54 },
+               { NULL, NULL, 0 },
+       };
+       if (stat_r->proc_status_failed ||
+           parse_key_val_file(PROC_STATUS_PATH, fields, event) < 0) {
+               stat_r->proc_status_failed = TRUE;
+               return -1;
+       }
+       return 0;
+}
+
+static int stat_get_rusage(struct process_stat *stat_r)
+{
+       struct rusage usage;
+
+       if (getrusage(RUSAGE_SELF, &usage) < 0)
+               return -1;
+       stat_r->utime = timeval_to_usecs(&usage.ru_utime);
+       stat_r->stime = timeval_to_usecs(&usage.ru_stime);
+       stat_r->minor_faults = usage.ru_minflt;
+       stat_r->major_faults = usage.ru_majflt;
+       stat_r->vol_cs = usage.ru_nvcsw;
+       stat_r->invol_cs = usage.ru_nivcsw;
+       return 0;
+}
+
+static int parse_stat_file(struct process_stat *stat_r, struct event *event)
+{
+       int fd = -1;
+       char buf[1024];
+       unsigned int i;
+       const char *const *tmp;
+       struct {
+               uint64_t *value;
+               unsigned int idx;
+       } fields[] = {
+               { &stat_r->minor_faults, 9 },
+               { &stat_r->major_faults, 11 },
+               { &stat_r->utime, 13 },
+               { &stat_r->stime, 14 },
+               { &stat_r->vsz, 22 },
+               { &stat_r->rss, 23 },
+       };
+       if (!stat_r->proc_stat_failed)
+               fd = open_fd(PROC_STAT_PATH, event);
+       if (fd == -1) {
+               stat_r->proc_stat_failed = TRUE;
+               /* vsz and rss are not provided by getrusage(), setting to undefined */
+               stat_r->vsz = stat_undefined;
+               stat_r->rss = stat_undefined;
+               if (stat_r->rusage_failed)
+                       return -1;
+               if (stat_get_rusage(stat_r) < 0) {
+                       e_error(event, "getrusage() failed: %m");
+                       stat_r->rusage_failed = TRUE;
+                       return -1;
+               }
+               return 0;
+       }
+       if (read_file(fd, PROC_STAT_PATH, buf, sizeof(buf), event) < 0) {
+               stat_r->proc_stat_failed = TRUE;
+               return -1;
+       }
+       tmp = t_strsplit(buf, " ");
+       unsigned int tmp_count = str_array_length(tmp);
+
+       for (i = 0; i < N_ELEMENTS(fields); i++) {
+               if (fields[i].idx >= tmp_count ||
+                   str_to_uint64(tmp[fields[i].idx], fields[i].value) < 0)
+                       *fields[i].value = stat_undefined;
+       }
+       /* rss is provided in pages, convert to bytes */
+       stat_r->rss *= sysconf(_SC_PAGESIZE);
+       return 0;
+}
+
+static int parse_all_stats(struct process_stat *stat_r, struct event *event)
+{
+       bool has_fields = FALSE;
+
+       if (parse_stat_file(stat_r, event) == 0)
+               has_fields = TRUE;
+       if (parse_proc_io(stat_r, event) == 0)
+               has_fields = TRUE;
+       if ((!stat_r->proc_stat_failed || stat_r->rusage_failed) &&
+           parse_proc_status(stat_r, event) == 0)
+               has_fields = TRUE;
+
+       if (has_fields)
+               return 0;
+       return -1;
+}
+
+void process_stat_read_start(struct process_stat *stat_r, struct event *event)
+{
+       i_zero(stat_r);
+       (void)parse_all_stats(stat_r, event);
+}
+
+void process_stat_read_finish(struct process_stat *stat, struct event *event)
+{
+       unsigned int i;
+       struct process_stat new_stat;
+       i_zero(&new_stat);
+       new_stat.proc_io_failed = stat->proc_io_failed;
+       new_stat.proc_status_failed = stat->proc_status_failed;
+       new_stat.proc_stat_failed = stat->proc_stat_failed;
+       new_stat.rusage_failed = stat->rusage_failed;
+       if (parse_all_stats(&new_stat, event) < 0) {
+               i_zero(stat);
+               return;
+       }
+       stat->vsz = new_stat.vsz == stat_undefined ? 0 : new_stat.vsz;
+       stat->rss = new_stat.rss == stat_undefined ? 0 : new_stat.rss;
+
+       unsigned int cumulative_field_offsets[] = {
+               offsetof(struct process_stat, utime),
+               offsetof(struct process_stat, stime),
+               offsetof(struct process_stat, minor_faults),
+               offsetof(struct process_stat, major_faults),
+               offsetof(struct process_stat, vol_cs),
+               offsetof(struct process_stat, invol_cs),
+               offsetof(struct process_stat, rchar),
+               offsetof(struct process_stat, wchar),
+               offsetof(struct process_stat, syscr),
+               offsetof(struct process_stat, syscw),
+       };
+       for (i = 0; i < N_ELEMENTS(cumulative_field_offsets); i++) {
+               uint64_t *old_value = PTR_OFFSET(stat, cumulative_field_offsets[i]);
+               uint64_t *new_value = PTR_OFFSET(&new_stat, cumulative_field_offsets[i]);
+               if (*old_value == stat_undefined || *new_value == stat_undefined)
+                       *old_value = 0;
+               else
+                       *old_value = *new_value > *old_value ?
+                               (*new_value - *old_value) : 0;
+       }
+}
diff --git a/src/lib/process-stat.h b/src/lib/process-stat.h
new file mode 100644 (file)
index 0000000..66fea7d
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef PROCESS_STAT_H
+#define PROCESS_STAT_H
+
+struct process_stat {
+       uint64_t utime;
+       uint64_t stime;
+       uint64_t minor_faults;
+       uint64_t major_faults;
+       uint64_t vol_cs;
+       uint64_t invol_cs;
+       uint64_t rss;
+       uint64_t vsz;
+       uint64_t rchar;
+       uint64_t wchar;
+       uint64_t syscr;
+       uint64_t syscw;
+       bool proc_io_failed:1;
+       bool rusage_failed:1;
+       bool proc_stat_failed:1;
+       bool proc_status_failed:1;
+};
+
+void process_stat_read_start(struct process_stat *stat_r, struct event *event);
+void process_stat_read_finish(struct process_stat *stat, struct event *event);
+
+#endif