[PATCH 1/2] Introduce basic rate limiter
Vladimir Davydov
vdavydov.dev at gmail.com
Tue Dec 11 19:31:17 MSK 2018
We will use it to limit the rate of log messages.
Needed for #2218
---
src/ratelimit.h | 104 +++++++++++++++++++++++++++++++++++++++++++++
test/unit/CMakeLists.txt | 2 +
test/unit/ratelimit.c | 78 ++++++++++++++++++++++++++++++++++
test/unit/ratelimit.result | 13 ++++++
4 files changed, 197 insertions(+)
create mode 100644 src/ratelimit.h
create mode 100644 test/unit/ratelimit.c
create mode 100644 test/unit/ratelimit.result
diff --git a/src/ratelimit.h b/src/ratelimit.h
new file mode 100644
index 00000000..2bf0bac7
--- /dev/null
+++ b/src/ratelimit.h
@@ -0,0 +1,104 @@
+#ifndef TARANTOOL_RATELIMIT_H_INCLUDED
+#define TARANTOOL_RATELIMIT_H_INCLUDED
+/*
+ * Copyright 2010-2018, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <stdbool.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ * Rate limit state.
+ */
+struct ratelimit {
+ /** Time interval used for rate limiting, in seconds. */
+ double interval;
+ /** Max number of events per interval. */
+ int burst;
+ /** Number of events emitted in the current interval. */
+ int emitted;
+ /** Number of events suppressed in the current interval. */
+ int suppressed;
+ /** Start time of the current interval. */
+ double start;
+};
+
+/**
+ * Rate limit state initializer.
+ */
+#define RATELIMIT_INITIALIZER(interval_init, burst_init) \
+ { (interval_init), (burst_init), 0, 0, 0 }
+
+/**
+ * Initialize a rate limit state.
+ */
+static inline void
+ratelimit_create(struct ratelimit *rl, double interval, int burst)
+{
+ rl->interval = interval;
+ rl->burst = burst;
+ rl->emitted = 0;
+ rl->suppressed = 0;
+ rl->start = 0;
+}
+
+/**
+ * Check if an event may be emitted. Returns true on success.
+ * @now is the current time.
+ *
+ * If the current interval is over, the total number of events
+ * suppressed in it is added to @suppressed.
+ */
+static inline bool
+ratelimit_check(struct ratelimit *rl, double now, int *suppressed)
+{
+ if (now > rl->start + rl->interval) {
+ /* Current interval is over, reset counters. */
+ *suppressed += rl->suppressed;
+ rl->emitted = 0;
+ rl->suppressed = 0;
+ rl->start = now;
+ }
+ if (rl->emitted < rl->burst) {
+ rl->emitted++;
+ return true;
+ } else {
+ rl->suppressed++;
+ return false;
+ }
+}
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif
+
+#endif /* TARANTOOL_RATELIMIT_H_INCLUDED */
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 7cba5cbb..0025d361 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -136,6 +136,8 @@ add_executable(rmean.test rmean.cc)
target_link_libraries(rmean.test stat unit)
add_executable(histogram.test histogram.c)
target_link_libraries(histogram.test stat unit)
+add_executable(ratelimit.test ratelimit.c)
+target_link_libraries(ratelimit.test unit)
add_executable(say.test say.c)
target_link_libraries(say.test core unit)
diff --git a/test/unit/ratelimit.c b/test/unit/ratelimit.c
new file mode 100644
index 00000000..3dfbe485
--- /dev/null
+++ b/test/unit/ratelimit.c
@@ -0,0 +1,78 @@
+#include <stdlib.h>
+#include <time.h>
+
+#include "unit.h"
+#include "ratelimit.h"
+
+#define check(expected_emitted, expected_suppressed) do { \
+ is(emitted, expected_emitted, "emitted %d expected %d", \
+ emitted, expected_emitted); \
+ is(suppressed, expected_suppressed, "suppressed %d expected %d",\
+ suppressed, expected_suppressed); \
+} while (0)
+
+int
+main()
+{
+ header();
+ plan(10);
+
+ srand(time(NULL));
+ double now = rand();
+
+ int burst = 10;
+ double interval = 5;
+ int count, emitted, suppressed;
+ struct ratelimit rl = RATELIMIT_INITIALIZER(interval, burst);
+ now += interval;
+
+ count = burst;
+ emitted = suppressed = 0;
+ for (int i = 0; i < count; i++) {
+ if (ratelimit_check(&rl, now, &suppressed))
+ emitted++;
+ now += interval / count / 2;
+ }
+ check(count, 0);
+
+ emitted = suppressed = 0;
+ for (int i = 0; i < count; i++) {
+ if (ratelimit_check(&rl, now, &suppressed))
+ emitted++;
+ now += interval / count / 2;
+ }
+ check(0, 0);
+
+ now += 1;
+ emitted = suppressed = 0;
+ if (ratelimit_check(&rl, now, &suppressed))
+ emitted++;
+ check(1, count);
+
+ now += interval * 2;
+ emitted = suppressed = 0;
+ if (ratelimit_check(&rl, now, &suppressed))
+ emitted++;
+ check(1, 0);
+
+ interval = 5;
+ burst = 100;
+ ratelimit_create(&rl, interval, burst);
+
+ int interval_count = 10;
+ count = burst * interval_count * 4;
+ emitted = suppressed = 0;
+ for (int i = 0; i < count; i++) {
+ if (ratelimit_check(&rl, now, &suppressed))
+ emitted++;
+ now += interval_count * interval / count;
+ }
+ now += interval;
+ ratelimit_check(&rl, now, &suppressed);
+ check(interval_count * burst, count - interval_count * burst);
+
+ check_plan();
+ footer();
+
+ return 0;
+}
diff --git a/test/unit/ratelimit.result b/test/unit/ratelimit.result
new file mode 100644
index 00000000..8a755c64
--- /dev/null
+++ b/test/unit/ratelimit.result
@@ -0,0 +1,13 @@
+ *** main ***
+1..10
+ok 1 - emitted 10 expected 10
+ok 2 - suppressed 0 expected 0
+ok 3 - emitted 0 expected 0
+ok 4 - suppressed 0 expected 0
+ok 5 - emitted 1 expected 1
+ok 6 - suppressed 10 expected 10
+ok 7 - emitted 1 expected 1
+ok 8 - suppressed 0 expected 0
+ok 9 - emitted 1000 expected 1000
+ok 10 - suppressed 3000 expected 3000
+ *** main: done ***
--
2.11.0
More information about the Tarantool-patches
mailing list