json-tree.c \
lib.c \
lib-signals.c \
+ log-throttle.c \
md4.c \
md5.c \
mempool.c \
lib.h \
lib-signals.h \
llist.h \
+ log-throttle.h \
macros.h \
md4.h \
md5.h \
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
+/* 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
+#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
test_json_parser,
test_json_tree,
test_llist,
+ test_log_throttle,
test_mempool_alloconly,
test_net,
test_numpack,
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
+/* 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();
+}