diff options
Diffstat (limited to 'src/lib/process-stat.c')
-rw-r--r-- | src/lib/process-stat.c | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/src/lib/process-stat.c b/src/lib/process-stat.c new file mode 100644 index 0000000..fae57c7 --- /dev/null +++ b/src/lib/process-stat.c @@ -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; + } +} |