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