]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
stats: Add support for carbon-server
authorAki Tuomi <aki.tuomi@dovecot.fi>
Sun, 6 Nov 2016 00:50:52 +0000 (02:50 +0200)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Tue, 15 Nov 2016 20:24:50 +0000 (22:24 +0200)
Allows admin to specify stats_carbon_server=ip:port.
Stats are delivered in "key value" format
to specified port. Can be received with e.g.
metronome.

src/stats/Makefile.am
src/stats/mail-stats.c
src/stats/mail-stats.h
src/stats/stats-carbon.c [new file with mode: 0644]
src/stats/stats-carbon.h [new file with mode: 0644]
src/stats/stats-settings.c
src/stats/stats-settings.h

index ec5ae7482dcd5dedf83e17d5357628d17832c906..7d9e68ecfdf9dd6c2c7e45d798faa01879d4f2f8 100644 (file)
@@ -26,6 +26,7 @@ stats_SOURCES = \
        mail-stats.c \
        mail-user.c \
        main.c \
+       stats-carbon.c \
        stats-settings.c
 
 noinst_HEADERS = \
@@ -40,4 +41,5 @@ noinst_HEADERS = \
        mail-session.h \
        mail-stats.h \
        mail-user.h \
+       stats-carbon.h \
        stats-settings.h
index e2970829503a5c25d6e2d1734d01dc659e232b72..0d5d6a4cd458d4ef806c9f5ff1ee98ba57264439 100644 (file)
@@ -2,19 +2,67 @@
 
 #include "lib.h"
 #include "ioloop.h"
-#include "time-util.h"
 #include "mail-stats.h"
+#include "stats-carbon.h"
+#include "stats-settings.h"
+#include "str.h"
 
 struct mail_global mail_global_stats;
 
+static void
+mail_global_stats_sent(void *ctx)
+{
+       struct mail_global *stats = ctx;
+       stats_carbon_destroy(&stats->stats_send_ctx);
+}
+
+static void
+mail_global_stats_send(void *u0 ATTR_UNUSED)
+{
+       unsigned long ts = (unsigned long)ioloop_time;
+       if (*stats_settings->carbon_name != '\0' &&
+           *stats_settings->carbon_server != '\0') {
+               string_t *str = t_str_new(256);
+               const char *prefix = t_strdup_printf("dovecot.%s.global",
+                                                    stats_settings->carbon_name);
+               str_printfa(str, "%s.logins %u %lu\r\n", prefix,
+                           mail_global_stats.num_logins, ts);
+               str_printfa(str, "%s.cmds %u %lu\r\n", prefix,
+                           mail_global_stats.num_cmds, ts);
+               str_printfa(str, "%s.connected_sessions %u %lu\r\n", prefix,
+                           mail_global_stats.num_connected_sessions,
+                           ts);
+               str_printfa(str, "%s.last_reset %lu %lu\r\n", prefix,
+                           mail_global_stats.reset_timestamp, ts);
+               /* then export rest of the stats */
+               for(size_t i = 0; i < stats_field_count(); i++) {
+                       str_printfa(str, "%s.%s ", prefix,
+                                   stats_field_name(i));
+                       stats_field_value(str, mail_global_stats.stats, i);
+                       str_printfa(str, " %lu\r\n", ts);
+               }
+
+               /* and send them along */
+               (void)stats_carbon_send(stats_settings->carbon_server, str_c(str),
+                                       mail_global_stats_sent, &mail_global_stats,
+                                       &mail_global_stats.stats_send_ctx);
+       }
+}
+
 void mail_global_init(void)
 {
        mail_global_stats.reset_timestamp = ioloop_time;
        mail_global_stats.stats = stats_alloc(default_pool);
+       mail_global_stats.to_stats_send = timeout_add(stats_settings->carbon_interval*1000,
+                                                     mail_global_stats_send,
+                                                     NULL);
 }
 
 void mail_global_deinit(void)
 {
+       if (mail_global_stats.stats_send_ctx != NULL)
+               stats_carbon_destroy(&mail_global_stats.stats_send_ctx);
+       timeout_remove(&mail_global_stats.to_stats_send);
        i_free(mail_global_stats.stats);
 }
 
index 045dd64f33d47b7fb91e8826a0ee6e27fee29682..efd08244e752e43115dbca0182f4e96becd9f47e 100644 (file)
@@ -7,6 +7,8 @@
 #include "guid.h"
 #include "stats.h"
 
+struct stats_send_ctx;
+
 struct mail_command {
        struct mail_command *stable_prev, *stable_next;
        struct mail_command *session_prev, *session_next;
@@ -104,6 +106,9 @@ struct mail_global {
        unsigned int num_logins;
        unsigned int num_cmds;
        unsigned int num_connected_sessions;
+
+       struct timeout *to_stats_send;
+       struct stats_send_ctx *stats_send_ctx;
 };
 
 extern struct mail_global mail_global_stats;
diff --git a/src/stats/stats-carbon.c b/src/stats/stats-carbon.c
new file mode 100644 (file)
index 0000000..9dadfbc
--- /dev/null
@@ -0,0 +1,128 @@
+/* Copyright (c) 2011-2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "time-util.h"
+#include "stats-settings.h"
+#include "mail-stats.h"
+#include "istream.h"
+#include "ostream.h"
+#include "net.h"
+#include "str.h"
+#include "write-full.h"
+#include "stats-carbon.h"
+
+#define CARBON_SERVER_DEFAULT_PORT 2003
+
+struct stats_send_ctx {
+       pool_t pool;
+       int fd;
+       unsigned long to_msecs;
+       const char *endpoint;
+       const char *str;
+       struct io *io;
+       struct timeout *to;
+
+       void (*callback)(void *);
+       void *ctx;
+};
+
+void
+stats_carbon_destroy(struct stats_send_ctx **_ctx)
+{
+       struct stats_send_ctx *ctx = *_ctx;
+       *_ctx = NULL;
+
+       if (ctx->io != NULL)
+               io_remove(&ctx->io);
+       if (ctx->to != NULL)
+               timeout_remove(&ctx->to);
+       if (ctx->fd != -1)
+               i_close_fd(&ctx->fd);
+       pool_unref(&ctx->pool);
+}
+
+static void
+stats_carbon_callback(struct stats_send_ctx *ctx)
+{
+       i_assert(ctx->callback != NULL);
+       void (*callback)(void *) = ctx->callback;
+       ctx->callback = NULL;
+       callback(ctx->ctx);
+}
+
+static void
+stats_carbon_timeout(struct stats_send_ctx *ctx)
+{
+       i_error("Stats submit(%s) failed: endpoint timeout after %lu msecs",
+               ctx->endpoint, ctx->to_msecs);
+       stats_carbon_callback(ctx);
+}
+
+static void
+stats_carbon_connected(struct stats_send_ctx *ctx)
+{
+       io_remove(&ctx->io);
+       if ((errno = net_geterror(ctx->fd)) != 0) {
+               i_error("connect(%s) failed: %m",
+                       ctx->endpoint);
+               stats_carbon_callback(ctx);
+               return;
+       }
+       if (write_full(ctx->fd, ctx->str, strlen(ctx->str)) < 0)
+               i_error("write(%s) failed: %m",
+                       ctx->endpoint);
+       stats_carbon_callback(ctx);
+}
+
+int
+stats_carbon_send(const char *endpoint, const char *data,
+                 void (*callback)(void *), void *cb_ctx,
+                 struct stats_send_ctx **ctx_r)
+{
+       const char *host;
+       in_port_t port;
+       struct ip_addr ip;
+
+       if (net_str2hostport(endpoint, CARBON_SERVER_DEFAULT_PORT,
+                            &host, &port) < 0 ||
+           net_addr2ip(host, &ip) < 0) {
+               i_error("stats_submit: Cannot parse endpoint '%s'",
+                       endpoint);
+               return -1;
+       }
+
+       pool_t pool = pool_alloconly_create("stats carbon send", 1024);
+       struct stats_send_ctx *ctx = p_new(pool,
+                                          struct stats_send_ctx, 1);
+       ctx->pool = pool;
+       ctx->str = p_strdup(ctx->pool, data);
+
+       ctx->fd = net_connect_ip(&ip, port, NULL);
+       if (ctx->fd < 0) {
+               i_error("connect(%s) failed: %m", endpoint);
+               stats_carbon_callback(ctx);
+               return -1;
+       }
+       ctx->io = io_add(ctx->fd, IO_WRITE,
+                        stats_carbon_connected,
+                        ctx);
+
+       /* give time for almost until next update
+          this is to ensure we leave a little pause between
+           attempts. Multiplier 800 gives us 20% window, and
+           ensures the number stays positive. */
+       ctx->to_msecs = stats_settings->carbon_interval*800;
+       ctx->to = timeout_add(ctx->to_msecs,
+                             stats_carbon_timeout,
+                             ctx);
+       if (net_ipport2str(&ip, port, &host) < 0)
+               i_unreached();
+       ctx->endpoint = p_strdup(ctx->pool, host);
+       ctx->callback = callback;
+       ctx->ctx = cb_ctx;
+
+       *ctx_r = ctx;
+
+       return 0;
+}
diff --git a/src/stats/stats-carbon.h b/src/stats/stats-carbon.h
new file mode 100644 (file)
index 0000000..0785976
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef STATS_CARBON
+#define STATS_CARBON 1
+
+struct stats_send_ctx;
+
+int
+stats_carbon_send(const char *endpoint, const char *data,
+                 void (*callback)(void *), void *cb_ctx,
+                 struct stats_send_ctx **ctx_r);
+void
+stats_carbon_destroy(struct stats_send_ctx **ctx);
+
+#endif
index b274300a5139ce0e0bf9696f3aff983d96e5cab8..347a4827d54f80c90d2f8e8b5fd1865cace90a78 100644 (file)
@@ -71,7 +71,9 @@ static const struct setting_define stats_setting_defines[] = {
        DEF(SET_TIME, user_min_time),
        DEF(SET_TIME, domain_min_time),
        DEF(SET_TIME, ip_min_time),
-
+       DEF(SET_STR, carbon_server),
+       DEF(SET_TIME, carbon_interval),
+       DEF(SET_STR, carbon_name),
        SETTING_DEFINE_LIST_END
 };
 
@@ -82,7 +84,11 @@ const struct stats_settings stats_default_settings = {
        .session_min_time = 60*15,
        .user_min_time = 60*60,
        .domain_min_time = 60*60*12,
-       .ip_min_time = 60*60*12
+       .ip_min_time = 60*60*12,
+
+       .carbon_interval = 30,
+       .carbon_server = "",
+       .carbon_name = ""
 };
 
 const struct setting_parser_info stats_setting_parser_info = {
index b8b3e6ee183ddfca77be655e4ba90f96b3f7a6a5..f762b63a027f2a1f7a601735e91f002f4170876f 100644 (file)
@@ -9,6 +9,10 @@ struct stats_settings {
        unsigned int user_min_time;
        unsigned int domain_min_time;
        unsigned int ip_min_time;
+
+       unsigned int carbon_interval;
+       const char *carbon_server;
+       const char *carbon_name;
 };
 
 extern const struct setting_parser_info stats_setting_parser_info;