1 /* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "time-util.h"
6 #include "stats-settings.h"
7 #include "mail-stats.h"
8 #include "istream.h"
9 #include "ostream.h"
10 #include "net.h"
11 #include "str.h"
12 #include "write-full.h"
13 #include "stats-carbon.h"
14 
15 #define CARBON_SERVER_DEFAULT_PORT 2003
16 
17 struct stats_send_ctx {
18 	pool_t pool;
19 	int fd;
20 	unsigned long to_msecs;
21 	const char *endpoint;
22 	const char *str;
23 	struct io *io;
24 	struct timeout *to;
25 
26 	void (*callback)(void *);
27 	void *ctx;
28 };
29 
30 void
stats_carbon_destroy(struct stats_send_ctx ** _ctx)31 stats_carbon_destroy(struct stats_send_ctx **_ctx)
32 {
33 	struct stats_send_ctx *ctx = *_ctx;
34 	*_ctx = NULL;
35 
36 	io_remove(&ctx->io);
37 	timeout_remove(&ctx->to);
38 	i_close_fd(&ctx->fd);
39 	pool_unref(&ctx->pool);
40 }
41 
42 static void
stats_carbon_callback(struct stats_send_ctx * ctx)43 stats_carbon_callback(struct stats_send_ctx *ctx)
44 {
45 	i_assert(ctx->callback != NULL);
46 	void (*callback)(void *) = ctx->callback;
47 	ctx->callback = NULL;
48 	callback(ctx->ctx);
49 }
50 
51 static void
stats_carbon_timeout(struct stats_send_ctx * ctx)52 stats_carbon_timeout(struct stats_send_ctx *ctx)
53 {
54 	i_error("Stats submit(%s) failed: endpoint timeout after %lu msecs",
55 		ctx->endpoint, ctx->to_msecs);
56 	stats_carbon_callback(ctx);
57 }
58 
59 static void
stats_carbon_connected(struct stats_send_ctx * ctx)60 stats_carbon_connected(struct stats_send_ctx *ctx)
61 {
62 	io_remove(&ctx->io);
63 	if ((errno = net_geterror(ctx->fd)) != 0) {
64 		i_error("connect(%s) failed: %m",
65 			ctx->endpoint);
66 		stats_carbon_callback(ctx);
67 		return;
68 	}
69 	if (write_full(ctx->fd, ctx->str, strlen(ctx->str)) < 0)
70 		i_error("write(%s) failed: %m",
71 			ctx->endpoint);
72 	stats_carbon_callback(ctx);
73 }
74 
75 int
stats_carbon_send(const char * endpoint,const char * data,void (* callback)(void *),void * cb_ctx,struct stats_send_ctx ** ctx_r)76 stats_carbon_send(const char *endpoint, const char *data,
77 		  void (*callback)(void *), void *cb_ctx,
78 		  struct stats_send_ctx **ctx_r)
79 {
80 	const char *host;
81 	in_port_t port;
82 	struct ip_addr ip;
83 
84 	if (net_str2hostport(endpoint, CARBON_SERVER_DEFAULT_PORT,
85 			     &host, &port) < 0 ||
86 	    net_addr2ip(host, &ip) < 0) {
87 		i_error("stats_submit: Cannot parse endpoint '%s'",
88 			endpoint);
89 		return -1;
90 	}
91 
92 	pool_t pool = pool_alloconly_create("stats carbon send", 1024);
93 	struct stats_send_ctx *ctx = p_new(pool,
94 					   struct stats_send_ctx, 1);
95 	ctx->pool = pool;
96 	ctx->str = p_strdup(ctx->pool, data);
97 
98 	ctx->fd = net_connect_ip(&ip, port, NULL);
99 	if (ctx->fd < 0) {
100 		i_error("connect(%s) failed: %m", endpoint);
101 		stats_carbon_callback(ctx);
102 		return -1;
103 	}
104 	ctx->io = io_add(ctx->fd, IO_WRITE,
105 			 stats_carbon_connected,
106 			 ctx);
107 
108 	/* give time for almost until next update
109 	   this is to ensure we leave a little pause between
110            attempts. Multiplier 800 gives us 20% window, and
111            ensures the number stays positive. */
112 	ctx->to_msecs = stats_settings->carbon_interval*800;
113 	ctx->to = timeout_add(ctx->to_msecs,
114 			      stats_carbon_timeout,
115 			      ctx);
116 	if (net_ipport2str(&ip, port, &host) < 0)
117 		i_unreached();
118 	ctx->endpoint = p_strdup(ctx->pool, host);
119 	ctx->callback = callback;
120 	ctx->ctx = cb_ctx;
121 
122 	*ctx_r = ctx;
123 
124 	return 0;
125 }
126