From 33ac7436a1ff14e4170ffcee5a155b6bae4e0ae3 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Wed, 5 Nov 2014 13:32:16 +0100 Subject: [PATCH] util: introduce ratelimit helpers This adds "struct ratelimit" and "ratelimit_test()". It's a very simple rate-limit helper modeled after Linux' lib/ratelimit.c by Dave Young. This comes in handy to limit log-messages in possible busy loops etc.. Signed-off-by: David Herrmann Reviewed-by: Peter Hutterer Signed-off-by: Peter Hutterer --- src/libinput-util.c | 48 +++++++++++++++++++++++++++++++++++++++++++++ src/libinput-util.h | 16 +++++++++++++++ test/misc.c | 42 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/src/libinput-util.c b/src/libinput-util.c index eeb9786a..34d55496 100644 --- a/src/libinput-util.c +++ b/src/libinput-util.c @@ -65,3 +65,51 @@ list_empty(const struct list *list) { return list->next == list; } + +void +ratelimit_init(struct ratelimit *r, uint64_t ival_ms, unsigned int burst) +{ + r->interval = ival_ms; + r->begin = 0; + r->burst = burst; + r->num = 0; +} + +/* + * Perform rate-limit test. Returns RATELIMIT_PASS if the rate-limited action + * is still allowed, RATELIMIT_THRESHOLD if the limit has been reached with + * this call, and RATELIMIT_EXCEEDED if you're beyond the threshold. + * It's safe to treat the return-value as boolean, if you're not interested in + * the exact state. It evaluates to "true" if the threshold hasn't been + * exceeded, yet. + * + * The ratelimit object must be initialized via ratelimit_init(). + * + * Modelled after Linux' lib/ratelimit.c by Dave Young + * , which is licensed GPLv2. + */ +enum ratelimit_state +ratelimit_test(struct ratelimit *r) +{ + struct timespec ts; + uint64_t mtime; + + if (r->interval <= 0 || r->burst <= 0) + return RATELIMIT_PASS; + + clock_gettime(CLOCK_MONOTONIC, &ts); + mtime = ts.tv_sec * 1000 + ts.tv_nsec / 1000 / 1000; + + if (r->begin <= 0 || r->begin + r->interval < mtime) { + /* reset counter */ + r->begin = mtime; + r->num = 1; + return RATELIMIT_PASS; + } else if (r->num < r->burst) { + /* continue burst */ + return (++r->num == r->burst) ? RATELIMIT_THRESHOLD + : RATELIMIT_PASS; + } + + return RATELIMIT_EXCEEDED; +} diff --git a/src/libinput-util.h b/src/libinput-util.h index 51759e87..909c9dbd 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -280,4 +280,20 @@ matrix_to_farray6(const struct matrix *m, float out[6]) out[5] = m->val[1][2]; } +enum ratelimit_state { + RATELIMIT_EXCEEDED, + RATELIMIT_THRESHOLD, + RATELIMIT_PASS, +}; + +struct ratelimit { + uint64_t interval; + uint64_t begin; + unsigned int burst; + unsigned int num; +}; + +void ratelimit_init(struct ratelimit *r, uint64_t ival_ms, unsigned int burst); +enum ratelimit_state ratelimit_test(struct ratelimit *r); + #endif /* LIBINPUT_UTIL_H */ diff --git a/test/misc.c b/test/misc.c index 1512180e..4ed9dce8 100644 --- a/test/misc.c +++ b/test/misc.c @@ -508,6 +508,46 @@ START_TEST(matrix_helpers) } END_TEST +START_TEST(ratelimit_helpers) +{ + struct ratelimit rl; + unsigned int i, j; + + /* 10 attempts every 100ms */ + ratelimit_init(&rl, 100, 10); + + for (j = 0; j < 3; ++j) { + /* a burst of 9 attempts must succeed */ + for (i = 0; i < 9; ++i) { + ck_assert_int_eq(ratelimit_test(&rl), + RATELIMIT_PASS); + } + + /* the 10th attempt reaches the threshold */ + ck_assert_int_eq(ratelimit_test(&rl), RATELIMIT_THRESHOLD); + + /* ..then further attempts must fail.. */ + ck_assert_int_eq(ratelimit_test(&rl), RATELIMIT_EXCEEDED); + + /* ..regardless of how often we try. */ + for (i = 0; i < 100; ++i) { + ck_assert_int_eq(ratelimit_test(&rl), + RATELIMIT_EXCEEDED); + } + + /* ..even after waiting 20ms */ + msleep(20); + for (i = 0; i < 100; ++i) { + ck_assert_int_eq(ratelimit_test(&rl), + RATELIMIT_EXCEEDED); + } + + /* but after 100ms the counter is reset */ + msleep(90); /* +10ms to account for time drifts */ + } +} +END_TEST + int main (int argc, char **argv) { litest_add_no_device("events:conversion", event_conversion_device_notify); litest_add_no_device("events:conversion", event_conversion_pointer); @@ -519,5 +559,7 @@ int main (int argc, char **argv) { litest_add_no_device("config:status string", config_status_string); litest_add_no_device("misc:matrix", matrix_helpers); + litest_add_no_device("misc:ratelimit", ratelimit_helpers); + return litest_run(argc, argv); }