journal-rate-limit: replace in-house management of JournalRateLimitGroup with OrderedHashmap

No functional change, just refactoring.
This commit is contained in:
Yu Watanabe 2024-05-11 21:09:44 +09:00
parent 8df477675d
commit 0e2e3fa35a
5 changed files with 60 additions and 123 deletions

View file

@ -1,18 +1,13 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include "alloc-util.h"
#include "hashmap.h"
#include "journald-rate-limit.h"
#include "list.h"
#include "logarithm.h"
#include "random-util.h"
#include "string-util.h"
#include "time-util.h"
#define POOLS_MAX 5
#define BUCKETS_MAX 127
#define GROUPS_MAX 2047
static const int priority_map[] = {
@ -26,17 +21,14 @@ static const int priority_map[] = {
[LOG_DEBUG] = 4,
};
typedef struct JournalRateLimitPool JournalRateLimitPool;
typedef struct JournalRateLimitGroup JournalRateLimitGroup;
struct JournalRateLimitPool {
typedef struct JournalRateLimitPool {
usec_t begin;
unsigned num;
unsigned suppressed;
};
} JournalRateLimitPool;
struct JournalRateLimitGroup {
JournalRateLimit *parent;
typedef struct JournalRateLimitGroup {
OrderedHashmap *groups_by_id;
char *id;
@ -44,49 +36,16 @@ struct JournalRateLimitGroup {
usec_t interval;
JournalRateLimitPool pools[POOLS_MAX];
uint64_t hash;
LIST_FIELDS(JournalRateLimitGroup, bucket);
LIST_FIELDS(JournalRateLimitGroup, lru);
};
struct JournalRateLimit {
JournalRateLimitGroup* buckets[BUCKETS_MAX];
JournalRateLimitGroup *lru, *lru_tail;
unsigned n_groups;
uint8_t hash_key[16];
};
JournalRateLimit *journal_ratelimit_new(void) {
JournalRateLimit *r;
r = new0(JournalRateLimit, 1);
if (!r)
return NULL;
random_bytes(r->hash_key, sizeof(r->hash_key));
return r;
}
} JournalRateLimitGroup;
static JournalRateLimitGroup* journal_ratelimit_group_free(JournalRateLimitGroup *g) {
if (!g)
return NULL;
if (g->parent) {
assert(g->parent->n_groups > 0);
if (g->parent->lru_tail == g)
g->parent->lru_tail = g->lru_prev;
LIST_REMOVE(lru, g->parent->lru, g);
LIST_REMOVE(bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g);
g->parent->n_groups--;
}
if (g->groups_by_id && g->id)
/* The group is already removed from the hashmap when this is called from the
* destructor of the hashmap. Hence, do not check the return value here. */
ordered_hashmap_remove_value(g->groups_by_id, g->id, g);
free(g->id);
return mfree(g);
@ -94,14 +53,13 @@ static JournalRateLimitGroup* journal_ratelimit_group_free(JournalRateLimitGroup
DEFINE_TRIVIAL_CLEANUP_FUNC(JournalRateLimitGroup*, journal_ratelimit_group_free);
void journal_ratelimit_free(JournalRateLimit *r) {
assert(r);
while (r->lru)
journal_ratelimit_group_free(r->lru);
free(r);
}
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
journal_ratelimit_group_hash_ops,
char,
string_hash_func,
string_compare_func,
JournalRateLimitGroup,
journal_ratelimit_group_free);
static bool journal_ratelimit_group_expired(JournalRateLimitGroup *g, usec_t ts) {
assert(g);
@ -113,26 +71,29 @@ static bool journal_ratelimit_group_expired(JournalRateLimitGroup *g, usec_t ts)
return true;
}
static void journal_ratelimit_vacuum(JournalRateLimit *r, usec_t ts) {
assert(r);
static void journal_ratelimit_vacuum(OrderedHashmap *groups_by_id, usec_t ts) {
/* Makes room for at least one new item, but drop all expired items too. */
while (r->n_groups >= GROUPS_MAX ||
(r->lru_tail && journal_ratelimit_group_expired(r->lru_tail, ts)))
journal_ratelimit_group_free(r->lru_tail);
while (ordered_hashmap_size(groups_by_id) >= GROUPS_MAX)
journal_ratelimit_group_free(ordered_hashmap_first(groups_by_id));
JournalRateLimitGroup *g;
while ((g = ordered_hashmap_first(groups_by_id)) && journal_ratelimit_group_expired(g, ts))
journal_ratelimit_group_free(g);
}
static int journal_ratelimit_group_new(
JournalRateLimit *rl,
OrderedHashmap **groups_by_id,
const char *id,
usec_t interval,
usec_t ts,
JournalRateLimitGroup **ret) {
_cleanup_(journal_ratelimit_group_freep) JournalRateLimitGroup *g = NULL;
int r;
assert(rl);
assert(groups_by_id);
assert(id);
assert(ret);
@ -142,51 +103,40 @@ static int journal_ratelimit_group_new(
*g = (JournalRateLimitGroup) {
.id = strdup(id),
.hash = siphash24_string(id, rl->hash_key),
.interval = interval,
};
if (!g->id)
return -ENOMEM;
journal_ratelimit_vacuum(rl, ts);
journal_ratelimit_vacuum(*groups_by_id, ts);
LIST_PREPEND(bucket, rl->buckets[g->hash % BUCKETS_MAX], g);
LIST_PREPEND(lru, rl->lru, g);
if (!g->lru_next)
rl->lru_tail = g;
rl->n_groups++;
r = ordered_hashmap_ensure_put(groups_by_id, &journal_ratelimit_group_hash_ops, g->id, g);
if (r < 0)
return r;
assert(r > 0);
g->parent = rl;
g->groups_by_id = *groups_by_id;
*ret = TAKE_PTR(g);
return 0;
}
static int journal_ratelimit_group_acquire(
JournalRateLimit *rl,
OrderedHashmap **groups_by_id,
const char *id,
usec_t interval,
usec_t ts,
JournalRateLimitGroup **ret) {
JournalRateLimitGroup *head, *g = NULL;
uint64_t h;
JournalRateLimitGroup *g;
assert(rl);
assert(groups_by_id);
assert(id);
assert(ret);
h = siphash24_string(id, rl->hash_key);
head = rl->buckets[h % BUCKETS_MAX];
LIST_FOREACH(bucket, i, head)
if (streq(i->id, id)) {
g = i;
break;
}
g = ordered_hashmap_get(*groups_by_id, id);
if (!g)
return journal_ratelimit_group_new(rl, id, interval, ts, ret);
return journal_ratelimit_group_new(groups_by_id, id, interval, ts, ret);
g->interval = interval;
@ -223,7 +173,7 @@ static unsigned burst_modulate(unsigned burst, uint64_t available) {
}
int journal_ratelimit_test(
JournalRateLimit *rl,
OrderedHashmap **groups_by_id,
const char *id,
usec_t rl_interval,
unsigned rl_burst,
@ -236,6 +186,7 @@ int journal_ratelimit_test(
usec_t ts;
int r;
assert(groups_by_id);
assert(id);
/* Returns:
@ -245,12 +196,9 @@ int journal_ratelimit_test(
* < 0 error
*/
if (!rl)
return 1;
ts = now(CLOCK_MONOTONIC);
r = journal_ratelimit_group_acquire(rl, id, rl_interval, ts, &g);
r = journal_ratelimit_group_acquire(groups_by_id, id, rl_interval, ts, &g);
if (r < 0)
return r;

View file

@ -1,14 +1,13 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include "hashmap.h"
#include "time-util.h"
typedef struct JournalRateLimit JournalRateLimit;
JournalRateLimit *journal_ratelimit_new(void);
void journal_ratelimit_free(JournalRateLimit *r);
int journal_ratelimit_test(
JournalRateLimit *rl,
OrderedHashmap **groups_by_id,
const char *id,
usec_t rl_interval,
unsigned rl_burst,

View file

@ -1274,7 +1274,7 @@ void server_dispatch_message(
(void) server_determine_space(s, &available, /* limit= */ NULL);
rl = journal_ratelimit_test(
s->ratelimit,
&s->ratelimit_groups_by_id,
c->unit,
c->log_ratelimit_interval,
c->log_ratelimit_burst,
@ -2809,10 +2809,6 @@ int server_init(Server *s, const char *namespace) {
if (r < 0)
return r;
s->ratelimit = journal_ratelimit_new();
if (!s->ratelimit)
return log_oom();
r = cg_get_root_path(&s->cgroup_root);
if (r < 0)
return log_error_errno(r, "Failed to acquire cgroup root path: %m");
@ -2913,8 +2909,7 @@ Server* server_free(Server *s) {
safe_close(s->notify_fd);
safe_close(s->forward_socket_fd);
if (s->ratelimit)
journal_ratelimit_free(s->ratelimit);
ordered_hashmap_free(s->ratelimit_groups_by_id);
server_unmap_seqnum_file(s->seqnum, sizeof(*s->seqnum));
server_unmap_seqnum_file(s->kernel_seqnum, sizeof(*s->kernel_seqnum));

View file

@ -14,7 +14,6 @@ typedef struct Server Server;
#include "hashmap.h"
#include "journal-file.h"
#include "journald-context.h"
#include "journald-rate-limit.h"
#include "journald-stream.h"
#include "list.h"
#include "prioq.h"
@ -108,7 +107,7 @@ struct Server {
char *buffer;
JournalRateLimit *ratelimit;
OrderedHashmap *ratelimit_groups_by_id;
usec_t sync_interval_usec;
usec_t ratelimit_interval;
unsigned ratelimit_burst;

View file

@ -4,41 +4,37 @@
#include "tests.h"
TEST(journal_ratelimit_test) {
JournalRateLimit *rl;
_cleanup_ordered_hashmap_free_ OrderedHashmap *rl = NULL;
int r;
assert_se(rl = journal_ratelimit_new());
for (unsigned i = 0; i < 20; i++) {
r = journal_ratelimit_test(rl, "hoge", USEC_PER_SEC, 10, LOG_DEBUG, 0);
r = journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_DEBUG, 0);
assert_se(r == (i < 10 ? 1 : 0));
r = journal_ratelimit_test(rl, "foo", 10 * USEC_PER_SEC, 10, LOG_DEBUG, 0);
r = journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_DEBUG, 0);
assert_se(r == (i < 10 ? 1 : 0));
}
/* Different priority group with the same ID is not ratelimited. */
assert_se(journal_ratelimit_test(rl, "hoge", USEC_PER_SEC, 10, LOG_INFO, 0) == 1);
assert_se(journal_ratelimit_test(rl, "foo", 10 * USEC_PER_SEC, 10, LOG_INFO, 0) == 1);
assert_se(journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_INFO, 0) == 1);
assert_se(journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_INFO, 0) == 1);
/* Still LOG_DEBUG is ratelimited. */
assert_se(journal_ratelimit_test(rl, "hoge", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 0);
assert_se(journal_ratelimit_test(rl, "foo", 10 * USEC_PER_SEC, 10, LOG_DEBUG, 0) == 0);
assert_se(journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 0);
assert_se(journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_DEBUG, 0) == 0);
/* Different ID is not ratelimited. */
assert_se(journal_ratelimit_test(rl, "quux", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 1);
assert_se(journal_ratelimit_test(&rl, "quux", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 1);
usleep_safe(USEC_PER_SEC);
/* The ratelimit is now expired (11 trials are suppressed, so the return value should be 12). */
assert_se(journal_ratelimit_test(rl, "hoge", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 1 + 11);
assert_se(journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 1 + 11);
/* foo is still ratelimited. */
assert_se(journal_ratelimit_test(rl, "foo", 10 * USEC_PER_SEC, 10, LOG_DEBUG, 0) == 0);
assert_se(journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_DEBUG, 0) == 0);
/* Still other priority and/or other IDs are not ratelimited. */
assert_se(journal_ratelimit_test(rl, "hoge", USEC_PER_SEC, 10, LOG_INFO, 0) == 1);
assert_se(journal_ratelimit_test(rl, "foo", 10 * USEC_PER_SEC, 10, LOG_INFO, 0) == 1);
assert_se(journal_ratelimit_test(rl, "quux", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 1);
journal_ratelimit_free(rl);
assert_se(journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_INFO, 0) == 1);
assert_se(journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_INFO, 0) == 1);
assert_se(journal_ratelimit_test(&rl, "quux", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 1);
}
DEFINE_TEST_MAIN(LOG_INFO);