1
/* Copyright (c) 2011-2012 Dovecot authors, see the included COPYING file */
8
#include "settings-parser.h"
9
#include "stats-connection.h"
10
#include "stats-plugin.h"
13
#include <sys/resource.h>
15
#define STATS_CONTEXT(obj) \
16
MODULE_CONTEXT(obj, stats_storage_module)
18
/* If session isn't refreshed every 15 minutes, it's dropped.
19
Must be smaller than MAIL_SESSION_IDLE_TIMEOUT_MSECS in stats server */
20
#define SESSION_STATS_FORCE_REFRESH_SECS (5*60)
21
#define REFRESH_CHECK_INTERVAL 100
22
#define MAIL_STATS_SOCKET_NAME "stats-mail"
23
#define PROC_IO_PATH "/proc/self/io"
25
#define USECS_PER_SEC 1000000
27
struct stats_transaction_context {
28
union mailbox_transaction_module_context module_ctx;
30
struct stats_transaction_context *prev, *next;
31
struct mailbox_transaction_context *trans;
33
struct mailbox_transaction_stats prev_stats;
36
struct stats_mailbox {
37
union mailbox_module_context module_ctx;
40
const char *stats_plugin_version = DOVECOT_VERSION;
42
struct stats_user_module stats_user_module =
43
MODULE_CONTEXT_INIT(&mail_user_module_register);
44
static MODULE_CONTEXT_DEFINE_INIT(stats_storage_module,
45
&mail_storage_module_register);
47
static bool proc_io_disabled = FALSE;
48
static int proc_io_fd = -1;
50
static struct stats_connection *global_stats_conn = NULL;
51
static struct mail_user *stats_global_user = NULL;
52
static unsigned int stats_user_count = 0;
54
static void session_stats_refresh_timeout(struct mail_user *user);
56
static void trans_stats_dec(struct mailbox_transaction_stats *dest,
57
const struct mailbox_transaction_stats *src)
59
dest->open_lookup_count -= src->open_lookup_count;
60
dest->stat_lookup_count -= src->stat_lookup_count;
61
dest->fstat_lookup_count -= src->fstat_lookup_count;
62
dest->files_read_count -= src->files_read_count;
63
dest->files_read_bytes -= src->files_read_bytes;
64
dest->cache_hit_count -= src->cache_hit_count;
67
static void trans_stats_add(struct mailbox_transaction_stats *dest,
68
const struct mailbox_transaction_stats *src)
70
dest->open_lookup_count += src->open_lookup_count;
71
dest->stat_lookup_count += src->stat_lookup_count;
72
dest->fstat_lookup_count += src->fstat_lookup_count;
73
dest->files_read_count += src->files_read_count;
74
dest->files_read_bytes += src->files_read_bytes;
75
dest->cache_hit_count += src->cache_hit_count;
78
static void user_trans_stats_get(struct stats_user *suser,
79
struct mailbox_transaction_stats *dest_r)
81
struct stats_transaction_context *strans;
83
memset(dest_r, 0, sizeof(*dest_r));
84
strans = suser->transactions;
85
for (; strans != NULL; strans = strans->next)
86
trans_stats_add(dest_r, &strans->trans->stats);
90
process_io_buffer_parse(const char *buf, struct mail_stats *stats)
92
const char *const *tmp;
94
tmp = t_strsplit(buf, "\n");
95
for (; *tmp != NULL; tmp++) {
96
if (strncmp(*tmp, "rchar: ", 7) == 0) {
97
if (str_to_uint64(*tmp + 7, &stats->read_bytes) < 0)
99
} else if (strncmp(*tmp, "wchar: ", 7) == 0) {
100
if (str_to_uint64(*tmp + 7, &stats->write_bytes) < 0)
102
} else if (strncmp(*tmp, "syscr: ", 7) == 0) {
103
if (str_to_uint32(*tmp + 7, &stats->read_count) < 0)
105
} else if (strncmp(*tmp, "syscw: ", 7) == 0) {
106
if (str_to_uint32(*tmp + 7, &stats->write_count) < 0)
113
static int process_io_open(void)
115
if (proc_io_fd == -1) {
116
if (proc_io_disabled)
118
proc_io_fd = open(PROC_IO_PATH, O_RDONLY);
119
if (proc_io_fd == -1) {
121
i_error("open(%s) failed: %m", PROC_IO_PATH);
122
proc_io_disabled = TRUE;
129
static void process_read_io_stats(struct mail_stats *stats)
134
if ((fd = process_io_open()) == -1)
137
ret = pread(fd, buf, sizeof(buf), 0);
140
i_error("read(%s) failed: %m", PROC_IO_PATH);
142
i_error("read(%s) returned EOF", PROC_IO_PATH);
143
} else if (ret == sizeof(buf)) {
144
/* just shouldn't happen.. */
145
i_error("%s is larger than expected", PROC_IO_PATH);
146
proc_io_disabled = TRUE;
150
if (process_io_buffer_parse(buf, stats) < 0) {
151
i_error("Invalid input in file %s",
153
proc_io_disabled = TRUE;
159
void mail_stats_get(struct stats_user *suser, struct mail_stats *stats_r)
163
memset(stats_r, 0, sizeof(*stats_r));
165
if (getrusage(RUSAGE_SELF, &usage) < 0)
166
memset(&usage, 0, sizeof(usage));
167
stats_r->user_cpu = usage.ru_utime;
168
stats_r->sys_cpu = usage.ru_stime;
169
stats_r->min_faults = usage.ru_minflt;
170
stats_r->maj_faults = usage.ru_majflt;
171
stats_r->vol_cs = usage.ru_nvcsw;
172
stats_r->invol_cs = usage.ru_nivcsw;
173
stats_r->disk_input = (unsigned long long)usage.ru_inblock * 512ULL;
174
stats_r->disk_output = (unsigned long long)usage.ru_oublock * 512ULL;
175
process_read_io_stats(stats_r);
176
user_trans_stats_get(suser, &stats_r->trans_stats);
179
static void stats_io_activate(void *context)
181
struct mail_user *user = context;
182
struct stats_user *suser = STATS_USER_CONTEXT(user);
184
if (stats_user_count == 1) {
185
/* the first user sets the global user. the second user sets
186
it to NULL. when we get back to one user we'll need to set
187
the global user again somewhere. do it here. */
188
stats_global_user = user;
190
i_assert(stats_global_user == NULL);
192
mail_stats_get(suser, &suser->pre_io_stats);
196
static void timeval_add_diff(struct timeval *dest,
197
const struct timeval *newsrc,
198
const struct timeval *oldsrc)
202
usecs = timeval_diff_usecs(newsrc, oldsrc);
203
dest->tv_sec += usecs / USECS_PER_SEC;
204
dest->tv_usec += usecs % USECS_PER_SEC;
205
if (dest->tv_usec > USECS_PER_SEC) {
206
dest->tv_usec -= USECS_PER_SEC;
211
void mail_stats_add_diff(struct mail_stats *dest,
212
const struct mail_stats *old_stats,
213
const struct mail_stats *new_stats)
215
dest->disk_input += new_stats->disk_input - old_stats->disk_input;
216
dest->disk_output += new_stats->disk_output - old_stats->disk_output;
217
dest->min_faults += new_stats->min_faults - old_stats->min_faults;
218
dest->maj_faults += new_stats->maj_faults - old_stats->maj_faults;
219
dest->vol_cs += new_stats->vol_cs - old_stats->vol_cs;
220
dest->invol_cs += new_stats->invol_cs - old_stats->invol_cs;
221
dest->read_count += new_stats->read_count - old_stats->read_count;
222
dest->write_count += new_stats->write_count - old_stats->write_count;
223
dest->read_bytes += new_stats->read_bytes - old_stats->read_bytes;
224
dest->write_bytes += new_stats->write_bytes - old_stats->write_bytes;
226
timeval_add_diff(&dest->user_cpu, &new_stats->user_cpu,
227
&old_stats->user_cpu);
228
timeval_add_diff(&dest->sys_cpu, &new_stats->sys_cpu,
229
&old_stats->sys_cpu);
230
trans_stats_dec(&dest->trans_stats, &old_stats->trans_stats);
231
trans_stats_add(&dest->trans_stats, &new_stats->trans_stats);
234
void mail_stats_export(string_t *str, const struct mail_stats *stats)
236
const struct mailbox_transaction_stats *tstats = &stats->trans_stats;
238
str_printfa(str, "\tucpu=%ld.%ld", (long)stats->user_cpu.tv_sec,
239
(long)stats->user_cpu.tv_usec);
240
str_printfa(str, "\tscpu=%ld.%ld", (long)stats->sys_cpu.tv_sec,
241
(long)stats->sys_cpu.tv_usec);
242
str_printfa(str, "\tminflt=%u", stats->min_faults);
243
str_printfa(str, "\tmajflt=%u", stats->maj_faults);
244
str_printfa(str, "\tvolcs=%u", stats->vol_cs);
245
str_printfa(str, "\tinvolcs=%u", stats->invol_cs);
246
str_printfa(str, "\tdiskin=%llu",
247
(unsigned long long)stats->disk_input);
248
str_printfa(str, "\tdiskout=%llu",
249
(unsigned long long)stats->disk_output);
250
str_printfa(str, "\trchar=%llu",
251
(unsigned long long)stats->read_bytes);
252
str_printfa(str, "\twchar=%llu",
253
(unsigned long long)stats->write_bytes);
254
str_printfa(str, "\tsyscr=%u", stats->read_count);
255
str_printfa(str, "\tsyscw=%u", stats->write_count);
256
str_printfa(str, "\tmlpath=%lu",
257
tstats->open_lookup_count + tstats->stat_lookup_count);
258
str_printfa(str, "\tmlattr=%lu",
259
tstats->fstat_lookup_count + tstats->stat_lookup_count);
260
str_printfa(str, "\tmrcount=%lu", tstats->files_read_count);
261
str_printfa(str, "\tmrbytes=%llu", tstats->files_read_bytes);
262
str_printfa(str, "\tmcache=%lu", tstats->cache_hit_count);
265
static void stats_add_session(struct mail_user *user)
267
struct stats_user *suser = STATS_USER_CONTEXT(user);
268
struct mail_stats new_stats;
270
mail_stats_get(suser, &new_stats);
271
mail_stats_add_diff(&suser->session_stats, &suser->pre_io_stats,
273
suser->pre_io_stats = new_stats;
276
static bool session_has_changed(const struct mail_stats *prev,
277
const struct mail_stats *cur)
279
if (cur->disk_input != prev->disk_input ||
280
cur->disk_output != prev->disk_output ||
281
memcmp(&cur->trans_stats, &prev->trans_stats,
282
sizeof(cur->trans_stats)) != 0)
285
/* allow a tiny bit of changes that are caused by this
287
if (timeval_diff_msecs(&cur->user_cpu, &prev->user_cpu) != 0)
289
if (timeval_diff_msecs(&cur->sys_cpu, &prev->sys_cpu) != 0)
292
if (cur->maj_faults > prev->maj_faults+10)
294
if (cur->invol_cs > prev->invol_cs+10)
296
/* don't check for read/write count/bytes changes, since they get
297
changed by stats checking itself */
302
session_stats_need_send(struct stats_user *suser, time_t now,
303
bool *changed_r, unsigned int *to_next_secs_r)
307
*to_next_secs_r = SESSION_STATS_FORCE_REFRESH_SECS;
309
if (session_has_changed(&suser->last_sent_session_stats,
310
&suser->session_stats)) {
311
*to_next_secs_r = suser->refresh_secs;
317
if (!suser->session_sent_duplicate) {
318
if (suser->last_session_update != now) {
319
/* send one duplicate notification so stats reader
320
knows that this session is idle now */
327
diff = now - suser->last_session_update;
328
if (diff < SESSION_STATS_FORCE_REFRESH_SECS) {
329
*to_next_secs_r = SESSION_STATS_FORCE_REFRESH_SECS - diff;
335
static void session_stats_refresh(struct mail_user *user)
337
struct stats_user *suser = STATS_USER_CONTEXT(user);
338
unsigned int to_next_secs;
339
time_t now = time(NULL);
342
if (session_stats_need_send(suser, now, &changed, &to_next_secs)) {
343
suser->session_sent_duplicate = !changed;
344
suser->last_session_update = now;
345
suser->last_sent_session_stats = suser->session_stats;
346
stats_connection_send_session(suser->stats_conn, user,
347
&suser->session_stats);
350
if (suser->to_stats_timeout != NULL)
351
timeout_remove(&suser->to_stats_timeout);
352
suser->to_stats_timeout =
353
timeout_add(to_next_secs*1000,
354
session_stats_refresh_timeout, user);
357
static struct mailbox_transaction_context *
358
stats_transaction_begin(struct mailbox *box,
359
enum mailbox_transaction_flags flags)
361
struct stats_user *suser = STATS_USER_CONTEXT(box->storage->user);
362
struct stats_mailbox *sbox = STATS_CONTEXT(box);
363
struct mailbox_transaction_context *trans;
364
struct stats_transaction_context *strans;
366
trans = sbox->module_ctx.super.transaction_begin(box, flags);
367
trans->stats_track = TRUE;
369
strans = i_new(struct stats_transaction_context, 1);
370
strans->trans = trans;
371
DLLIST_PREPEND(&suser->transactions, strans);
373
MODULE_CONTEXT_SET(trans, stats_storage_module, strans);
377
static void stats_transaction_free(struct stats_user *suser,
378
struct stats_transaction_context *strans)
380
DLLIST_REMOVE(&suser->transactions, strans);
382
trans_stats_add(&suser->session_stats.trans_stats,
383
&strans->trans->stats);
387
stats_transaction_commit(struct mailbox_transaction_context *ctx,
388
struct mail_transaction_commit_changes *changes_r)
390
struct stats_transaction_context *strans = STATS_CONTEXT(ctx);
391
struct stats_mailbox *sbox = STATS_CONTEXT(ctx->box);
392
struct stats_user *suser = STATS_USER_CONTEXT(ctx->box->storage->user);
394
stats_transaction_free(suser, strans);
395
return sbox->module_ctx.super.transaction_commit(ctx, changes_r);
399
stats_transaction_rollback(struct mailbox_transaction_context *ctx)
401
struct stats_transaction_context *strans = STATS_CONTEXT(ctx);
402
struct stats_mailbox *sbox = STATS_CONTEXT(ctx->box);
403
struct stats_user *suser = STATS_USER_CONTEXT(ctx->box->storage->user);
405
stats_transaction_free(suser, strans);
406
sbox->module_ctx.super.transaction_rollback(ctx);
409
static bool stats_search_next_nonblock(struct mail_search_context *ctx,
410
struct mail **mail_r, bool *tryagain_r)
412
struct stats_mailbox *sbox = STATS_CONTEXT(ctx->transaction->box);
413
struct mail_user *user = ctx->transaction->box->storage->user;
414
struct stats_user *suser = STATS_USER_CONTEXT(user);
417
ret = sbox->module_ctx.super.
418
search_next_nonblock(ctx, mail_r, tryagain_r);
419
if (!ret && !*tryagain_r) {
425
++suser->refresh_check_counter % REFRESH_CHECK_INTERVAL == 0) {
426
/* a) retrying, so this is a long running search.
427
b) we've returned enough matches */
428
if (time(NULL) != suser->last_session_update)
429
session_stats_refresh(user);
434
static void stats_mailbox_allocated(struct mailbox *box)
436
struct mailbox_vfuncs *v = box->vlast;
437
struct stats_mailbox *sbox;
438
struct stats_user *suser = STATS_USER_CONTEXT(box->storage->user);
443
sbox = p_new(box->pool, struct stats_mailbox, 1);
444
sbox->module_ctx.super = *v;
445
box->vlast = &sbox->module_ctx.super;
447
v->transaction_begin = stats_transaction_begin;
448
v->transaction_commit = stats_transaction_commit;
449
v->transaction_rollback = stats_transaction_rollback;
450
v->search_next_nonblock = stats_search_next_nonblock;
451
MODULE_CONTEXT_SET(box, stats_storage_module, sbox);
454
static void session_stats_refresh_timeout(struct mail_user *user)
456
if (stats_global_user != NULL)
457
stats_add_session(user);
458
session_stats_refresh(user);
461
static void stats_io_deactivate(void *context)
463
struct mail_user *user = context;
464
struct stats_user *suser = STATS_USER_CONTEXT(user);
465
unsigned int last_update_secs;
467
if (stats_global_user == NULL)
468
stats_add_session(user);
470
last_update_secs = time(NULL) - suser->last_session_update;
471
if (last_update_secs >= suser->refresh_secs) {
472
if (stats_global_user != NULL)
473
stats_add_session(user);
474
session_stats_refresh(user);
475
} else if (suser->to_stats_timeout == NULL) {
476
suser->to_stats_timeout =
477
timeout_add(suser->refresh_secs*1000,
478
session_stats_refresh_timeout, user);
482
static void stats_user_deinit(struct mail_user *user)
484
struct stats_user *suser = STATS_USER_CONTEXT(user);
485
struct stats_connection *stats_conn = suser->stats_conn;
487
i_assert(stats_user_count > 0);
488
if (--stats_user_count == 0) {
489
/* we were updating the session lazily. do one final update. */
490
i_assert(stats_global_user == user);
491
stats_add_session(user);
492
stats_global_user = NULL;
494
i_assert(stats_global_user == NULL);
497
io_loop_context_remove_callbacks(suser->ioloop_ctx,
499
stats_io_deactivate, user);
500
/* send final stats before disconnection */
501
session_stats_refresh(user);
502
stats_connection_disconnect(stats_conn, user);
504
if (suser->to_stats_timeout != NULL)
505
timeout_remove(&suser->to_stats_timeout);
506
suser->module_ctx.super.deinit(user);
508
stats_connection_unref(&stats_conn);
511
static void stats_user_created(struct mail_user *user)
513
struct ioloop_context *ioloop_ctx =
514
io_loop_get_current_context(current_ioloop);
515
struct stats_user *suser;
516
struct mail_user_vfuncs *v = user->vlast;
517
const char *path, *str, *error;
518
unsigned int refresh_secs;
520
if (ioloop_ctx == NULL) {
521
/* we're probably running some test program, or at least
522
mail-storage-service wasn't used to create this user.
523
disable stats tracking. */
526
if (user->autocreated) {
527
/* lda / shared user. we're not tracking this one. */
531
/* get refresh time */
532
str = mail_user_plugin_getenv(user, "stats_refresh");
535
if (settings_get_time(str, &refresh_secs, &error) < 0) {
536
i_error("stats: Invalid stats_refresh setting: %s", error);
539
if (refresh_secs == 0)
542
if (global_stats_conn == NULL) {
543
path = t_strconcat(user->set->base_dir,
544
"/"MAIL_STATS_SOCKET_NAME, NULL);
545
global_stats_conn = stats_connection_create(path);
547
stats_connection_ref(global_stats_conn);
549
if (stats_user_count == 0) {
550
/* first user connection */
551
stats_global_user = user;
552
} else if (stats_user_count == 1) {
553
/* second user connection. we'll need to start doing
554
per-io callback tracking now. */
555
stats_add_session(stats_global_user);
556
stats_global_user = NULL;
560
suser = p_new(user->pool, struct stats_user, 1);
561
suser->module_ctx.super = *v;
562
user->vlast = &suser->module_ctx.super;
563
v->deinit = stats_user_deinit;
565
suser->refresh_secs = refresh_secs;
566
str = mail_user_plugin_getenv(user, "stats_track_cmds");
567
if (str != NULL && strcmp(str, "yes") == 0)
568
suser->track_commands = TRUE;
570
suser->stats_conn = global_stats_conn;
571
guid_128_generate(suser->session_guid);
572
suser->last_session_update = time(NULL);
574
suser->ioloop_ctx = ioloop_ctx;
575
io_loop_context_add_callbacks(ioloop_ctx,
577
stats_io_deactivate, user);
579
MODULE_CONTEXT_SET(user, stats_user_module, suser);
580
stats_connection_connect(suser->stats_conn, user);
583
static struct mail_storage_hooks stats_mail_storage_hooks = {
584
.mailbox_allocated = stats_mailbox_allocated,
585
.mail_user_created = stats_user_created
588
void stats_plugin_init(struct module *module)
590
mail_storage_hooks_add(module, &stats_mail_storage_hooks);
593
void stats_plugin_deinit(void)
595
if (global_stats_conn != NULL)
596
stats_connection_unref(&global_stats_conn);
597
mail_storage_hooks_remove(&stats_mail_storage_hooks);