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