mail-stats-fill.c revision 8c533d1c49f47b7919de72c794abe9d2e0660ebd
/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "time-util.h"
#include "restrict-access.h"
#include "stats-plugin.h"
#include "mail-stats.h"
#include <sys/resource.h>
#define PROC_IO_PATH "/proc/self/io"
static bool proc_io_disabled = FALSE;
static int proc_io_fd = -1;
static int
process_io_buffer_parse(const char *buf, struct mail_stats *stats)
{
const char *const *tmp;
tmp = t_strsplit(buf, "\n");
for (; *tmp != NULL; tmp++) {
if (strncmp(*tmp, "rchar: ", 7) == 0) {
if (str_to_uint64(*tmp + 7, &stats->read_bytes) < 0)
return -1;
} else if (strncmp(*tmp, "wchar: ", 7) == 0) {
if (str_to_uint64(*tmp + 7, &stats->write_bytes) < 0)
return -1;
} else if (strncmp(*tmp, "syscr: ", 7) == 0) {
if (str_to_uint32(*tmp + 7, &stats->read_count) < 0)
return -1;
} else if (strncmp(*tmp, "syscw: ", 7) == 0) {
if (str_to_uint32(*tmp + 7, &stats->write_count) < 0)
return -1;
}
}
return 0;
}
static int process_io_open(void)
{
uid_t uid;
if (proc_io_fd != -1)
return proc_io_fd;
if (proc_io_disabled)
return -1;
bool dumpable = restrict_access_get_dumpable();
if (!dumpable)
restrict_access_set_dumpable(TRUE);
proc_io_fd = open(PROC_IO_PATH, O_RDONLY);
if (proc_io_fd == -1 && errno == EACCES) {
/* kludge: if we're running with permissions temporarily
dropped, get them temporarily back so we can open
/proc/self/io. */
uid = geteuid();
if (seteuid(0) == 0) {
proc_io_fd = open(PROC_IO_PATH, O_RDONLY);
if (seteuid(uid) < 0) {
/* oops, this is bad */
i_fatal("stats: seteuid(%s) failed", dec2str(uid));
}
}
errno = EACCES;
}
if (!dumpable)
restrict_access_set_dumpable(FALSE);
if (proc_io_fd == -1) {
/* ignore access errors too, certain security options can
prevent root access to this file when not owned by root */
if (errno != ENOENT && errno != EACCES)
i_error("open(%s) failed: %m", PROC_IO_PATH);
proc_io_disabled = TRUE;
return -1;
}
return proc_io_fd;
}
static void process_read_io_stats(struct mail_stats *stats)
{
char buf[1024];
int fd, ret;
if ((fd = process_io_open()) == -1)
return;
ret = pread(fd, buf, sizeof(buf), 0);
if (ret <= 0) {
if (ret == -1)
i_error("read(%s) failed: %m", PROC_IO_PATH);
else
i_error("read(%s) returned EOF", PROC_IO_PATH);
} else if (ret == sizeof(buf)) {
/* just shouldn't happen.. */
i_error("%s is larger than expected", PROC_IO_PATH);
proc_io_disabled = TRUE;
} else {
buf[ret] = '\0';
T_BEGIN {
if (process_io_buffer_parse(buf, stats) < 0) {
i_error("Invalid input in file %s",
PROC_IO_PATH);
proc_io_disabled = TRUE;
}
} T_END;
}
}
static void
user_trans_stats_get(struct stats_user *suser, struct mail_stats *dest_r)
{
struct stats_transaction_context *strans;
mail_stats_add_transaction(dest_r, &suser->finished_transaction_stats);
for (strans = suser->transactions; strans != NULL; strans = strans->next)
mail_stats_add_transaction(dest_r, &strans->trans->stats);
}
void mail_stats_fill(struct stats_user *suser, struct mail_stats *stats_r)
{
static bool getrusage_broken = FALSE;
static struct rusage prev_usage;
struct rusage usage;
i_zero(stats_r);
/* cputime */
if (getrusage(RUSAGE_SELF, &usage) < 0) {
if (!getrusage_broken) {
i_error("getrusage() failed: %m");
getrusage_broken = TRUE;
}
usage = prev_usage;
} else if (timeval_diff_usecs(&usage.ru_stime, &prev_usage.ru_stime) < 0) {
/* This seems to be a Linux bug. */
usage.ru_stime = prev_usage.ru_stime;
}
prev_usage = usage;
stats_r->user_cpu = usage.ru_utime;
stats_r->sys_cpu = usage.ru_stime;
stats_r->min_faults = usage.ru_minflt;
stats_r->maj_faults = usage.ru_majflt;
stats_r->vol_cs = usage.ru_nvcsw;
stats_r->invol_cs = usage.ru_nivcsw;
stats_r->disk_input = (unsigned long long)usage.ru_inblock * 512ULL;
stats_r->disk_output = (unsigned long long)usage.ru_oublock * 512ULL;
(void)gettimeofday(&stats_r->clock_time, NULL);
process_read_io_stats(stats_r);
user_trans_stats_get(suser, stats_r);
}
void mail_stats_fill_global_deinit(void)
{
i_close_fd(&proc_io_fd);
}