Mercurial > dovecot > core-2.2
changeset 13294:c51fbe64eae1
Initial implementation of statistics gathering daemon and plugins to feed it.
Some statistics are still missing, some of the code is a bit ugly and the
internal protocols will probably still change.
line wrap: on
line diff
--- a/.hgignore Fri Aug 26 05:10:54 2011 +0300 +++ b/.hgignore Fri Aug 26 05:15:12 2011 +0300 @@ -82,6 +82,7 @@ src/lmtp/lmtp src/master/dovecot src/ssl-params/ssl-params +src/stats/stats src/plugins/fts-squat/squat-test src/pop3-login/pop3-login src/pop3/pop3
--- a/configure.in Fri Aug 26 05:10:54 2011 +0300 +++ b/configure.in Fri Aug 26 05:15:12 2011 +0300 @@ -2724,6 +2724,7 @@ src/pop3/Makefile src/pop3-login/Makefile src/ssl-params/Makefile +src/stats/Makefile src/util/Makefile src/plugins/Makefile src/plugins/acl/Makefile @@ -2741,6 +2742,8 @@ src/plugins/quota/Makefile src/plugins/imap-quota/Makefile src/plugins/snarf/Makefile +src/plugins/stats/Makefile +src/plugins/imap-stats/Makefile src/plugins/trash/Makefile src/plugins/virtual/Makefile src/plugins/zlib/Makefile
--- a/src/Makefile.am Fri Aug 26 05:10:54 2011 +0300 +++ b/src/Makefile.am Fri Aug 26 05:15:12 2011 +0300 @@ -42,4 +42,5 @@ doveadm \ dsync \ ssl-params \ + stats \ plugins
--- a/src/plugins/Makefile.am Fri Aug 26 05:10:54 2011 +0300 +++ b/src/plugins/Makefile.am Fri Aug 26 05:15:12 2011 +0300 @@ -24,6 +24,8 @@ quota \ imap-quota \ snarf \ + stats \ + imap-stats \ trash \ virtual \ $(ZLIB) \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-stats/Makefile.am Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,27 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/imap \ + -I$(top_srcdir)/src/plugins/stats + +imap_moduledir = $(moduledir) + +NOPLUGIN_LDFLAGS = +lib95_imap_stats_plugin_la_LDFLAGS = -module -avoid-version + +imap_module_LTLIBRARIES = \ + lib95_imap_stats_plugin.la + +if DOVECOT_PLUGIN_DEPS +lib95_imap_stats_plugin_la_LIBADD = \ + ../stats/lib90_stats_plugin.la +endif + +lib95_imap_stats_plugin_la_SOURCES = \ + imap-stats-plugin.c + +noinst_HEADERS = \ + imap-stats-plugin.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-stats/imap-stats-plugin.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,110 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" +#include "imap-commands.h" +#include "stats-plugin.h" +#include "stats-connection.h" +#include "imap-stats-plugin.h" + +#define IMAP_STATS_IMAP_CONTEXT(obj) \ + MODULE_CONTEXT(obj, imap_stats_imap_module) + +struct stats_client_command { + union imap_module_context module_ctx; + + unsigned int id; + bool continued; + struct mail_stats stats, pre_stats; + struct mailbox_transaction_stats pre_trans_stats; +}; + +static MODULE_CONTEXT_DEFINE_INIT(imap_stats_imap_module, + &imap_module_register); + +const char *imap_stats_plugin_version = DOVECOT_VERSION; + +static void stats_command_pre(struct client_command_context *cmd) +{ + struct stats_user *suser = STATS_USER_CONTEXT(cmd->client->user); + struct stats_client_command *scmd; + static unsigned int stats_cmd_id_counter = 0; + + if (suser == NULL) + return; + + scmd = IMAP_STATS_IMAP_CONTEXT(cmd); + if (scmd == NULL) { + scmd = p_new(cmd->pool, struct stats_client_command, 1); + scmd->id = ++stats_cmd_id_counter; + MODULE_CONTEXT_SET(cmd, imap_stats_imap_module, scmd); + } + mail_stats_get(suser, &scmd->pre_stats); + scmd->pre_trans_stats = suser->session_stats.trans_stats; +} + +static void stats_command_post(struct client_command_context *cmd) +{ + struct stats_user *suser = STATS_USER_CONTEXT(cmd->client->user); + struct stats_client_command *scmd = IMAP_STATS_IMAP_CONTEXT(cmd); + struct mail_stats stats, pre_trans_stats, trans_stats; + unsigned int args_pos = 0; + string_t *str; + bool done; + + if (scmd == NULL) + return; + + mail_stats_get(suser, &stats); + mail_stats_add_diff(&scmd->stats, &scmd->pre_stats, &stats); + + /* mail_stats_get() can't see the transactions that already went + away, so we'll need to use the session's stats difference */ + memset(&pre_trans_stats, 0, sizeof(pre_trans_stats)); + memset(&trans_stats, 0, sizeof(trans_stats)); + pre_trans_stats.trans_stats = scmd->pre_trans_stats; + trans_stats.trans_stats = suser->session_stats.trans_stats; + mail_stats_add_diff(&scmd->stats, &pre_trans_stats, &trans_stats); + + str = t_str_new(128); + str_append(str, "UPDATE-CMD\t"); + str_append(str, guid_128_to_string(suser->session_guid)); + + done = cmd->state == CLIENT_COMMAND_STATE_DONE; + str_printfa(str, "\t%u\t%d\t", scmd->id, done); + if (scmd->continued) + str_append_c(str, '\t'); + else { + str_append(str, cmd->name); + str_append_c(str, '\t'); + args_pos = str_len(str); + if (cmd->args != NULL) + str_append(str, cmd->args); + scmd->continued = TRUE; + } + + mail_stats_export(str, &scmd->stats); + str_append_c(str, '\n'); + + if (str_len(str) > PIPE_BUF) { + /* truncate the args so it fits */ + i_assert(args_pos != 0); + str_delete(str, args_pos, str_len(str) - PIPE_BUF); + i_assert(str_len(str) == PIPE_BUF); + } + + stats_connection_send(suser->stats_conn, str); +} + +void imap_stats_plugin_init(struct module *module ATTR_UNUSED) +{ + command_hook_register(stats_command_pre, stats_command_post); +} + +void imap_stats_plugin_deinit(void) +{ + command_hook_unregister(stats_command_pre, stats_command_post); +} + +const char *imap_stats_plugin_dependencies[] = { "stats", NULL }; +const char imap_stats_plugin_binary_dependency[] = "imap";
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/imap-stats/imap-stats-plugin.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,12 @@ +#ifndef IMAP_STATS_PLUGIN_H +#define IMAP_STATS_PLUGIN_H + +struct module; + +extern const char *imap_stats_plugin_dependencies[]; +extern const char imap_stats_plugin_binary_dependency[]; + +void imap_stats_plugin_init(struct module *module); +void imap_stats_plugin_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/stats/Makefile.am Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,19 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +NOPLUGIN_LDFLAGS = +lib90_stats_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib90_stats_plugin.la + +lib90_stats_plugin_la_SOURCES = \ + stats-connection.c \ + stats-plugin.c + +noinst_HEADERS = \ + stats-connection.h \ + stats-plugin.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/stats/stats-connection.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,135 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "network.h" +#include "str.h" +#include "strescape.h" +#include "mail-storage.h" +#include "stats-plugin.h" +#include "stats-connection.h" + +struct stats_connection { + int refcount; + + int fd; + char *path; +}; + +struct stats_connection * +stats_connection_create(const char *path) +{ + struct stats_connection *conn; + + conn = i_new(struct stats_connection, 1); + conn->refcount = 1; + conn->path = i_strdup(path); + conn->fd = open(path, O_WRONLY); + if (conn->fd == -1) + i_error("stats: open(%s) failed: %m", path); + return conn; +} + +void stats_connection_ref(struct stats_connection *conn) +{ + conn->refcount++; +} + +void stats_connection_unref(struct stats_connection **_conn) +{ + struct stats_connection *conn = *_conn; + + i_assert(conn->refcount > 0); + if (--conn->refcount > 0) + return; + + *_conn = NULL; + if (conn->fd != -1) { + if (close(conn->fd) < 0) + i_error("close(%s) failed: %m", conn->path); + } + i_free(conn->path); + i_free(conn); +} + +void stats_connection_send(struct stats_connection *conn, const string_t *str) +{ + static bool pipe_warned = FALSE; + ssize_t ret; + + if (conn->fd == -1) + return; + + if (str_len(str) > PIPE_BUF && !pipe_warned) { + i_warning("stats update sent more bytes that PIPE_BUF " + "(%"PRIuSIZE_T" > %u), this may break statistics", + str_len(str), (unsigned int)PIPE_BUF); + pipe_warned = TRUE; + } + + ret = write(conn->fd, str_data(str), str_len(str)); + if (ret != (ssize_t)str_len(str)) { + if (ret < 0) + i_error("write(%s) failed: %m", conn->path); + else if ((size_t)ret != str_len(str)) + i_error("write(%s) wrote partial update", conn->path); + /* this shouldn't happen, just stop sending updates */ + if (close(conn->fd) < 0) + i_error("close(%s) failed: %m", conn->path); + conn->fd = -1; + } +} + +void stats_connection_connect(struct stats_connection *conn, + struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT(user); + string_t *str = t_str_new(128); + + str_append(str, "CONNECT\t"); + /* required fields */ + str_append(str, guid_128_to_string(suser->session_guid)); + str_append_c(str, '\t'); + str_tabescape_write(str, user->username); + str_append_c(str, '\t'); + str_tabescape_write(str, user->service); + + /* optional fields */ + if (user->local_ip != NULL) { + str_append(str, "\tlip="); + str_append(str, net_ip2addr(user->local_ip)); + } + if (user->remote_ip != NULL) { + str_append(str, "\trip="); + str_append(str, net_ip2addr(user->remote_ip)); + } + str_append_c(str, '\n'); + stats_connection_send(conn, str); +} + +void stats_connection_disconnect(struct stats_connection *conn, + struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT(user); + string_t *str = t_str_new(128); + + str_append(str, "DISCONNECT\t"); + str_append(str, guid_128_to_string(suser->session_guid)); + str_append_c(str, '\n'); + stats_connection_send(conn, str); +} + +void stats_connection_send_session(struct stats_connection *conn, + struct mail_user *user, + const struct mail_stats *stats) +{ + struct stats_user *suser = STATS_USER_CONTEXT(user); + string_t *str = t_str_new(128); + + str_append(str, "UPDATE-SESSION\t"); + str_append(str, guid_128_to_string(suser->session_guid)); + + mail_stats_export(str, stats); + + str_append_c(str, '\n'); + stats_connection_send(conn, str); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/stats/stats-connection.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,21 @@ +#ifndef STATS_CONNECTION_H +#define STATS_CONNECTION_H + +struct mail_stats; +struct mail_user; + +struct stats_connection *stats_connection_create(const char *path); +void stats_connection_ref(struct stats_connection *conn); +void stats_connection_unref(struct stats_connection **conn); + +void stats_connection_connect(struct stats_connection *conn, + struct mail_user *user); +void stats_connection_disconnect(struct stats_connection *conn, + struct mail_user *user); + +void stats_connection_send_session(struct stats_connection *conn, + struct mail_user *user, + const struct mail_stats *stats); +void stats_connection_send(struct stats_connection *conn, const string_t *str); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/stats/stats-plugin.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,394 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "hostpid.h" +#include "llist.h" +#include "str.h" +#include "time-util.h" +#include "stats-connection.h" +#include "stats-plugin.h" + +#include <sys/time.h> +#include <sys/resource.h> + +#define STATS_CONTEXT(obj) \ + MODULE_CONTEXT(obj, stats_storage_module) + +/* Refresh session every 10 seconds, if anything has changed */ +#define SESSION_STATS_REFRESH_MSECS (10*1000) +/* If session isn't refreshed every 15 minutes, it's dropped. + Must be smaller than MAIL_SESSION_IDLE_TIMEOUT_MSECS in stats server */ +#define SESSION_STATS_FORCE_REFRESH_SECS (5*60) +#define MAIL_STATS_SOCKET_NAME "stats-mail" + +#define USECS_PER_SEC 1000000 + +struct stats_transaction_context { + union mailbox_transaction_module_context module_ctx; + + struct stats_transaction_context *prev, *next; + struct mailbox_transaction_context *trans; + + struct mailbox_transaction_stats prev_stats; +}; + +struct stats_mailbox { + union mailbox_module_context module_ctx; +}; + +const char *stats_plugin_version = DOVECOT_VERSION; + +struct stats_user_module stats_user_module = + MODULE_CONTEXT_INIT(&mail_user_module_register); +static MODULE_CONTEXT_DEFINE_INIT(stats_storage_module, + &mail_storage_module_register); + +static struct stats_connection *global_stats_conn = NULL; + +static int +process_io_buffer_parse(const char *buf, uint64_t *read_bytes_r, + uint64_t *write_bytes_r) +{ + const char *const *tmp; + uint64_t cbytes; + + tmp = t_strsplit(buf, "\n"); + for (; *tmp != NULL; tmp++) { + if (strncmp(*tmp, "read_bytes: ", 12) == 0) { + if (str_to_uint64(*tmp + 12, read_bytes_r) < 0) + return -1; + } else if (strncmp(*tmp, "write_bytes: ", 13) == 0) { + if (str_to_uint64(*tmp + 13, write_bytes_r) < 0) + return -1; + } else if (strncmp(*tmp, "cancelled_write_bytes: ", 23) == 0) { + if (str_to_uint64(*tmp + 23, &cbytes) < 0) + return -1; + /* it's not 100% correct to simply subtract the + cancelled bytes from write bytes, but it's close + enough. */ + if (*write_bytes_r < cbytes) + *write_bytes_r = 0; + else + *write_bytes_r -= cbytes; + } + } + return 0; +} + +static void +process_read_io_stats(uint64_t *read_bytes_r, uint64_t *write_bytes_r) +{ + static bool io_disabled = FALSE; + char path[100], buf[1024]; + int fd, ret; + + *read_bytes_r = *write_bytes_r = 0; + + if (io_disabled) + return; + + i_snprintf(path, sizeof(path), "/proc/%s/io", my_pid); + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno != ENOENT) + i_error("open(%s) failed: %m", path); + io_disabled = TRUE; + return; + } + ret = read(fd, buf, sizeof(buf)); + if (ret <= 0) { + if (ret == -1) + i_error("read(%s) failed: %m", path); + else + i_error("read(%s) returned EOF", path); + } else if (ret == sizeof(buf)) { + /* just shouldn't happen.. */ + i_error("%s is larger than expected", path); + io_disabled = TRUE; + } else { + buf[ret] = '\0'; + T_BEGIN { + if (process_io_buffer_parse(buf, read_bytes_r, + write_bytes_r) < 0) { + i_error("Invalid input in file %s", path); + io_disabled = TRUE; + } + } T_END; + } + if (close(fd) < 0) + i_error("close(%s) failed: %m", path); +} + +static void trans_stats_dec(struct mailbox_transaction_stats *dest, + const struct mailbox_transaction_stats *src) +{ + dest->open_lookup_count -= src->open_lookup_count; + dest->stat_lookup_count -= src->stat_lookup_count; + dest->fstat_lookup_count -= src->fstat_lookup_count; + dest->files_read_count -= src->files_read_count; + dest->files_read_bytes -= src->files_read_bytes; + dest->cache_hit_count -= src->cache_hit_count; +} + +static void trans_stats_add(struct mailbox_transaction_stats *dest, + const struct mailbox_transaction_stats *src) +{ + dest->open_lookup_count += src->open_lookup_count; + dest->stat_lookup_count += src->stat_lookup_count; + dest->fstat_lookup_count += src->fstat_lookup_count; + dest->files_read_count += src->files_read_count; + dest->files_read_bytes += src->files_read_bytes; + dest->cache_hit_count += src->cache_hit_count; +} + +static void user_trans_stats_get(struct stats_user *suser, + struct mailbox_transaction_stats *dest_r) +{ + struct stats_transaction_context *strans; + + memset(dest_r, 0, sizeof(*dest_r)); + strans = suser->transactions; + for (; strans != NULL; strans = strans->next) + trans_stats_add(dest_r, &strans->trans->stats); +} + +void mail_stats_get(struct stats_user *suser, struct mail_stats *stats_r) +{ + struct rusage usage; + + memset(stats_r, 0, sizeof(*stats_r)); + /* cputime */ + if (getrusage(RUSAGE_SELF, &usage) < 0) + memset(&usage, 0, sizeof(usage)); + stats_r->cpu_secs = usage.ru_utime; + /* disk io */ + process_read_io_stats(&stats_r->disk_input, &stats_r->disk_output); + user_trans_stats_get(suser, &stats_r->trans_stats); +} + +static struct mailbox_transaction_context * +stats_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags) +{ + struct stats_user *suser = STATS_USER_CONTEXT(box->storage->user); + struct stats_mailbox *sbox = STATS_CONTEXT(box); + struct mailbox_transaction_context *trans; + struct stats_transaction_context *strans; + + trans = sbox->module_ctx.super.transaction_begin(box, flags); + trans->stats_track = TRUE; + + strans = i_new(struct stats_transaction_context, 1); + strans->trans = trans; + DLLIST_PREPEND(&suser->transactions, strans); + + MODULE_CONTEXT_SET(trans, stats_storage_module, strans); + return trans; +} + +static void stats_transaction_free(struct stats_user *suser, + struct stats_transaction_context *strans) +{ + DLLIST_REMOVE(&suser->transactions, strans); + + trans_stats_add(&suser->session_stats.trans_stats, + &strans->trans->stats); +} + +static int +stats_transaction_commit(struct mailbox_transaction_context *ctx, + struct mail_transaction_commit_changes *changes_r) +{ + struct stats_transaction_context *strans = STATS_CONTEXT(ctx); + struct stats_mailbox *sbox = STATS_CONTEXT(ctx->box); + struct stats_user *suser = STATS_USER_CONTEXT(ctx->box->storage->user); + + stats_transaction_free(suser, strans); + return sbox->module_ctx.super.transaction_commit(ctx, changes_r); +} + +static void +stats_transaction_rollback(struct mailbox_transaction_context *ctx) +{ + struct stats_transaction_context *strans = STATS_CONTEXT(ctx); + struct stats_mailbox *sbox = STATS_CONTEXT(ctx->box); + struct stats_user *suser = STATS_USER_CONTEXT(ctx->box->storage->user); + + stats_transaction_free(suser, strans); + sbox->module_ctx.super.transaction_rollback(ctx); +} + +static void stats_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct stats_mailbox *sbox; + + sbox = p_new(box->pool, struct stats_mailbox, 1); + sbox->module_ctx.super = *v; + box->vlast = &sbox->module_ctx.super; + + v->transaction_begin = stats_transaction_begin; + v->transaction_commit = stats_transaction_commit; + v->transaction_rollback = stats_transaction_rollback; + MODULE_CONTEXT_SET(box, stats_storage_module, sbox); +} + +static void stats_io_activate(void *context) +{ + struct mail_user *user = context; + struct stats_user *suser = STATS_USER_CONTEXT(user); + + mail_stats_get(suser, &suser->pre_io_stats); +} + +void mail_stats_add_diff(struct mail_stats *dest, + const struct mail_stats *old_stats, + const struct mail_stats *new_stats) +{ + struct timeval *tv; + long long usecs; + + dest->disk_input += new_stats->disk_input - old_stats->disk_input; + dest->disk_output += new_stats->disk_output - old_stats->disk_output; + + usecs = timeval_diff_usecs(&new_stats->cpu_secs, &old_stats->cpu_secs); + tv = &dest->cpu_secs; + tv->tv_sec += usecs / USECS_PER_SEC; + tv->tv_usec += usecs % USECS_PER_SEC; + if (tv->tv_usec > USECS_PER_SEC) { + tv->tv_usec -= USECS_PER_SEC; + tv->tv_sec++; + } + trans_stats_dec(&dest->trans_stats, &old_stats->trans_stats); + trans_stats_add(&dest->trans_stats, &new_stats->trans_stats); +} + +void mail_stats_export(string_t *str, const struct mail_stats *stats) +{ + const struct mailbox_transaction_stats *tstats = &stats->trans_stats; + + str_printfa(str, "\tcpu=%ld.%ld", (long)stats->cpu_secs.tv_sec, + (long)stats->cpu_secs.tv_usec); + str_printfa(str, "\tdiskin=%llu", + (unsigned long long)stats->disk_input); + str_printfa(str, "\tdiskout=%llu", + (unsigned long long)stats->disk_output); + str_printfa(str, "\tlpath=%lu", + tstats->open_lookup_count + tstats->stat_lookup_count); + str_printfa(str, "\tlattr=%lu", + tstats->fstat_lookup_count + tstats->stat_lookup_count); + str_printfa(str, "\trcount=%lu", tstats->files_read_count); + str_printfa(str, "\trbytes=%llu", tstats->files_read_bytes); + str_printfa(str, "\tcache=%lu", tstats->cache_hit_count); +} + +static void stats_io_deactivate(void *context) +{ + struct mail_user *user = context; + struct stats_user *suser = STATS_USER_CONTEXT(user); + struct mail_stats new_stats; + + mail_stats_get(suser, &new_stats); + mail_stats_add_diff(&suser->session_stats, &suser->pre_io_stats, + &new_stats); +} + +static bool session_stats_need_send(struct stats_user *suser) +{ + if (memcmp(&suser->last_sent_session_stats, &suser->session_stats, + sizeof(suser->session_stats)) != 0) + return TRUE; + + return ioloop_time - suser->last_session_update >= + SESSION_STATS_FORCE_REFRESH_SECS; +} + +static void session_stats_refresh(struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT(user); + + if (session_stats_need_send(suser)) { + suser->last_session_update = ioloop_time; + suser->last_sent_session_stats = suser->session_stats; + stats_connection_send_session(suser->stats_conn, user, + &suser->session_stats); + } +} + +static void stats_user_deinit(struct mail_user *user) +{ + struct stats_user *suser = STATS_USER_CONTEXT(user); + struct stats_connection *stats_conn = suser->stats_conn; + + io_loop_context_remove_callbacks(suser->ioloop_ctx, + stats_io_activate, + stats_io_deactivate, user); + /* send final stats before disconnection */ + session_stats_refresh(user); + stats_connection_disconnect(stats_conn, user); + + timeout_remove(&suser->to_stats_timeout); + suser->module_ctx.super.deinit(user); + + stats_connection_unref(&stats_conn); +} + +static void stats_user_created(struct mail_user *user) +{ + struct ioloop_context *ioloop_ctx = + io_loop_get_current_context(current_ioloop); + struct stats_user *suser; + struct mail_user_vfuncs *v = user->vlast; + const char *path; + + if (ioloop_ctx == NULL) { + /* we're probably running some test program, or at least + mail-storage-service wasn't used to create this user. + disable stats tracking. */ + return; + } + + if (global_stats_conn == NULL) { + path = t_strconcat(user->set->base_dir, + "/"MAIL_STATS_SOCKET_NAME, NULL); + global_stats_conn = stats_connection_create(path); + } else { + stats_connection_ref(global_stats_conn); + } + + suser = p_new(user->pool, struct stats_user, 1); + suser->module_ctx.super = *v; + user->vlast = &suser->module_ctx.super; + user->v.deinit = stats_user_deinit; + + suser->stats_conn = global_stats_conn; + guid_128_generate(suser->session_guid); + suser->last_session_update = ioloop_time; + suser->to_stats_timeout = timeout_add(SESSION_STATS_REFRESH_MSECS, + session_stats_refresh, user); + + suser->ioloop_ctx = ioloop_ctx; + io_loop_context_add_callbacks(ioloop_ctx, + stats_io_activate, + stats_io_deactivate, user); + + MODULE_CONTEXT_SET(user, stats_user_module, suser); + stats_connection_connect(suser->stats_conn, user); +} + +static struct mail_storage_hooks stats_mail_storage_hooks = { + .mailbox_allocated = stats_mailbox_allocated, + .mail_user_created = stats_user_created +}; + +void stats_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &stats_mail_storage_hooks); +} + +void stats_plugin_deinit(void) +{ + mail_storage_hooks_remove(&stats_mail_storage_hooks); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/stats/stats-plugin.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,51 @@ +#ifndef STATS_PLUGIN_H +#define STATS_PLUGIN_H + +#include "module-context.h" +#include "guid.h" +#include "mail-user.h" +#include "mail-storage-private.h" + +#define STATS_USER_CONTEXT(obj) \ + MODULE_CONTEXT(obj, stats_user_module) + +struct mail_stats { + struct timeval cpu_secs; + uint64_t disk_input, disk_output; + struct mailbox_transaction_stats trans_stats; +}; + +struct stats_user { + union mail_user_module_context module_ctx; + + struct ioloop_context *ioloop_ctx; + struct stats_connection *stats_conn; + guid_128_t session_guid; + + /* current session statistics */ + struct mail_stats session_stats; + /* stats before calling IO callback. after IO callback this value is + compared to current stats to see the difference */ + struct mail_stats pre_io_stats; + + time_t last_session_update; + struct timeout *to_stats_timeout; + /* stats that were last sent to stats server */ + struct mail_stats last_sent_session_stats; + + /* list of all currently existing transactions for this user */ + struct stats_transaction_context *transactions; +}; + +extern MODULE_CONTEXT_DEFINE(stats_user_module, &mail_user_module_register); + +void mail_stats_get(struct stats_user *suser, struct mail_stats *stats_r); +void mail_stats_add_diff(struct mail_stats *dest, + const struct mail_stats *old_stats, + const struct mail_stats *new_stats); +void mail_stats_export(string_t *str, const struct mail_stats *stats); + +void stats_plugin_init(struct module *module); +void stats_plugin_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/Makefile.am Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,40 @@ +pkglibexecdir = $(libexecdir)/dovecot + +pkglibexec_PROGRAMS = stats + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master + +stats_LDADD = \ + $(LIBDOVECOT) \ + $(MODULE_LIBS) +stats_DEPENDENCIES = $(LIBDOVECOT_DEPS) + +stats_SOURCES = \ + client.c \ + client-export.c \ + global-memory.c \ + mail-command.c \ + mail-domain.c \ + mail-ip.c \ + mail-server-connection.c \ + mail-session.c \ + mail-stats.c \ + mail-user.c \ + main.c \ + stats-settings.c + +noinst_HEADERS = \ + client.h \ + client-export.h \ + global-memory.h \ + mail-command.h \ + mail-domain.h \ + mail-ip.h \ + mail-server-connection.h \ + mail-session.h \ + mail-stats.h \ + mail-user.h \ + stats-settings.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/client-export.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,586 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "network.h" +#include "ostream.h" +#include "str.h" +#include "strescape.h" +#include "wildcard-match.h" +#include "mail-stats.h" +#include "mail-command.h" +#include "mail-session.h" +#include "mail-user.h" +#include "mail-domain.h" +#include "mail-ip.h" +#include "client.h" +#include "client-export.h" + +enum mail_export_level { + MAIL_EXPORT_LEVEL_COMMAND, + MAIL_EXPORT_LEVEL_SESSION, + MAIL_EXPORT_LEVEL_USER, + MAIL_EXPORT_LEVEL_DOMAIN, + MAIL_EXPORT_LEVEL_IP +}; +static const char *mail_export_level_names[] = { + "command", "session", "user", "domain", "ip" +}; + +struct mail_export_filter { + const char *user, *domain; + struct ip_addr ip; + unsigned int ip_bits; + time_t since; + bool connected; +}; + +struct client_export_cmd { + enum mail_export_level level; + struct mail_export_filter filter; + string_t *str; + int (*export_iter)(struct client *client); + bool header_sent; +}; + +static int +mail_export_level_parse(const char *str, enum mail_export_level *level_r) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(mail_export_level_names); i++) { + if (strcmp(mail_export_level_names[i], str) == 0) { + *level_r = (enum mail_export_level)i; + return 0; + } + } + return -1; +} + +static int +mail_export_parse_filter(const char *const *args, pool_t pool, + struct mail_export_filter *filter_r, + const char **error_r) +{ + unsigned long l; + + /* filters: + user=<wildcard> | domain=<wildcard> + ip=<ip>[/<mask>] + since=<timestamp> + connected + */ + memset(filter_r, 0, sizeof(*filter_r)); + for (; *args != NULL; args++) { + if (strncmp(*args, "user=", 5) == 0) + filter_r->user = p_strdup(pool, *args + 5); + else if (strncmp(*args, "domain=", 7) == 0) + filter_r->domain = p_strdup(pool, *args + 7); + else if (strncmp(*args, "ip=", 3) == 0) { + if (net_parse_range(*args + 3, &filter_r->ip, + &filter_r->ip_bits) < 0) { + *error_r = "Invalid ip filter"; + return -1; + } + } else if (strncmp(*args, "since=", 6) == 0) { + if (str_to_ulong(*args + 6, &l) < 0) { + *error_r = "Invalid since filter"; + return -1; + } + filter_r->since = (time_t)l; + } else if (strcmp(*args, "connected") == 0) { + filter_r->connected = TRUE; + } + } + return 0; +} + +static void +client_export_mail_stats(string_t *str, const struct mail_stats *stats) +{ +#define MAIL_STATS_HEADER "\tcpu\tdisk_input\tdisk_output" \ + "\tlookup_path\tlookup_attr\tread_count\tread_bytes\tcache_hits\n" + + str_printfa(str, "\t%ld.%u", (long)stats->cpu_secs.tv_sec, + (unsigned int)stats->cpu_secs.tv_usec); + str_printfa(str, "\t%llu\t%llu", + (unsigned long long)stats->disk_input, + (unsigned long long)stats->disk_output); + str_printfa(str, "\t%u\t%u\t%u\t%llu\t%u", + stats->lookup_path, stats->lookup_attr, + stats->read_count, + (unsigned long long)stats->read_bytes, + stats->cache_hits); +} + +static bool +mail_export_filter_match_session(const struct mail_export_filter *filter, + const struct mail_session *session) +{ + if (filter->connected && session->disconnected) + return FALSE; + if (filter->since > session->last_update) + return FALSE; + if (filter->user != NULL && + !wildcard_match(session->user->name, filter->user)) + return FALSE; + if (filter->domain != NULL && + !wildcard_match(session->user->domain->name, filter->domain)) + return FALSE; + if (filter->ip_bits > 0 && + !net_is_in_network(&session->ip->ip, &filter->ip, filter->ip_bits)) + return FALSE; + return TRUE; +} + +static bool +mail_export_filter_match_user_common(const struct mail_export_filter *filter, + const struct mail_user *user) +{ + struct mail_session *s; + bool connected = FALSE, ip_ok = FALSE; + + if (filter->user != NULL && + !wildcard_match(user->name, filter->user)) + return FALSE; + + if (filter->connected || filter->ip_bits > 0) { + for (s = user->sessions; s != NULL; s = s->user_next) { + if (!s->disconnected) + connected = TRUE; + if (filter->ip_bits > 0 && + net_is_in_network(&s->ip->ip, &filter->ip, + filter->ip_bits)) + ip_ok = TRUE; + + } + if (filter->connected && !connected) + return FALSE; + if (filter->ip_bits > 0 && !ip_ok) + return FALSE; + } + return TRUE; +} + +static bool +mail_export_filter_match_user(const struct mail_export_filter *filter, + const struct mail_user *user) +{ + if (filter->since > user->last_update) + return FALSE; + if (filter->domain != NULL && + !wildcard_match(user->domain->name, filter->domain)) + return FALSE; + return mail_export_filter_match_user_common(filter, user); +} + +static bool +mail_export_filter_match_domain(const struct mail_export_filter *filter, + const struct mail_domain *domain) +{ + struct mail_user *user; + + if (filter->since > domain->last_update) + return FALSE; + if (filter->domain != NULL && + !wildcard_match(domain->name, filter->domain)) + return FALSE; + + if (filter->user != NULL || filter->connected || filter->ip_bits > 0) { + for (user = domain->users; user != NULL; user = user->domain_next) { + if (mail_export_filter_match_user_common(filter, user)) + break; + } + if (user == NULL) + return FALSE; + } + return TRUE; +} + +static bool +mail_export_filter_match_ip(const struct mail_export_filter *filter, + const struct mail_ip *ip) +{ + struct mail_session *s; + bool connected = FALSE, user_ok = FALSE, domain_ok = FALSE; + + if (filter->connected || filter->ip_bits > 0) { + for (s = ip->sessions; s != NULL; s = s->ip_next) { + if (!s->disconnected) + connected = TRUE; + if (filter->user != NULL && + wildcard_match(s->user->name, filter->user)) + user_ok = TRUE; + if (filter->domain != NULL && + wildcard_match(s->user->domain->name, filter->domain)) + domain_ok = TRUE; + } + if (filter->connected && !connected) + return FALSE; + if (filter->user != NULL && !user_ok) + return FALSE; + if (filter->domain != NULL && !domain_ok) + return FALSE; + } + if (filter->since > ip->last_update) + return FALSE; + if (filter->ip_bits > 0 && + !net_is_in_network(&ip->ip, &filter->ip, filter->ip_bits)) + return FALSE; + return TRUE; +} + +static int client_export_iter_command(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_command *command = client->mail_cmd_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_SESSION); + mail_command_unref(&client->mail_cmd_iter); + + if (!cmd->header_sent) { + o_stream_send_str(client->output, + "cmd\targs\tsession\tlast_update"MAIL_STATS_HEADER); + cmd->header_sent = TRUE; + } + + for (; command != NULL; command = command->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_session(&cmd->filter, + command->session)) + continue; + + str_truncate(cmd->str, 0); + str_tabescape_write(cmd->str, command->name); + str_append_c(cmd->str, '\t'); + str_tabescape_write(cmd->str, command->args); + T_BEGIN { + str_append(cmd->str, + guid_128_to_string(command->session->guid)); + } T_END; + str_printfa(cmd->str, "\t%ld", (long)command->last_update); + client_export_mail_stats(cmd->str, &command->stats); + str_append_c(cmd->str, '\n'); + o_stream_send(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (command != NULL) { + client->mail_cmd_iter = command; + mail_command_ref(command); + return 0; + } + return 1; +} + +static int client_export_iter_session(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_session *session = client->mail_session_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_SESSION); + mail_session_unref(&client->mail_session_iter); + + if (!cmd->header_sent) { + o_stream_send_str(client->output, + "session\tuser\tservice\tconnected" + "\tlast_update\tnum_cmds" + MAIL_STATS_HEADER); + cmd->header_sent = TRUE; + } + + for (; session != NULL; session = session->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_session(&cmd->filter, session)) + continue; + + str_truncate(cmd->str, 0); + T_BEGIN { + str_append(cmd->str, guid_128_to_string(session->guid)); + } T_END; + str_append_c(cmd->str, '\t'); + str_tabescape_write(cmd->str, session->user->name); + str_append_c(cmd->str, '\t'); + str_tabescape_write(cmd->str, session->service); + str_printfa(cmd->str, "\t%d\t%ld\t%u", + !session->disconnected, + (long)session->last_update, + session->num_cmds); + client_export_mail_stats(cmd->str, &session->stats); + str_append_c(cmd->str, '\n'); + o_stream_send(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (session != NULL) { + client->mail_session_iter = session; + mail_session_ref(session); + return 0; + } + return 1; +} + +static int client_export_iter_user(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_user *user = client->mail_user_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_USER); + mail_user_unref(&client->mail_user_iter); + + if (!cmd->header_sent) { + o_stream_send_str(client->output, + "user\treset_timestamp\tlast_update" + "\tnum_logins\tnum_cmds"MAIL_STATS_HEADER); + cmd->header_sent = TRUE; + } + + for (; user != NULL; user = user->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_user(&cmd->filter, user)) + continue; + + str_truncate(cmd->str, 0); + str_tabescape_write(cmd->str, user->name); + str_printfa(cmd->str, "\t%ld\t%ld\t%u\t%u", + (long)user->reset_timestamp, + (long)user->last_update, + user->num_logins, user->num_cmds); + client_export_mail_stats(cmd->str, &user->stats); + str_append_c(cmd->str, '\n'); + o_stream_send(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (user != NULL) { + client->mail_user_iter = user; + mail_user_ref(user); + return 0; + } + return 1; +} + +static int client_export_iter_domain(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_domain *domain = client->mail_domain_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_DOMAIN); + mail_domain_unref(&client->mail_domain_iter); + + if (!cmd->header_sent) { + o_stream_send_str(client->output, + "domain\treset_timestamp\tlast_update" + "\tnum_logins\tnum_cmds"MAIL_STATS_HEADER); + cmd->header_sent = TRUE; + } + + for (; domain != NULL; domain = domain->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_domain(&cmd->filter, domain)) + continue; + + str_truncate(cmd->str, 0); + str_tabescape_write(cmd->str, domain->name); + str_printfa(cmd->str, "\t%ld\t%ld\t%u\t%u", + (long)domain->reset_timestamp, + (long)domain->last_update, + domain->num_logins, domain->num_cmds); + client_export_mail_stats(cmd->str, &domain->stats); + str_append_c(cmd->str, '\n'); + o_stream_send(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (domain != NULL) { + client->mail_domain_iter = domain; + mail_domain_ref(domain); + return 0; + } + return 1; +} + +static int client_export_iter_ip(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + struct mail_ip *ip = client->mail_ip_iter; + + i_assert(cmd->level == MAIL_EXPORT_LEVEL_IP); + mail_ip_unref(&client->mail_ip_iter); + + for (; ip != NULL; ip = ip->stable_next) { + if (client_is_busy(client)) + break; + if (!mail_export_filter_match_ip(&cmd->filter, ip)) + continue; + + str_truncate(cmd->str, 0); + T_BEGIN { + str_append(cmd->str, net_ip2addr(&ip->ip)); + } T_END; + str_printfa(cmd->str, "\t%ld\t%ld\t%u\t%u", + (long)ip->reset_timestamp, + (long)ip->last_update, + ip->num_logins, ip->num_cmds); + client_export_mail_stats(cmd->str, &ip->stats); + str_append_c(cmd->str, '\n'); + o_stream_send(client->output, str_data(cmd->str), + str_len(cmd->str)); + } + + if (ip != NULL) { + client->mail_ip_iter = ip; + mail_ip_ref(ip); + return 0; + } + return 1; +} + +static int client_export_more(struct client *client) +{ + if (client->cmd_export->export_iter(client) == 0) + return 0; + o_stream_send_str(client->output, "\n"); + return 1; +} + +static bool client_export_iter_init(struct client *client) +{ + struct client_export_cmd *cmd = client->cmd_export; + + if (cmd->filter.user != NULL && strchr(cmd->filter.user, '*') == NULL && + (cmd->level == MAIL_EXPORT_LEVEL_USER || + cmd->level == MAIL_EXPORT_LEVEL_SESSION)) { + /* exact user */ + struct mail_user *user = mail_user_lookup(cmd->filter.user); + if (user == NULL) + return FALSE; + if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) { + client->mail_session_iter = user->sessions; + if (client->mail_session_iter == NULL) + return FALSE; + mail_session_ref(client->mail_session_iter); + cmd->export_iter = client_export_iter_session; + } else { + client->mail_user_iter = user; + mail_user_ref(user); + cmd->export_iter = client_export_iter_user; + } + return TRUE; + } + if (cmd->filter.ip_bits == IPADDR_BITS(&cmd->filter.ip) && + (cmd->level == MAIL_EXPORT_LEVEL_IP || + cmd->level == MAIL_EXPORT_LEVEL_SESSION)) { + /* exact IP address */ + struct mail_ip *ip = mail_ip_lookup(&cmd->filter.ip); + if (ip == NULL) + return FALSE; + if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) { + client->mail_session_iter = ip->sessions; + if (client->mail_session_iter == NULL) + return FALSE; + mail_session_ref(client->mail_session_iter); + cmd->export_iter = client_export_iter_session; + } else { + client->mail_ip_iter = ip; + mail_ip_ref(ip); + cmd->export_iter = client_export_iter_ip; + } + return TRUE; + } + if (cmd->filter.domain != NULL && + strchr(cmd->filter.domain, '*') == NULL && + (cmd->level == MAIL_EXPORT_LEVEL_DOMAIN || + cmd->level == MAIL_EXPORT_LEVEL_USER)) { + /* exact domain */ + struct mail_domain *domain = + mail_domain_lookup(cmd->filter.domain); + if (domain == NULL) + return FALSE; + if (cmd->level == MAIL_EXPORT_LEVEL_USER) { + client->mail_user_iter = domain->users; + mail_user_ref(client->mail_user_iter); + cmd->export_iter = client_export_iter_user; + } else { + client->mail_domain_iter = domain; + mail_domain_ref(domain); + cmd->export_iter = client_export_iter_domain; + } + return TRUE; + } + + switch (cmd->level) { + case MAIL_EXPORT_LEVEL_COMMAND: + client->mail_cmd_iter = stable_mail_commands; + if (client->mail_cmd_iter == NULL) + return FALSE; + mail_command_ref(client->mail_cmd_iter); + cmd->export_iter = client_export_iter_command; + break; + case MAIL_EXPORT_LEVEL_SESSION: + client->mail_session_iter = stable_mail_sessions; + if (client->mail_session_iter == NULL) + return FALSE; + mail_session_ref(client->mail_session_iter); + cmd->export_iter = client_export_iter_session; + break; + case MAIL_EXPORT_LEVEL_USER: + client->mail_user_iter = stable_mail_users; + if (client->mail_user_iter == NULL) + return FALSE; + mail_user_ref(client->mail_user_iter); + cmd->export_iter = client_export_iter_user; + break; + case MAIL_EXPORT_LEVEL_DOMAIN: + client->mail_domain_iter = stable_mail_domains; + if (client->mail_domain_iter == NULL) + return FALSE; + mail_domain_ref(client->mail_domain_iter); + cmd->export_iter = client_export_iter_domain; + break; + case MAIL_EXPORT_LEVEL_IP: + client->mail_ip_iter = stable_mail_ips; + if (client->mail_ip_iter == NULL) + return FALSE; + mail_ip_ref(client->mail_ip_iter); + cmd->export_iter = client_export_iter_ip; + break; + } + i_assert(cmd->export_iter != NULL); + return TRUE; +} + +int client_export(struct client *client, const char *const *args, + const char **error_r) +{ + const char *level_str = args[0]; + struct client_export_cmd *cmd; + + p_clear(client->cmd_pool); + cmd = p_new(client->cmd_pool, struct client_export_cmd, 1); + cmd->str = str_new(client->cmd_pool, 256); + + if (level_str == NULL) { + *error_r = "Missing level parameter"; + return -1; + } + if (mail_export_level_parse(level_str, &cmd->level) < 0) { + *error_r = "Invalid level"; + return -1; + } + if (mail_export_parse_filter(args + 1, client->cmd_pool, + &cmd->filter, error_r) < 0) + return -1; + + client->cmd_export = cmd; + if (!client_export_iter_init(client)) { + /* nothing to export */ + o_stream_send_str(client->output, "\n"); + return 1; + } + client->cmd_more = client_export_more; + return client_export_more(client); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/client-export.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,9 @@ +#ifndef CLIENT_EXPORT_H +#define CLIENT_EXPORT_H + +struct client; + +int client_export(struct client *client, const char *const *args, + const char **error_r); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/client.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,198 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "strescape.h" +#include "master-service.h" +#include "mail-command.h" +#include "mail-session.h" +#include "mail-user.h" +#include "mail-domain.h" +#include "mail-ip.h" +#include "client-export.h" +#include "client.h" + +#include <unistd.h> + +#define CLIENT_MAX_SIMULTANEOUS_ITER_COUNT 1000 +#define MAX_INBUF_SIZE 1024 +#define OUTBUF_THROTTLE_SIZE (1024*64) + +static struct client *clients; + +bool client_is_busy(struct client *client) +{ + client->iter_count++; + if (client->iter_count % CLIENT_MAX_SIMULTANEOUS_ITER_COUNT == 0) + return TRUE; + if (o_stream_get_buffer_used_size(client->output) < OUTBUF_THROTTLE_SIZE) + return FALSE; + if (o_stream_flush(client->output) < 0) + return TRUE; + return o_stream_get_buffer_used_size(client->output) >= OUTBUF_THROTTLE_SIZE; +} + +static int +client_handle_request(struct client *client, const char *const *args, + const char **error_r) +{ + const char *cmd = args[0]; + + if (cmd == NULL) { + *error_r = "Missing command"; + return -1; + } + args++; + + if (strcmp(cmd, "EXPORT") == 0) + return client_export(client, args, error_r); + + *error_r = "Unknown command"; + return -1; +} + +static const char *const* +client_read_next_line(struct client *client) +{ + const char *line; + char **args; + unsigned int i; + + line = i_stream_next_line(client->input); + if (line == NULL) + return NULL; + + args = p_strsplit(pool_datastack_create(), line, "\t"); + for (i = 0; args[i] != NULL; i++) + args[i] = str_tabunescape(args[i]); + return (void *)args; +} + +static void client_input(struct client *client) +{ + const char *const *args, *error; + int ret; + + if (client->to_pending != NULL) + timeout_remove(&client->to_pending); + + switch (i_stream_read(client->input)) { + case -2: + i_error("BUG: Stats client sent too much data"); + client_destroy(&client); + return; + case -1: + client_destroy(&client); + return; + } + + o_stream_cork(client->output); + while ((args = client_read_next_line(client)) != NULL) { + ret = client_handle_request(client, args, &error); + if (ret < 0) { + i_error("Stats client input error: %s", error); + client_destroy(&client); + return; + } + if (ret == 0) { + o_stream_set_flush_pending(client->output, TRUE); + io_remove(&client->io); + break; + } + client->cmd_more = NULL; + } + o_stream_uncork(client->output); +} + +static int client_output(struct client *client) +{ + int ret = 1; + + o_stream_cork(client->output); + if (o_stream_flush(client->output) < 0) { + client_destroy(&client); + return 1; + } + if (client->cmd_more != NULL) + ret = client->cmd_more(client); + o_stream_uncork(client->output); + + if (ret > 0) { + client->cmd_more = NULL; + if (client->io == NULL) + client_enable_io(client); + } + return ret; +} + +void client_enable_io(struct client *client) +{ + i_assert(client->io == NULL); + + client->io = io_add(client->fd, IO_READ, client_input, client); + if (client->to_pending == NULL) + client->to_pending = timeout_add(0, client_input, client); +} + +struct client *client_create(int fd) +{ + struct client *client; + + client = i_new(struct client, 1); + client->fd = fd; + client->io = io_add(fd, IO_READ, client_input, client); + client->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE); + client->output = o_stream_create_fd(fd, (size_t)-1, FALSE); + o_stream_set_flush_callback(client->output, client_output, client); + client->cmd_pool = pool_alloconly_create("cmd pool", 1024); + + DLLIST_PREPEND(&clients, client); + return client; +} + +static void client_unref_iters(struct client *client) +{ + if (client->mail_cmd_iter != NULL) + mail_command_unref(&client->mail_cmd_iter); + if (client->mail_session_iter != NULL) + mail_session_unref(&client->mail_session_iter); + if (client->mail_user_iter != NULL) + mail_user_unref(&client->mail_user_iter); + if (client->mail_domain_iter != NULL) + mail_domain_unref(&client->mail_domain_iter); + if (client->mail_ip_iter != NULL) + mail_ip_unref(&client->mail_ip_iter); +} + +void client_destroy(struct client **_client) +{ + struct client *client = *_client; + + *_client = NULL; + + DLLIST_REMOVE(&clients, client); + if (client->io != NULL) + io_remove(&client->io); + i_stream_destroy(&client->input); + o_stream_destroy(&client->output); + if (close(client->fd) < 0) + i_error("close(client) failed: %m"); + + client_unref_iters(client); + pool_unref(&client->cmd_pool); + i_free(client); + + master_service_client_connection_destroyed(master_service); +} + +void clients_destroy_all(void) +{ + while (clients != NULL) { + struct client *client = clients; + + client_destroy(&client); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/client.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,35 @@ +#ifndef CLIENT_H +#define CLIENT_H + +struct client { + struct client *prev, *next; + + int fd; + struct io *io; + struct istream *input; + struct ostream *output; + struct timeout *to_pending; + + pool_t cmd_pool; + struct client_export_cmd *cmd_export; + int (*cmd_more)(struct client *client); + + /* command iterators. while non-NULL, they've increased the + struct's refcount so it won't be deleted during iteration */ + unsigned int iter_count; + struct mail_command *mail_cmd_iter; + struct mail_session *mail_session_iter; + struct mail_user *mail_user_iter; + struct mail_domain *mail_domain_iter; + struct mail_ip *mail_ip_iter; +}; + +struct client *client_create(int fd); +void client_destroy(struct client **client); + +bool client_is_busy(struct client *client); +void client_enable_io(struct client *client); + +void clients_destroy_all(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/global-memory.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,46 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-command.h" +#include "mail-session.h" +#include "mail-user.h" +#include "mail-domain.h" +#include "mail-ip.h" +#include "stats-settings.h" +#include "global-memory.h" + +size_t global_used_memory = 0; + +static bool global_memory_free_something(void) +{ + size_t orig_used_memory = global_used_memory; + + mail_commands_free_memory(); + if (global_used_memory > stats_settings->memory_limit) + mail_sessions_free_memory(); + if (global_used_memory > stats_settings->memory_limit) + mail_users_free_memory(); + if (global_used_memory > stats_settings->memory_limit) + mail_ips_free_memory(); + if (global_used_memory > stats_settings->memory_limit) + mail_domains_free_memory(); + + return global_used_memory < orig_used_memory; +} + +void global_memory_alloc(size_t size) +{ + i_assert(size < (size_t)-1 - global_used_memory); + global_used_memory += size; + + while (global_used_memory > stats_settings->memory_limit) { + if (!global_memory_free_something()) + break; + } +} + +void global_memory_free(size_t size) +{ + i_assert(size <= global_used_memory); + global_used_memory -= size; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/global-memory.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,9 @@ +#ifndef GLOBAL_MEMORY_H +#define GLOBAL_MEMORY_H + +extern size_t global_used_memory; + +void global_memory_alloc(size_t size); +void global_memory_free(size_t size); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-command.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,175 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "llist.h" +#include "global-memory.h" +#include "stats-settings.h" +#include "mail-stats.h" +#include "mail-session.h" +#include "mail-command.h" + +/* commands are sorted by their last_update timestamp, oldest first */ +struct mail_command *stable_mail_commands; + +static size_t mail_command_memsize(const struct mail_command *cmd) +{ + return sizeof(*cmd) + strlen(cmd->name) + 1 + strlen(cmd->args) + 1; +} + +static struct mail_command * +mail_command_find(struct mail_session *session, unsigned int id) +{ + struct mail_command *cmd; + + i_assert(id != 0); + + if (id > session->highest_cmd_id) { + /* fast path for new commands */ + return NULL; + } + for (cmd = session->commands; cmd != NULL; cmd = cmd->session_next) { + if (cmd->id == id) + return cmd; + } + /* expired */ + return NULL; +} + +static struct mail_command * +mail_command_add(struct mail_session *session, const char *name, + const char *args) +{ + struct mail_command *cmd; + + cmd = i_new(struct mail_command, 1); + cmd->refcount = 1; /* unrefed at "done" */ + cmd->session = session; + cmd->name = i_strdup(name); + cmd->args = i_strdup(args); + cmd->last_update = ioloop_time; + + DLLIST_PREPEND_FULL(&stable_mail_commands, cmd, + stable_prev, stable_next); + DLLIST_PREPEND_FULL(&session->commands, cmd, + session_prev, session_next); + mail_session_ref(cmd->session); + global_memory_alloc(mail_command_memsize(cmd)); + return cmd; +} + +static void mail_command_free(struct mail_command *cmd) +{ + i_assert(cmd->refcount == 0); + + global_memory_free(mail_command_memsize(cmd)); + + DLLIST_REMOVE_FULL(&stable_mail_commands, cmd, + stable_prev, stable_next); + DLLIST_REMOVE_FULL(&cmd->session->commands, cmd, + session_prev, session_next); + mail_session_unref(&cmd->session); + i_free(cmd->name); + i_free(cmd->args); + i_free(cmd); +} + +void mail_command_ref(struct mail_command *cmd) +{ + cmd->refcount++; +} + +void mail_command_unref(struct mail_command **_cmd) +{ + struct mail_command *cmd = *_cmd; + + i_assert(cmd->refcount > 0); + cmd->refcount--; + + *_cmd = NULL; +} + +int mail_command_update_parse(const char *const *args, const char **error_r) +{ + struct mail_session *session; + struct mail_command *cmd; + struct mail_stats stats, diff_stats; + unsigned int cmd_id; + bool done; + int ret; + + /* <session guid> <cmd id> <done> <name> <args> [key=value ..] */ + if (str_array_length(args) < 4) { + *error_r = "UPDATE-CMD: Too few parameters"; + return -1; + } + if ((ret = mail_session_lookup(args[0], &session, error_r)) <= 0) + return ret; + + if (str_to_uint(args[1], &cmd_id) < 0 || cmd_id == 0) { + *error_r = "UPDATE-CMD: Invalid command id"; + return -1; + } + if (strcmp(args[2], "0") != 0 && + strcmp(args[2], "1") != 0) { + *error_r = "UPDATE-CMD: Invalid done parameter"; + return -1; + } + done = args[2][0] == '1'; + if (mail_stats_parse(args+5, &stats, error_r) < 0) { + *error_r = t_strconcat("UPDATE-CMD: ", *error_r, NULL); + return -1; + } + + cmd = mail_command_find(session, cmd_id); + if (cmd == NULL) { + cmd = mail_command_add(session, args[3], args[4]); + cmd->id = cmd_id; + cmd->stats = stats; + diff_stats = stats; + + session->num_cmds++; + session->user->num_cmds++; + session->user->domain->num_cmds++; + if (session->ip != NULL) + session->ip->num_cmds++; + } else { + if (!mail_stats_diff(&cmd->stats, &stats, &diff_stats)) { + *error_r = "UPDATE-SESSION: stats shrank"; + return -1; + } + cmd->last_update = ioloop_time; + mail_stats_add(&session->stats, &diff_stats); + } + if (done) { + cmd->id = 0; + mail_command_unref(&cmd); + } + mail_session_refresh(session, &diff_stats); + return 0; +} + +void mail_commands_free_memory(void) +{ + while (stable_mail_commands != NULL && + stable_mail_commands->refcount == 0) { + i_assert(stable_mail_commands->id == 0); + mail_command_free(stable_mail_commands); + + if (global_used_memory < stats_settings->memory_limit) + break; + if (ioloop_time - + stable_mail_commands->last_update < stats_settings->command_min_time) + break; + } +} + +void mail_commands_init(void) +{ +} + +void mail_commands_deinit(void) +{ + while (stable_mail_commands != NULL) + mail_command_free(stable_mail_commands); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-command.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,17 @@ +#ifndef MAIL_COMMAND_H +#define MAIL_COMMAND_H + +struct mail_command; + +extern struct mail_command *stable_mail_commands; + +int mail_command_update_parse(const char *const *args, const char **error_r); + +void mail_command_ref(struct mail_command *cmd); +void mail_command_unref(struct mail_command **cmd); + +void mail_commands_free_memory(void); +void mail_commands_init(void); +void mail_commands_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-domain.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,121 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "hash.h" +#include "llist.h" +#include "global-memory.h" +#include "stats-settings.h" +#include "mail-stats.h" +#include "mail-domain.h" + +static struct hash_table *mail_domains_hash; +/* domains are sorted by their last_update timestamp, oldest first */ +static struct mail_domain *mail_domains_head, *mail_domains_tail; +struct mail_domain *stable_mail_domains; + +static size_t mail_domain_memsize(const struct mail_domain *domain) +{ + return sizeof(*domain) + strlen(domain->name) + 1; +} + +struct mail_domain *mail_domain_login(const char *name) +{ + struct mail_domain *domain; + + domain = hash_table_lookup(mail_domains_hash, name); + if (domain != NULL) { + domain->num_logins++; + mail_domain_refresh(domain, NULL); + return domain; + } + + domain = i_new(struct mail_domain, 1); + domain->name = i_strdup(name); + domain->reset_timestamp = ioloop_time; + + hash_table_insert(mail_domains_hash, domain->name, domain); + DLLIST_PREPEND_FULL(&stable_mail_domains, domain, + stable_prev, stable_next); + DLLIST2_APPEND_FULL(&mail_domains_head, &mail_domains_tail, domain, + sorted_prev, sorted_next); + domain->num_logins++; + domain->last_update = ioloop_time; + global_memory_alloc(mail_domain_memsize(domain)); + return domain; +} + +struct mail_domain *mail_domain_lookup(const char *name) +{ + return hash_table_lookup(mail_domains_hash, name); +} + +void mail_domain_ref(struct mail_domain *domain) +{ + domain->refcount++; +} + +void mail_domain_unref(struct mail_domain **_domain) +{ + struct mail_domain *domain = *_domain; + + i_assert(domain->refcount > 0); + domain->refcount--; + + *_domain = NULL; +} + +static void mail_domain_free(struct mail_domain *domain) +{ + i_assert(domain->refcount == 0); + i_assert(domain->users == NULL); + + global_memory_free(mail_domain_memsize(domain)); + hash_table_remove(mail_domains_hash, domain->name); + DLLIST_REMOVE_FULL(&stable_mail_domains, domain, + stable_prev, stable_next); + DLLIST2_REMOVE_FULL(&mail_domains_head, &mail_domains_tail, domain, + sorted_prev, sorted_next); + + i_free(domain->name); + i_free(domain); +} + +void mail_domain_refresh(struct mail_domain *domain, + const struct mail_stats *diff_stats) +{ + if (diff_stats != NULL) + mail_stats_add(&domain->stats, diff_stats); + domain->last_update = ioloop_time; + DLLIST2_REMOVE_FULL(&mail_domains_head, &mail_domains_tail, domain, + sorted_prev, sorted_next); + DLLIST2_APPEND_FULL(&mail_domains_head, &mail_domains_tail, domain, + sorted_prev, sorted_next); +} + +void mail_domains_free_memory(void) +{ + while (mail_domains_head != NULL && mail_domains_head->refcount == 0) { + mail_domain_free(mail_domains_head); + + if (global_used_memory < stats_settings->memory_limit) + break; + if (ioloop_time - + mail_domains_head->last_update < stats_settings->domain_min_time) + break; + } +} + +void mail_domains_init(void) +{ + mail_domains_hash = + hash_table_create(default_pool, default_pool, 0, + str_hash, (hash_cmp_callback_t *)strcmp); +} + +void mail_domains_deinit(void) +{ + while (mail_domains_head != NULL) + mail_domain_free(mail_domains_head); + hash_table_destroy(&mail_domains_hash); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-domain.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,20 @@ +#ifndef MAIL_DOMAIN_H +#define MAIL_DOMAIN_H + +struct mail_stats; + +extern struct mail_domain *stable_mail_domains; + +struct mail_domain *mail_domain_login(const char *name); +struct mail_domain *mail_domain_lookup(const char *name); +void mail_domain_refresh(struct mail_domain *domain, + const struct mail_stats *diff_stats); + +void mail_domain_ref(struct mail_domain *domain); +void mail_domain_unref(struct mail_domain **domain); + +void mail_domains_free_memory(void); +void mail_domains_init(void); +void mail_domains_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-ip.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,118 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "hash.h" +#include "llist.h" +#include "global-memory.h" +#include "stats-settings.h" +#include "mail-stats.h" +#include "mail-ip.h" + +static struct hash_table *mail_ips_hash; +/* ips are sorted by their last_update timestamp, oldest first */ +static struct mail_ip *mail_ips_head, *mail_ips_tail; +struct mail_ip *stable_mail_ips; + +static size_t mail_ip_memsize(const struct mail_ip *ip) +{ + return sizeof(*ip); +} + +struct mail_ip *mail_ip_login(const struct ip_addr *ip_addr) +{ + struct mail_ip *ip; + + ip = hash_table_lookup(mail_ips_hash, ip_addr); + if (ip != NULL) { + ip->num_logins++; + mail_ip_refresh(ip, NULL); + return ip; + } + + ip = i_new(struct mail_ip, 1); + ip->ip = *ip_addr; + ip->reset_timestamp = ioloop_time; + + hash_table_insert(mail_ips_hash, &ip->ip, ip); + DLLIST_PREPEND_FULL(&stable_mail_ips, ip, stable_prev, stable_next); + DLLIST2_APPEND_FULL(&mail_ips_head, &mail_ips_tail, ip, + sorted_prev, sorted_next); + ip->num_logins++; + ip->last_update = ioloop_time; + global_memory_alloc(mail_ip_memsize(ip)); + return ip; +} + +struct mail_ip *mail_ip_lookup(const struct ip_addr *ip_addr) +{ + return hash_table_lookup(mail_ips_hash, ip_addr); +} + +void mail_ip_ref(struct mail_ip *ip) +{ + ip->refcount++; +} + +void mail_ip_unref(struct mail_ip **_ip) +{ + struct mail_ip *ip = *_ip; + + i_assert(ip->refcount > 0); + ip->refcount--; + + *_ip = NULL; +} + +static void mail_ip_free(struct mail_ip *ip) +{ + i_assert(ip->refcount == 0); + i_assert(ip->sessions == NULL); + + global_memory_free(mail_ip_memsize(ip)); + hash_table_remove(mail_ips_hash, &ip->ip); + DLLIST_REMOVE_FULL(&stable_mail_ips, ip, stable_prev, stable_next); + DLLIST2_REMOVE_FULL(&mail_ips_head, &mail_ips_tail, ip, + sorted_prev, sorted_next); + + i_free(ip); +} + +void mail_ip_refresh(struct mail_ip *ip, const struct mail_stats *diff_stats) +{ + if (diff_stats != NULL) + mail_stats_add(&ip->stats, diff_stats); + ip->last_update = ioloop_time; + DLLIST2_REMOVE_FULL(&mail_ips_head, &mail_ips_tail, ip, + sorted_prev, sorted_next); + DLLIST2_APPEND_FULL(&mail_ips_head, &mail_ips_tail, ip, + sorted_prev, sorted_next); +} + +void mail_ips_free_memory(void) +{ + while (mail_ips_head != NULL && mail_ips_head->refcount == 0) { + mail_ip_free(mail_ips_head); + + if (global_used_memory < stats_settings->memory_limit) + break; + if (ioloop_time - + mail_ips_head->last_update < stats_settings->ip_min_time) + break; + } +} + +void mail_ips_init(void) +{ + mail_ips_hash = + hash_table_create(default_pool, default_pool, 0, + (hash_callback_t *)net_ip_hash, + (hash_cmp_callback_t *)net_ip_cmp); +} + +void mail_ips_deinit(void) +{ + while (mail_ips_head != NULL) + mail_ip_free(mail_ips_head); + hash_table_destroy(&mail_ips_hash); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-ip.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,17 @@ +#ifndef MAIL_IP_H +#define MAIL_IP_H + +extern struct mail_ip *stable_mail_ips; + +struct mail_ip *mail_ip_login(const struct ip_addr *ip_addr); +struct mail_ip *mail_ip_lookup(const struct ip_addr *ip_addr); +void mail_ip_refresh(struct mail_ip *ip, const struct mail_stats *diff_stats); + +void mail_ip_ref(struct mail_ip *ip); +void mail_ip_unref(struct mail_ip **ip); + +void mail_ips_free_memory(void); +void mail_ips_init(void); +void mail_ips_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-server-connection.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,108 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "strescape.h" +#include "istream.h" +#include "ostream.h" +#include "master-service.h" +#include "mail-session.h" +#include "mail-command.h" +#include "mail-server-connection.h" + +#include <unistd.h> + +#define MAX_INBUF_SIZE (PIPE_BUF*2) + +struct mail_server_connection { + int fd; + struct istream *input; + struct ostream *output; + struct io *io; +}; + +static const char *const* +mail_server_connection_next_line(struct mail_server_connection *conn) +{ + const char *line; + char **args; + unsigned int i; + + line = i_stream_next_line(conn->input); + if (line == NULL) + return NULL; + + args = p_strsplit(pool_datastack_create(), line, "\t"); + for (i = 0; args[i] != NULL; i++) + args[i] = str_tabunescape(args[i]); + return (void *)args; +} + +static int +mail_server_connection_request(const char *const *args, const char **error_r) +{ + const char *cmd = args[0]; + + if (cmd == NULL) { + *error_r = "Missing command"; + return -1; + } + args++; + + if (strcmp(cmd, "CONNECT") == 0) + return mail_session_connect_parse(args, error_r); + if (strcmp(cmd, "DISCONNECT") == 0) + return mail_session_disconnect_parse(args, error_r); + if (strcmp(cmd, "UPDATE-SESSION") == 0) + return mail_session_update_parse(args, error_r); + if (strcmp(cmd, "UPDATE-CMD") == 0) + return mail_command_update_parse(args, error_r); + + *error_r = "Unknown command"; + return -1; +} + +static void mail_server_connection_input(struct mail_server_connection *conn) +{ + const char *const *args, *error; + + switch (i_stream_read(conn->input)) { + case -2: + i_error("BUG: Mail server sent too much data"); + mail_server_connection_destroy(&conn); + return; + case -1: + mail_server_connection_destroy(&conn); + return; + } + + while ((args = mail_server_connection_next_line(conn)) != NULL) { + if (mail_server_connection_request(args, &error) < 0) + i_error("Mail server input error: %s", error); + } +} + +struct mail_server_connection *mail_server_connection_create(int fd) +{ + struct mail_server_connection *conn; + + conn = i_new(struct mail_server_connection, 1); + conn->fd = fd; + conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE); + conn->output = o_stream_create_fd(fd, (size_t)-1, FALSE); + conn->io = io_add(fd, IO_READ, mail_server_connection_input, conn); + return conn; +} + +void mail_server_connection_destroy(struct mail_server_connection **_conn) +{ + struct mail_server_connection *conn = *_conn; + + *_conn = NULL; + + io_remove(&conn->io); + i_stream_destroy(&conn->input); + o_stream_destroy(&conn->output); + if (close(conn->fd) < 0) + i_error("close(conn) failed: %m"); + i_free(conn); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-server-connection.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,7 @@ +#ifndef MAIL_SERVER_CONNECTION_H +#define MAIL_SERVER_CONNECTION_H + +struct mail_server_connection *mail_server_connection_create(int fd); +void mail_server_connection_destroy(struct mail_server_connection **conn); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-session.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,242 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "hash.h" +#include "llist.h" +#include "global-memory.h" +#include "stats-settings.h" +#include "mail-stats.h" +#include "mail-user.h" +#include "mail-ip.h" +#include "mail-session.h" + +/* If session doesn't receive any updates for this long, assume that the + process associated with it has crashed, and forcibly disconnect the + session. Must be larger than SESSION_STATS_FORCE_REFRESH_SECS in + stats plugin */ +#define MAIL_SESSION_IDLE_TIMEOUT_MSECS (1000*60*10) + +static struct hash_table *mail_sessions_hash; +/* sessions are sorted by their last_update timestamp, oldest first */ +static struct mail_session *mail_sessions_head, *mail_sessions_tail; +struct mail_session *stable_mail_sessions; + +static size_t mail_session_memsize(const struct mail_session *session) +{ + return sizeof(*session) + strlen(session->service) + 1; +} + +static void mail_session_disconnect(struct mail_session *session) +{ + hash_table_remove(mail_sessions_hash, session->guid); + session->disconnected = TRUE; + timeout_remove(&session->to_idle); + mail_session_unref(&session); +} + +static void mail_session_idle_timeout(struct mail_session *session) +{ + i_warning("Session %s appears to be have crashed, disconnecting it", + guid_128_to_string(session->guid)); + mail_session_disconnect(session); +} + +int mail_session_connect_parse(const char *const *args, const char **error_r) +{ + struct mail_session *session; + guid_128_t session_guid; + struct ip_addr ip; + unsigned int i; + + /* <session guid> <username> <service> [key=value ..] */ + if (str_array_length(args) < 3) { + *error_r = "CONNECT: Too few parameters"; + return -1; + } + if (guid_128_from_string(args[0], session_guid) < 0) { + *error_r = "CONNECT: Invalid GUID"; + return -1; + } + session = hash_table_lookup(mail_sessions_hash, session_guid); + if (session != NULL) { + *error_r = "CONNECT: Duplicate session GUID"; + return -1; + } + session = i_new(struct mail_session, 1); + session->refcount = 1; /* unrefed at disconnect */ + session->service = i_strdup(args[2]); + memcpy(session->guid, session_guid, sizeof(session->guid)); + session->last_update = ioloop_time; + session->to_idle = timeout_add(MAIL_SESSION_IDLE_TIMEOUT_MSECS, + mail_session_idle_timeout, session); + + session->user = mail_user_login(args[1]); + for (i = 3; args[i] != NULL; i++) { + if (strncmp(args[i], "rip=", 4) == 0 && + net_addr2ip(args[i] + 4, &ip)) { + session->ip = mail_ip_login(&ip); + } + } + + hash_table_insert(mail_sessions_hash, session->guid, session); + DLLIST_PREPEND_FULL(&stable_mail_sessions, session, + stable_prev, stable_next); + DLLIST2_APPEND_FULL(&mail_sessions_head, &mail_sessions_tail, session, + sorted_prev, sorted_next); + DLLIST_PREPEND_FULL(&session->user->sessions, session, + user_prev, user_next); + mail_user_ref(session->user); + if (session->ip != NULL) { + DLLIST_PREPEND_FULL(&session->ip->sessions, session, + ip_prev, ip_next); + mail_ip_ref(session->ip); + } + global_memory_alloc(mail_session_memsize(session)); + return 0; +} + +void mail_session_ref(struct mail_session *session) +{ + session->refcount++; +} + +void mail_session_unref(struct mail_session **_session) +{ + struct mail_session *session = *_session; + + i_assert(session->refcount > 0); + session->refcount--; + + *_session = NULL; +} + +static void mail_session_free(struct mail_session *session) +{ + i_assert(session->refcount == 0); + + global_memory_free(mail_session_memsize(session)); + + if (session->to_idle != NULL) + timeout_remove(&session->to_idle); + if (!session->disconnected) + hash_table_remove(mail_sessions_hash, session->guid); + DLLIST_REMOVE_FULL(&stable_mail_sessions, session, + stable_prev, stable_next); + DLLIST2_REMOVE_FULL(&mail_sessions_head, &mail_sessions_tail, session, + sorted_prev, sorted_next); + DLLIST_REMOVE_FULL(&session->user->sessions, session, + user_prev, user_next); + mail_user_unref(&session->user); + if (session->ip != NULL) { + DLLIST_REMOVE_FULL(&session->ip->sessions, session, + ip_prev, ip_next); + mail_ip_unref(&session->ip); + } + + i_free(session->service); + i_free(session); +} + +int mail_session_lookup(const char *guid, struct mail_session **session_r, + const char **error_r) +{ + guid_128_t session_guid; + + if (guid == NULL) { + *error_r = "Too few parameters"; + return -1; + } + if (guid_128_from_string(guid, session_guid) < 0) { + *error_r = "Invalid GUID"; + return -1; + } + *session_r = hash_table_lookup(mail_sessions_hash, session_guid); + if (*session_r == NULL) { + i_warning("mail disconnect couldn't find session GUID: %s", + guid_128_to_string(session_guid)); + return 0; + } + return 1; +} + +int mail_session_disconnect_parse(const char *const *args, const char **error_r) +{ + struct mail_session *session; + int ret; + + /* <session guid> */ + if ((ret = mail_session_lookup(args[0], &session, error_r)) <= 0) + return ret; + + mail_session_disconnect(session); + return 0; +} + +void mail_session_refresh(struct mail_session *session, + const struct mail_stats *diff_stats) +{ + if (diff_stats != NULL) + mail_stats_add(&session->stats, diff_stats); + session->last_update = ioloop_time; + DLLIST2_REMOVE_FULL(&mail_sessions_head, &mail_sessions_tail, session, + sorted_prev, sorted_next); + DLLIST2_APPEND_FULL(&mail_sessions_head, &mail_sessions_tail, session, + sorted_prev, sorted_next); + + mail_user_refresh(session->user, diff_stats); + if (session->ip != NULL) + mail_ip_refresh(session->ip, diff_stats); +} + +int mail_session_update_parse(const char *const *args, const char **error_r) +{ + struct mail_session *session; + struct mail_stats stats, diff_stats; + int ret; + + /* <session guid> [key=value ..] */ + if ((ret = mail_session_lookup(args[0], &session, error_r)) <= 0) + return ret; + + if (mail_stats_parse(args+1, &stats, error_r) < 0) { + *error_r = t_strconcat("UPDATE-SESSION: ", *error_r, NULL); + return -1; + } + + if (!mail_stats_diff(&session->stats, &stats, &diff_stats)) { + *error_r = "UPDATE-SESSION: stats shrank"; + return -1; + } + mail_session_refresh(session, &diff_stats); + return 0; +} + +void mail_sessions_free_memory(void) +{ + while (mail_sessions_head != NULL && + mail_sessions_head->refcount == 0) { + i_assert(mail_sessions_head->disconnected); + mail_session_free(mail_sessions_head); + + if (global_used_memory < stats_settings->memory_limit) + break; + if (ioloop_time - + mail_sessions_head->last_update < stats_settings->session_min_time) + break; + } +} + +void mail_sessions_init(void) +{ + mail_sessions_hash = + hash_table_create(default_pool, default_pool, 0, + guid_128_hash, guid_128_cmp); +} + +void mail_sessions_deinit(void) +{ + while (mail_sessions_head != NULL) + mail_session_free(mail_sessions_head); + hash_table_destroy(&mail_sessions_hash); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-session.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,26 @@ +#ifndef MAIL_SESSION_H +#define MAIL_SESSION_H + +struct mail_stats; +struct mail_session; + +extern struct mail_session *stable_mail_sessions; + +int mail_session_connect_parse(const char *const *args, const char **error_r); +int mail_session_disconnect_parse(const char *const *args, const char **error_r); +int mail_session_update_parse(const char *const *args, const char **error_r); +int mail_session_cmd_update_parse(const char *const *args, const char **error_r); + +void mail_session_ref(struct mail_session *session); +void mail_session_unref(struct mail_session **session); + +int mail_session_lookup(const char *guid, struct mail_session **session_r, + const char **error_r); +void mail_session_refresh(struct mail_session *session, + const struct mail_stats *diff_stats); + +void mail_sessions_free_memory(void); +void mail_sessions_init(void); +void mail_sessions_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-stats.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,170 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "time-util.h" +#include "mail-stats.h" + +struct mail_stats_parse_map { + const char *name; + unsigned int offset; + unsigned int size; +} parse_map[] = { +#define E(parsename, name) { parsename, offsetof(struct mail_stats, name), sizeof(((struct mail_stats *)0)->name) } + E("diskin", disk_input), + E("diskout", disk_output), + E("lpath", lookup_path), + E("lattr", lookup_attr), + E("rcount", read_count), + E("rbytes", read_bytes), + E("cache", cache_hits) +}; + +static int mail_stats_parse_cpu(const char *value, struct mail_stats *stats) +{ + const char *p, *secs_str; + unsigned long secs, usecs; + + p = strchr(value, '.'); + if (p == NULL) + return -1; + + secs_str = t_strdup_until(value, p++); + if (str_to_ulong(secs_str, &secs) < 0 || + str_to_ulong(p, &usecs) < 0 || + usecs > 1000000) + return -1; + + stats->cpu_secs.tv_sec = secs; + stats->cpu_secs.tv_usec = usecs; + return 0; +} + +static struct mail_stats_parse_map * +parse_map_find(const char *name) +{ + unsigned int i; + + for (i = 0; i < N_ELEMENTS(parse_map); i++) { + if (strcmp(parse_map[i].name, name) == 0) + return &parse_map[i]; + } + return NULL; +} + +static int +mail_stats_parse_one(const char *key, const char *value, + struct mail_stats *stats, const char **error_r) +{ + struct mail_stats_parse_map *map; + void *dest; + + map = parse_map_find(key); + if (map == NULL) + return 0; + + dest = PTR_OFFSET(stats, map->offset); + switch (map->size) { + case sizeof(uint32_t): + if (str_to_uint32(value, dest) < 0) { + *error_r = "invalid number"; + return -1; + } + break; + case sizeof(uint64_t): + if (str_to_uint64(value, dest) < 0) { + *error_r = "invalid number"; + return -1; + } + break; + default: + i_unreached(); + } + return 0; +} + +int mail_stats_parse(const char *const *args, struct mail_stats *stats_r, + const char **error_r) +{ + const char *p, *key, *value; + unsigned int i; + + memset(stats_r, 0, sizeof(*stats_r)); + for (i = 0; args[i] != NULL; i++) { + p = strchr(args[i], '='); + if (p == NULL) { + *error_r = "mail stats parameter missing '='"; + return -1; + } + key = t_strdup_until(args[i], p); + value = p + 1; + if (strcmp(key, "cpu") == 0) { + if (mail_stats_parse_cpu(value, stats_r) < 0) { + *error_r = "invalid cpu parameter"; + return -1; + } + } else { + if (mail_stats_parse_one(key, value, + stats_r, error_r) < 0) + return -1; + } + } + return 0; +} + +bool mail_stats_diff(const struct mail_stats *stats1, + const struct mail_stats *stats2, + struct mail_stats *diff_stats_r) +{ + long long diff_usecs; + + memset(diff_stats_r, 0, sizeof(*diff_stats_r)); + + diff_usecs = timeval_diff_usecs(&stats2->cpu_secs, &stats1->cpu_secs); + if (diff_usecs < 0) + return FALSE; + diff_stats_r->cpu_secs.tv_sec = diff_usecs / 1000000; + diff_stats_r->cpu_secs.tv_usec = diff_usecs % 1000000; + + if (stats1->disk_input > stats2->disk_input) + return FALSE; + diff_stats_r->disk_input = stats2->disk_input - stats1->disk_input; + if (stats1->disk_output > stats2->disk_output) + return FALSE; + diff_stats_r->disk_output = stats2->disk_output - stats1->disk_output; + + if (stats1->lookup_path > stats2->lookup_path) + return FALSE; + diff_stats_r->lookup_path = stats2->lookup_path - stats1->lookup_path; + if (stats1->lookup_attr > stats2->lookup_attr) + return FALSE; + diff_stats_r->lookup_attr = stats2->lookup_attr - stats1->lookup_attr; + if (stats1->read_count > stats2->read_count) + return FALSE; + diff_stats_r->read_count = stats2->read_count - stats1->read_count; + if (stats1->cache_hits > stats2->cache_hits) + return FALSE; + diff_stats_r->cache_hits = stats2->cache_hits - stats1->cache_hits; + if (stats1->read_bytes > stats2->read_bytes) + return FALSE; + diff_stats_r->read_bytes = stats2->read_bytes - stats1->read_bytes; + + return TRUE; +} + +void mail_stats_add(struct mail_stats *dest, const struct mail_stats *src) +{ + dest->cpu_secs.tv_sec += src->cpu_secs.tv_sec; + dest->cpu_secs.tv_usec += src->cpu_secs.tv_usec; + if (dest->cpu_secs.tv_usec > 1000000) { + dest->cpu_secs.tv_usec -= 1000000; + dest->cpu_secs.tv_sec++; + } + dest->disk_input += src->disk_input; + dest->disk_output += src->disk_output; + + dest->lookup_path += src->lookup_path; + dest->lookup_attr += src->lookup_attr; + dest->read_count += src->read_count; + dest->cache_hits += src->cache_hits; + dest->read_bytes += src->read_bytes; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-stats.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,110 @@ +#ifndef MAIL_STATS_H +#define MAIL_STATS_H + +#include "network.h" +#include "guid.h" + +struct mail_stats { + struct timeval cpu_secs; + uint64_t disk_input, disk_output; + + uint32_t lookup_path, lookup_attr, read_count, cache_hits; + uint64_t read_bytes; +}; + +struct mail_command { + struct mail_command *stable_prev, *stable_next; + struct mail_command *session_prev, *session_next; + + struct mail_session *session; + char *name, *args; + /* non-zero id means the command is still running */ + unsigned int id; + + time_t last_update; + struct mail_stats stats; + + int refcount; +}; + +struct mail_session { + struct mail_session *stable_prev, *stable_next; + struct mail_session *sorted_prev, *sorted_next; + struct mail_session *user_prev, *user_next; + struct mail_session *ip_prev, *ip_next; + + /* if guid is empty, the session no longer exists */ + guid_128_t guid; + struct mail_user *user; + char *service; + /* ip address may be NULL if there's none */ + struct mail_ip *ip; + struct timeout *to_idle; + + struct mail_stats stats; + time_t last_update; + unsigned int num_cmds; + + bool disconnected; + unsigned int highest_cmd_id; + int refcount; + struct mail_command *commands; +}; + +struct mail_user { + struct mail_user *stable_prev, *stable_next; + struct mail_user *sorted_prev, *sorted_next; + struct mail_user *domain_prev, *domain_next; + char *name; + struct mail_domain *domain; + time_t reset_timestamp; + + time_t last_update; + struct mail_stats stats; + unsigned int num_logins; + unsigned int num_cmds; + + int refcount; + struct mail_session *sessions; +}; + +struct mail_domain { + struct mail_domain *stable_prev, *stable_next; + struct mail_domain *sorted_prev, *sorted_next; + char *name; + time_t reset_timestamp; + + time_t last_update; + struct mail_stats stats; + unsigned int num_logins; + unsigned int num_cmds; + + int refcount; + struct mail_user *users; +}; + +struct mail_ip { + struct mail_ip *stable_prev, *stable_next; + struct mail_ip *sorted_prev, *sorted_next; + struct ip_addr ip; + time_t reset_timestamp; + + time_t last_update; + struct mail_stats stats; + unsigned int num_logins; + unsigned int num_cmds; + + int refcount; + struct mail_session *sessions; +}; + +int mail_stats_parse(const char *const *args, struct mail_stats *stats_r, + const char **error_r); +/* diff1 is supposed to have smaller values than diff2. Returns TRUE if this + is so, FALSE if not */ +bool mail_stats_diff(const struct mail_stats *stats1, + const struct mail_stats *stats2, + struct mail_stats *diff_stats_r); +void mail_stats_add(struct mail_stats *dest, const struct mail_stats *src); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-user.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,138 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "hash.h" +#include "llist.h" +#include "global-memory.h" +#include "stats-settings.h" +#include "mail-stats.h" +#include "mail-domain.h" +#include "mail-user.h" + +static struct hash_table *mail_users_hash; +/* users are sorted by their last_update timestamp, oldest first */ +static struct mail_user *mail_users_head, *mail_users_tail; +struct mail_user *stable_mail_users; + +static size_t mail_user_memsize(const struct mail_user *user) +{ + return sizeof(*user) + strlen(user->name) + 1; +} + +struct mail_user *mail_user_login(const char *username) +{ + struct mail_user *user; + const char *domain; + + user = hash_table_lookup(mail_users_hash, username); + if (user != NULL) { + user->num_logins++; + user->domain->num_logins++; + mail_user_refresh(user, NULL); + return user; + } + + domain = strchr(username, '@'); + if (domain != NULL) + domain++; + else + domain = ""; + + user = i_new(struct mail_user, 1); + user->name = i_strdup(username); + user->reset_timestamp = ioloop_time; + user->domain = mail_domain_login(domain); + + hash_table_insert(mail_users_hash, user->name, user); + DLLIST_PREPEND_FULL(&stable_mail_users, user, + stable_prev, stable_next); + DLLIST2_APPEND_FULL(&mail_users_head, &mail_users_tail, user, + sorted_prev, sorted_next); + DLLIST_PREPEND_FULL(&user->domain->users, user, + domain_prev, domain_next); + mail_domain_ref(user->domain); + + user->num_logins++; + user->last_update = ioloop_time; + global_memory_alloc(mail_user_memsize(user)); + return user; +} + +struct mail_user *mail_user_lookup(const char *username) +{ + return hash_table_lookup(mail_users_hash, username); +} + +void mail_user_ref(struct mail_user *user) +{ + user->refcount++; +} + +void mail_user_unref(struct mail_user **_user) +{ + struct mail_user *user = *_user; + + i_assert(user->refcount > 0); + user->refcount--; + + *_user = NULL; +} + +static void mail_user_free(struct mail_user *user) +{ + i_assert(user->refcount == 0); + i_assert(user->sessions == NULL); + + global_memory_free(mail_user_memsize(user)); + DLLIST_REMOVE_FULL(&stable_mail_users, user, + stable_prev, stable_next); + DLLIST2_REMOVE_FULL(&mail_users_head, &mail_users_tail, user, + sorted_prev, sorted_next); + DLLIST_REMOVE_FULL(&user->domain->users, user, + domain_prev, domain_next); + mail_domain_unref(&user->domain); + + i_free(user->name); + i_free(user); +} + +void mail_user_refresh(struct mail_user *user, + const struct mail_stats *diff_stats) +{ + if (diff_stats != NULL) + mail_stats_add(&user->stats, diff_stats); + user->last_update = ioloop_time; + DLLIST2_REMOVE_FULL(&mail_users_head, &mail_users_tail, user, + sorted_prev, sorted_next); + DLLIST2_APPEND_FULL(&mail_users_head, &mail_users_tail, user, + sorted_prev, sorted_next); + mail_domain_refresh(user->domain, diff_stats); +} + +void mail_users_free_memory(void) +{ + while (mail_users_head != NULL && mail_users_head->refcount == 0) { + mail_user_free(mail_users_head); + + if (global_used_memory < stats_settings->memory_limit) + break; + if (ioloop_time - + mail_users_head->last_update < stats_settings->user_min_time) + break; + } +} + +void mail_users_init(void) +{ + mail_users_hash = + hash_table_create(default_pool, default_pool, 0, + str_hash, (hash_cmp_callback_t *)strcmp); +} + +void mail_users_deinit(void) +{ + while (mail_users_head != NULL) + mail_user_free(mail_users_head); + hash_table_destroy(&mail_users_hash); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/mail-user.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,21 @@ +#ifndef MAIL_USER_H +#define MAIL_USER_H + +struct mail_stats; + +extern struct mail_user *stable_mail_users; + +struct mail_user *mail_user_login(const char *username); +struct mail_user *mail_user_lookup(const char *username); + +void mail_user_refresh(struct mail_user *user, + const struct mail_stats *diff_stats); + +void mail_user_ref(struct mail_user *user); +void mail_user_unref(struct mail_user **user); + +void mail_users_free_memory(void); +void mail_users_init(void); +void mail_users_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/main.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,80 @@ +/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "restrict-access.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "global-memory.h" +#include "stats-settings.h" +#include "mail-server-connection.h" +#include "mail-command.h" +#include "mail-session.h" +#include "mail-user.h" +#include "mail-domain.h" +#include "client.h" + +struct stats_settings set; +static struct mail_server_connection *mail_server_conn = NULL; + +static void client_connected(struct master_service_connection *conn) +{ + if (conn->fifo) { + if (mail_server_conn != NULL) { + i_error("Received another mail-server connection"); + return; + } + mail_server_conn = mail_server_connection_create(conn->fd); + } else { + client_create(conn->fd); + } + master_service_client_connection_accept(conn); +} + +int main(int argc, char *argv[]) +{ + const struct setting_parser_info *set_roots[] = { + &stats_setting_parser_info, + NULL + }; + const enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_NO_IDLE_DIE | + MASTER_SERVICE_FLAG_UPDATE_PROCTITLE; + const char *error; + void **sets; + + master_service = master_service_init("stats", service_flags, + &argc, &argv, NULL); + if (master_getopt(master_service) > 0) + return FATAL_DEFAULT; + if (master_service_settings_read_simple(master_service, set_roots, + &error) < 0) + i_fatal("Error reading configuration: %s", error); + master_service_init_log(master_service, "stats: "); + + restrict_access_by_env(NULL, FALSE); + restrict_access_allow_coredumps(TRUE); + master_service_init_finish(master_service); + + sets = master_service_settings_get_others(master_service); + stats_settings = sets[0]; + + mail_commands_init(); + mail_sessions_init(); + mail_users_init(); + mail_domains_init(); + + master_service_run(master_service, client_connected); + + clients_destroy_all(); + mail_commands_deinit(); + mail_sessions_deinit(); + mail_users_deinit(); + mail_domains_deinit(); + + if (mail_server_conn != NULL) + mail_server_connection_destroy(&mail_server_conn); + + i_assert(global_used_memory == 0); + master_service_deinit(&master_service); + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/stats-settings.c Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,94 @@ +/* Copyright (c) 2009-2011 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "settings-parser.h" +#include "service-settings.h" +#include "stats-settings.h" + +/* <settings checks> */ +static struct file_listener_settings stats_unix_listeners_array[] = { + { "stats", 0600, "", "" } +}; +static struct file_listener_settings *stats_unix_listeners[] = { + &stats_unix_listeners_array[0] +}; +static buffer_t stats_unix_listeners_buf = { + stats_unix_listeners, sizeof(stats_unix_listeners), { 0, } +}; +static struct file_listener_settings stats_fifo_listeners_array[] = { + { "stats-mail", 0600, "", "" } +}; +static struct file_listener_settings *stats_fifo_listeners[] = { + &stats_fifo_listeners_array[0] +}; +static buffer_t stats_fifo_listeners_buf = { + stats_fifo_listeners, + sizeof(stats_fifo_listeners), { 0, } +}; +/* </settings checks> */ + +struct service_settings stats_service_settings = { + .name = "stats", + .protocol = "", + .type = "", + .executable = "stats", + .user = "$default_internal_user", + .group = "", + .privileged_group = "", + .extra_groups = "", + .chroot = "", + + .drop_priv_before_exec = FALSE, + + .process_min_avail = 0, + .process_limit = 1, + .client_limit = 0, + .service_count = 0, + .idle_kill = -1U, + .vsz_limit = (uoff_t)-1, + + .unix_listeners = { { &stats_unix_listeners_buf, + sizeof(stats_unix_listeners[0]) } }, + .fifo_listeners = { { &stats_fifo_listeners_buf, + sizeof(stats_fifo_listeners[0]) } }, + .inet_listeners = ARRAY_INIT +}; + +#undef DEF +#define DEF(type, name) \ + { type, #name, offsetof(struct stats_settings, name), NULL } + +static const struct setting_define stats_setting_defines[] = { + DEF(SET_SIZE, memory_limit), + DEF(SET_TIME, command_min_time), + DEF(SET_TIME, session_min_time), + DEF(SET_TIME, user_min_time), + DEF(SET_TIME, domain_min_time), + DEF(SET_TIME, ip_min_time), + + SETTING_DEFINE_LIST_END +}; + +const struct stats_settings stats_default_settings = { + .memory_limit = 1024*1024*16, + + .command_min_time = 60, + .session_min_time = 60*15, + .user_min_time = 60*60, + .domain_min_time = 60*60*12, + .ip_min_time = 60*60*12 +}; + +const struct setting_parser_info stats_setting_parser_info = { + .module_name = "stats", + .defines = stats_setting_defines, + .defaults = &stats_default_settings, + + .type_offset = (size_t)-1, + .struct_size = sizeof(struct stats_settings), + + .parent_offset = (size_t)-1 +}; + +const struct stats_settings *stats_settings;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stats/stats-settings.h Fri Aug 26 05:15:12 2011 +0300 @@ -0,0 +1,18 @@ +#ifndef STATS_SETTINGS_H +#define STATS_SETTINGS_H + +struct stats_settings { + uoff_t memory_limit; + + unsigned int command_min_time; + unsigned int session_min_time; + unsigned int user_min_time; + unsigned int domain_min_time; + unsigned int ip_min_time; +}; + +extern const struct setting_parser_info stats_setting_parser_info; +extern const struct stats_settings *stats_settings; + +#endif +