changeset 20582:3e02c55136a6

lib: Added log throttling API.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Tue, 02 Aug 2016 23:14:23 +0300
parents e8a810c9c96c
children c4cc24c77ad8
files src/lib/Makefile.am src/lib/log-throttle.c src/lib/log-throttle.h src/lib/test-lib.c src/lib/test-lib.h src/lib/test-log-throttle.c
diffstat 6 files changed, 172 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib/Makefile.am	Thu Aug 04 17:39:02 2016 +0300
+++ b/src/lib/Makefile.am	Tue Aug 02 23:14:23 2016 +0300
@@ -91,6 +91,7 @@
 	json-tree.c \
 	lib.c \
 	lib-signals.c \
+	log-throttle.c \
 	md4.c \
 	md5.c \
 	mempool.c \
@@ -229,6 +230,7 @@
 	lib.h \
 	lib-signals.h \
 	llist.h \
+	log-throttle.h \
 	macros.h \
 	md4.h \
 	md5.h \
@@ -329,6 +331,7 @@
 	test-json-parser.c \
 	test-json-tree.c \
 	test-llist.c \
+	test-log-throttle.c \
 	test-mempool-alloconly.c \
 	test-pkcs5.c \
 	test-net.c \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/log-throttle.c	Tue Aug 02 23:14:23 2016 +0300
@@ -0,0 +1,78 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "time-util.h"
+#include "log-throttle.h"
+
+struct log_throttle {
+	struct log_throttle_settings set;
+	log_throttle_callback_t *callback;
+	void *context;
+
+	struct timeval last_time;
+	unsigned int last_count;
+
+	struct timeout *to_throttled;
+};
+
+#undef log_throttle_init
+struct log_throttle *
+log_throttle_init(const struct log_throttle_settings *set,
+		  log_throttle_callback_t *callback, void *context)
+{
+	struct log_throttle *throttle;
+
+	i_assert(set->throttle_at_max_per_interval > 0);
+	i_assert(set->unthrottle_at_max_per_interval > 0);
+
+	throttle = i_new(struct log_throttle, 1);
+	throttle->set = *set;
+	if (throttle->set.interval_msecs == 0)
+		throttle->set.interval_msecs = 1000;
+	throttle->callback = callback;
+	throttle->context = context;
+	return throttle;
+}
+
+void log_throttle_deinit(struct log_throttle **_throttle)
+{
+	struct log_throttle *throttle = *_throttle;
+
+	*_throttle = NULL;
+	if (throttle->to_throttled != NULL)
+		timeout_remove(&throttle->to_throttled);
+	i_free(throttle);
+}
+
+static void log_throttle_callback(struct log_throttle *throttle)
+{
+	if (throttle->last_count > 0)
+		throttle->callback(throttle->last_count, throttle->context);
+	if (throttle->last_count < throttle->set.unthrottle_at_max_per_interval)
+		timeout_remove(&throttle->to_throttled);
+	throttle->last_count = 0;
+}
+
+bool log_throttle_accept(struct log_throttle *throttle)
+{
+	if (throttle->to_throttled != NULL) {
+		/* unthrottling and last_count resets are done only by
+		   the callback */
+		throttle->last_count++;
+		return FALSE;
+	} else if (timeval_diff_msecs(&ioloop_timeval, &throttle->last_time) >=
+				(int)throttle->set.interval_msecs) {
+		throttle->last_time = ioloop_timeval;
+		throttle->last_count = 1;
+		return TRUE;
+	} else if (++throttle->last_count <= throttle->set.throttle_at_max_per_interval) {
+		return TRUE;
+	} else {
+		throttle->last_count = 1;
+		throttle->to_throttled =
+			timeout_add(throttle->set.interval_msecs,
+				    log_throttle_callback, throttle);
+		return FALSE;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/log-throttle.h	Tue Aug 02 23:14:23 2016 +0300
@@ -0,0 +1,32 @@
+#ifndef LOG_THROTTLE_H
+#define LOG_THROTTLE_H
+
+struct log_throttle_settings {
+	/* Start throttling after we reach this many log events/interval. */
+	unsigned int throttle_at_max_per_interval;
+	/* Throttling continues until there's only this many or below
+	   log events/interval. */
+	unsigned int unthrottle_at_max_per_interval;
+	/* Interval unit in milliseconds. The throttled-callback is also called
+	   at this interval. Default (0) is 1000 milliseconds. */
+	unsigned int interval_msecs;
+};
+
+typedef void
+log_throttle_callback_t(unsigned int new_events_count, void *context);
+
+struct log_throttle *
+log_throttle_init(const struct log_throttle_settings *set,
+		  log_throttle_callback_t *callback, void *context);
+#define log_throttle_init(set, callback, context) \
+	log_throttle_init(set + \
+		CALLBACK_TYPECHECK(callback, void (*)(unsigned int, typeof(context))), \
+		(log_throttle_callback_t *)callback, context)
+void log_throttle_deinit(struct log_throttle **throttle);
+
+/* Increase event count. Returns TRUE if the event should be logged,
+   FALSE if it's throttled. ioloop_timeval is used to determine the current
+   time. */
+bool log_throttle_accept(struct log_throttle *throttle);
+
+#endif
--- a/src/lib/test-lib.c	Thu Aug 04 17:39:02 2016 +0300
+++ b/src/lib/test-lib.c	Tue Aug 02 23:14:23 2016 +0300
@@ -36,6 +36,7 @@
 		test_json_parser,
 		test_json_tree,
 		test_llist,
+		test_log_throttle,
 		test_mempool_alloconly,
 		test_net,
 		test_numpack,
--- a/src/lib/test-lib.h	Thu Aug 04 17:39:02 2016 +0300
+++ b/src/lib/test-lib.h	Tue Aug 02 23:14:23 2016 +0300
@@ -37,6 +37,7 @@
 void test_json_parser(void);
 void test_json_tree(void);
 void test_llist(void);
+void test_log_throttle(void);
 void test_mempool_alloconly(void);
 enum fatal_test_state fatal_mempool(int);
 void test_pkcs5_pbkdf2(void);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/test-log-throttle.c	Tue Aug 02 23:14:23 2016 +0300
@@ -0,0 +1,57 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "ioloop.h"
+#include "log-throttle.h"
+
+static unsigned int test_log_throttle_new_events_count;
+
+static void test_log_throttle_callback(unsigned int new_events_count,
+				       struct ioloop *ioloop)
+{
+	test_log_throttle_new_events_count = new_events_count;
+	io_loop_stop(ioloop);
+}
+
+void test_log_throttle(void)
+{
+	const struct log_throttle_settings set = {
+		.throttle_at_max_per_interval = 10,
+		.unthrottle_at_max_per_interval = 5,
+		.interval_msecs = 10,
+	};
+	struct log_throttle *throttle;
+	struct ioloop *ioloop;
+	unsigned int i;
+
+	test_begin("log throttle");
+
+	ioloop = io_loop_create();
+	throttle = log_throttle_init(&set, test_log_throttle_callback, ioloop);
+
+	/* throttle once and drop out just below */
+	for (i = 0; i < 10; i++)
+		test_assert_idx(log_throttle_accept(throttle), i);
+	for (i = 0; i < 4; i++)
+		test_assert_idx(!log_throttle_accept(throttle), i);
+
+	io_loop_run(ioloop);
+	test_assert(test_log_throttle_new_events_count == 4);
+
+	/* throttle and continue just above */
+	for (i = 0; i < 10; i++)
+		test_assert_idx(log_throttle_accept(throttle), i);
+	for (i = 0; i < 5; i++)
+		test_assert_idx(!log_throttle_accept(throttle), i);
+
+	io_loop_run(ioloop);
+	test_assert(test_log_throttle_new_events_count == 5);
+
+	/* we should be still throttled */
+	test_assert(!log_throttle_accept(throttle));
+
+	log_throttle_deinit(&throttle);
+	io_loop_destroy(&ioloop);
+
+	test_end();
+}