summaryrefslogtreecommitdiffstats
path: root/src/plugins/old-stats/mail-stats-fill.c
blob: 10d8c398f9a8b987d9917f13ea055f6ae7a152ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "time-util.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 (str_begins(*tmp, "rchar: ")) {
			if (str_to_uint64(*tmp + 7, &stats->read_bytes) < 0)
				return -1;
		} else if (str_begins(*tmp, "wchar: ")) {
			if (str_to_uint64(*tmp + 7, &stats->write_bytes) < 0)
				return -1;
		} else if (str_begins(*tmp, "syscr: ")) {
			if (str_to_uint32(*tmp + 7, &stats->read_count) < 0)
				return -1;
		} else if (str_begins(*tmp, "syscw: ")) {
			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;

	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 (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;
	i_gettimeofday(&stats_r->clock_time);
	process_read_io_stats(stats_r);
	user_trans_stats_get(suser, stats_r);
}

void mail_stats_global_preinit(void)
{
	(void)process_io_open();
}

void mail_stats_fill_global_deinit(void)
{
	i_close_fd(&proc_io_fd);
}