]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journald-rate-limit.c
tree-wide: remove Lennart's copyright lines
[thirdparty/systemd.git] / src / journal / journald-rate-limit.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
6e409ce1 2
6e409ce1 3#include <errno.h>
07630cea 4#include <string.h>
6e409ce1 5
b5efdb8a 6#include "alloc-util.h"
6e409ce1 7#include "hashmap.h"
cf0fbc49 8#include "journald-rate-limit.h"
07630cea 9#include "list.h"
3df3e884 10#include "random-util.h"
07630cea
LP
11#include "string-util.h"
12#include "util.h"
6e409ce1
LP
13
14#define POOLS_MAX 5
15#define BUCKETS_MAX 127
16#define GROUPS_MAX 2047
17
18static const int priority_map[] = {
19 [LOG_EMERG] = 0,
20 [LOG_ALERT] = 0,
21 [LOG_CRIT] = 0,
22 [LOG_ERR] = 1,
23 [LOG_WARNING] = 2,
24 [LOG_NOTICE] = 3,
25 [LOG_INFO] = 3,
26 [LOG_DEBUG] = 4
27};
28
29typedef struct JournalRateLimitPool JournalRateLimitPool;
30typedef struct JournalRateLimitGroup JournalRateLimitGroup;
31
32struct JournalRateLimitPool {
33 usec_t begin;
34 unsigned num;
35 unsigned suppressed;
36};
37
38struct JournalRateLimitGroup {
39 JournalRateLimit *parent;
40
41 char *id;
42 JournalRateLimitPool pools[POOLS_MAX];
0cb3c286 43 uint64_t hash;
6e409ce1
LP
44
45 LIST_FIELDS(JournalRateLimitGroup, bucket);
46 LIST_FIELDS(JournalRateLimitGroup, lru);
47};
48
49struct JournalRateLimit {
50 usec_t interval;
51 unsigned burst;
52
53 JournalRateLimitGroup* buckets[BUCKETS_MAX];
54 JournalRateLimitGroup *lru, *lru_tail;
55
56 unsigned n_groups;
9bf3b535
LP
57
58 uint8_t hash_key[16];
6e409ce1
LP
59};
60
61JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) {
62 JournalRateLimit *r;
63
64 assert(interval > 0 || burst == 0);
65
66 r = new0(JournalRateLimit, 1);
67 if (!r)
68 return NULL;
69
70 r->interval = interval;
71 r->burst = burst;
72
9bf3b535
LP
73 random_bytes(r->hash_key, sizeof(r->hash_key));
74
6e409ce1
LP
75 return r;
76}
77
78static void journal_rate_limit_group_free(JournalRateLimitGroup *g) {
79 assert(g);
80
81 if (g->parent) {
82 assert(g->parent->n_groups > 0);
83
84 if (g->parent->lru_tail == g)
85 g->parent->lru_tail = g->lru_prev;
86
71fda00f
LP
87 LIST_REMOVE(lru, g->parent->lru, g);
88 LIST_REMOVE(bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g);
6e409ce1 89
313cefa1 90 g->parent->n_groups--;
6e409ce1
LP
91 }
92
93 free(g->id);
94 free(g);
95}
96
97void journal_rate_limit_free(JournalRateLimit *r) {
98 assert(r);
99
100 while (r->lru)
101 journal_rate_limit_group_free(r->lru);
783d2675
LP
102
103 free(r);
6e409ce1
LP
104}
105
44a6b1b6 106_pure_ static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, usec_t ts) {
6e409ce1
LP
107 unsigned i;
108
109 assert(g);
110
111 for (i = 0; i < POOLS_MAX; i++)
112 if (g->pools[i].begin + g->parent->interval >= ts)
113 return false;
114
115 return true;
116}
117
118static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) {
119 assert(r);
120
121 /* Makes room for at least one new item, but drop all
122 * expored items too. */
123
124 while (r->n_groups >= GROUPS_MAX ||
125 (r->lru_tail && journal_rate_limit_group_expired(r->lru_tail, ts)))
126 journal_rate_limit_group_free(r->lru_tail);
127}
128
129static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) {
130 JournalRateLimitGroup *g;
b826ab58 131 struct siphash state;
6e409ce1
LP
132
133 assert(r);
134 assert(id);
135
136 g = new0(JournalRateLimitGroup, 1);
137 if (!g)
138 return NULL;
139
140 g->id = strdup(id);
141 if (!g->id)
142 goto fail;
143
0cb3c286 144 siphash24_init(&state, r->hash_key);
b826ab58 145 string_hash_func(g->id, &state);
933f9cae 146 g->hash = siphash24_finalize(&state);
6e409ce1
LP
147
148 journal_rate_limit_vacuum(r, ts);
149
71fda00f
LP
150 LIST_PREPEND(bucket, r->buckets[g->hash % BUCKETS_MAX], g);
151 LIST_PREPEND(lru, r->lru, g);
6e409ce1
LP
152 if (!g->lru_next)
153 r->lru_tail = g;
313cefa1 154 r->n_groups++;
6e409ce1
LP
155
156 g->parent = r;
157 return g;
158
159fail:
160 journal_rate_limit_group_free(g);
161 return NULL;
162}
163
6e409ce1
LP
164static unsigned burst_modulate(unsigned burst, uint64_t available) {
165 unsigned k;
166
167 /* Modulates the burst rate a bit with the amount of available
168 * disk space */
169
170 k = u64log2(available);
171
172 /* 1MB */
173 if (k <= 20)
174 return burst;
175
eeb08480 176 burst = (burst * (k-16)) / 4;
6e409ce1
LP
177
178 /*
179 * Example:
180 *
181 * <= 1MB = rate * 1
182 * 16MB = rate * 2
183 * 256MB = rate * 3
184 * 4GB = rate * 4
185 * 64GB = rate * 5
186 * 1TB = rate * 6
187 */
188
189 return burst;
190}
191
192int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) {
0cb3c286 193 uint64_t h;
6e409ce1
LP
194 JournalRateLimitGroup *g;
195 JournalRateLimitPool *p;
b826ab58 196 struct siphash state;
6e409ce1
LP
197 unsigned burst;
198 usec_t ts;
199
200 assert(id);
201
ec6fe7c8
LP
202 /* Returns:
203 *
204 * 0 → the log message shall be suppressed,
205 * 1 + n → the log message shall be permitted, and n messages were dropped from the peer before
206 * < 0 → error
207 */
208
6e409ce1
LP
209 if (!r)
210 return 1;
211
212 if (r->interval == 0 || r->burst == 0)
213 return 1;
214
215 burst = burst_modulate(r->burst, available);
216
217 ts = now(CLOCK_MONOTONIC);
218
0cb3c286 219 siphash24_init(&state, r->hash_key);
b826ab58 220 string_hash_func(id, &state);
933f9cae 221 h = siphash24_finalize(&state);
6e409ce1
LP
222 g = r->buckets[h % BUCKETS_MAX];
223
224 LIST_FOREACH(bucket, g, g)
225 if (streq(g->id, id))
226 break;
227
228 if (!g) {
229 g = journal_rate_limit_group_new(r, id, ts);
230 if (!g)
231 return -ENOMEM;
232 }
233
234 p = &g->pools[priority_map[priority]];
235
236 if (p->begin <= 0) {
237 p->suppressed = 0;
238 p->num = 1;
239 p->begin = ts;
240 return 1;
241 }
242
243 if (p->begin + r->interval < ts) {
244 unsigned s;
245
246 s = p->suppressed;
247 p->suppressed = 0;
248 p->num = 1;
249 p->begin = ts;
250
251 return 1 + s;
252 }
253
d2665e08 254 if (p->num < burst) {
6e409ce1
LP
255 p->num++;
256 return 1;
257 }
258
259 p->suppressed++;
260 return 0;
261}