Squid Web Cache master
Loading...
Searching...
No Matches
CacheDigest.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9/* DEBUG: section 70 Cache Digest */
10
11#include "squid.h"
12#include "md5.h"
13#include "StatCounters.h"
14#include "Store.h"
15#include "store_key_md5.h"
16
17#if USE_CACHE_DIGESTS
18
19#include "CacheDigest.h"
20#include "util.h"
21
22/* local types */
23
24typedef struct {
25 int bit_count; /* total number of bits */
26 int bit_on_count; /* #bits turned on */
27 int bseq_len_sum; /* sum of all bit seq length */
28 int bseq_count; /* number of bit seqs */
30
31/* local functions */
32static void cacheDigestHashKey(const CacheDigest * cd, const cache_key * key);
33
34/* static array used by cacheDigestHashKey for optimization purposes */
35static uint32_t hashed_keys[4];
36
37void
38CacheDigest::init(uint64_t newCapacity)
39{
40 const auto newMaskSz = CacheDigest::CalcMaskSize(newCapacity, bits_per_entry);
41 assert(newCapacity > 0 && bits_per_entry > 0);
42 assert(newMaskSz != 0);
43 capacity = newCapacity;
44 mask_size = newMaskSz;
45 mask = static_cast<char *>(xcalloc(mask_size,1));
46 debugs(70, 2, "capacity: " << capacity << " entries, bpe: " << bits_per_entry << "; size: "
47 << mask_size << " bytes");
48}
49
50CacheDigest::CacheDigest(uint64_t aCapacity, uint8_t bpe) :
51 count(0),
52 del_count(0),
53 capacity(0),
54 mask(nullptr),
55 mask_size(0),
56 bits_per_entry(bpe)
57{
58 static_assert(SQUID_MD5_DIGEST_LENGTH == 16); /* our hash functions rely on 16 byte keys */
59 updateCapacity(aCapacity);
60}
61
66
69{
71 cl->count = count;
72 cl->del_count = del_count;
74 memcpy(cl->mask, mask, mask_size);
75 return cl;
76}
77
78void
80{
81 count = del_count = 0;
82 memset(mask, 0, mask_size);
83}
84
85void
86CacheDigest::updateCapacity(uint64_t newCapacity)
87{
89 init(newCapacity); // will re-init mask and mask_size
90}
91
92bool
94{
95 assert(key);
96 /* hash */
97 cacheDigestHashKey(this, key);
98 /* test corresponding bits */
99 return
104}
105
106void
108{
109 assert(key);
110 /* hash */
111 cacheDigestHashKey(this, key);
112 /* turn on corresponding bits */
113 int on_xition_cnt = 0;
114
115 if (!CBIT_TEST(mask, hashed_keys[0])) {
117 ++on_xition_cnt;
118 }
119
120 if (!CBIT_TEST(mask, hashed_keys[1])) {
122 ++on_xition_cnt;
123 }
124
125 if (!CBIT_TEST(mask, hashed_keys[2])) {
127 ++on_xition_cnt;
128 }
129
130 if (!CBIT_TEST(mask, hashed_keys[3])) {
132 ++on_xition_cnt;
133 }
134
135 statCounter.cd.on_xition_count.count(on_xition_cnt);
136 ++count;
137}
138
139void
141{
142 assert(key);
143 ++del_count;
144 /* we do not support deletions from the digest */
145}
146
147/* returns mask utilization parameters */
148static void
150{
151 int on_count = 0;
152 int pos = cd->mask_size * 8;
153 int seq_len_sum = 0;
154 int seq_count = 0;
155 int cur_seq_len = 0;
156 int cur_seq_type = 1;
157 assert(stats);
158 memset(stats, 0, sizeof(*stats));
159
160 while (pos-- > 0) {
161 const int is_on = 0 != CBIT_TEST(cd->mask, pos);
162
163 if (is_on)
164 ++on_count;
165
166 if (is_on != cur_seq_type || !pos) {
167 seq_len_sum += cur_seq_len;
168 ++seq_count;
169 cur_seq_type = is_on;
170 cur_seq_len = 0;
171 }
172
173 ++cur_seq_len;
174 }
175
176 stats->bit_count = cd->mask_size * 8;
177 stats->bit_on_count = on_count;
178 stats->bseq_len_sum = seq_len_sum;
179 stats->bseq_count = seq_count;
180}
181
182double
184{
185 CacheDigestStats stats;
186 cacheDigestStats(this, &stats);
187 return xpercent(stats.bit_on_count, stats.bit_count);
188}
189
190void
191cacheDigestGuessStatsUpdate(CacheDigestGuessStats * stats, int real_hit, int guess_hit)
192{
193 assert(stats);
194
195 if (real_hit) {
196 if (guess_hit)
197 ++stats->trueHits;
198 else
199 ++stats->falseMisses;
200 } else {
201 if (guess_hit)
202 ++stats->falseHits;
203 else
204 ++stats->trueMisses;
205 }
206}
207
208void
210{
211 const int true_count = stats->trueHits + stats->trueMisses;
212 const int false_count = stats->falseHits + stats->falseMisses;
213 const int hit_count = stats->trueHits + stats->falseHits;
214 const int miss_count = stats->trueMisses + stats->falseMisses;
215 const int tot_count = true_count + false_count;
216
217 assert(!label.isEmpty());
218 assert(tot_count == hit_count + miss_count); /* paranoid */
219
220 if (!tot_count) {
221 storeAppendPrintf(sentry, "no guess stats for " SQUIDSBUFPH " available\n", SQUIDSBUFPRINT(label));
222 return;
223 }
224
225 storeAppendPrintf(sentry, "Digest guesses stats for " SQUIDSBUFPH ":\n", SQUIDSBUFPRINT(label));
226 storeAppendPrintf(sentry, "guess\t hit\t\t miss\t\t total\t\t\n");
227 storeAppendPrintf(sentry, " \t #\t %%\t #\t %%\t #\t %%\t\n");
228 storeAppendPrintf(sentry, "true\t %d\t %.2f\t %d\t %.2f\t %d\t %.2f\n",
229 stats->trueHits, xpercent(stats->trueHits, tot_count),
230 stats->trueMisses, xpercent(stats->trueMisses, tot_count),
231 true_count, xpercent(true_count, tot_count));
232 storeAppendPrintf(sentry, "false\t %d\t %.2f\t %d\t %.2f\t %d\t %.2f\n",
233 stats->falseHits, xpercent(stats->falseHits, tot_count),
234 stats->falseMisses, xpercent(stats->falseMisses, tot_count),
235 false_count, xpercent(false_count, tot_count));
236 storeAppendPrintf(sentry, "all\t %d\t %.2f\t %d\t %.2f\t %d\t %.2f\n",
237 hit_count, xpercent(hit_count, tot_count),
238 miss_count, xpercent(miss_count, tot_count),
239 tot_count, xpercent(tot_count, tot_count));
240}
241
242void
244{
245 CacheDigestStats stats;
246 assert(cd && e);
247 cacheDigestStats(cd, &stats);
248 storeAppendPrintf(e, SQUIDSBUFPH " digest: size: %d bytes\n",
249 SQUIDSBUFPRINT(label), stats.bit_count / 8
250 );
251 storeAppendPrintf(e, "\t entries: count: %" PRIu64 " capacity: %" PRIu64 " util: %d%%\n",
252 cd->count,
253 cd->capacity,
254 xpercentInt(cd->count, cd->capacity)
255 );
256 storeAppendPrintf(e, "\t deletion attempts: %" PRIu64 "\n",
257 cd->del_count
258 );
259 storeAppendPrintf(e, "\t bits: per entry: %d on: %d capacity: %d util: %d%%\n",
260 cd->bits_per_entry,
261 stats.bit_on_count, stats.bit_count,
263 );
264 storeAppendPrintf(e, "\t bit-seq: count: %d avg.len: %.2f\n",
265 stats.bseq_count,
266 xdiv(stats.bseq_len_sum, stats.bseq_count)
267 );
268}
269
273static uint64_t
274UnsafeMaskSize(const uint64_t cap, const uint8_t bpe)
275{
276 Assure(bpe);
277
278 // This limit is paranoid because no instance can store enough objects to
279 // exceed this maximum.
280 const auto maxMaskSize = std::numeric_limits<uint64_t>::max() / 8;
281
282 // Same as ((cap*bpe + 7)/8 > maxMaskSize) but without overflowing multiplication or sum
283 if (cap > (maxMaskSize*8 - 7)/bpe)
284 return maxMaskSize;
285
286 return (cap*bpe + 7)/8;
287}
288
289uint32_t
290CacheDigest::CalcMaskSize(uint64_t cap, uint8_t bpe)
291{
292 // Our mask_size data member is uint32_t. That type is hard-coded in several
293 // places. TODO: Use a unique type name while revising related types. We
294 // cannot simply cap calculations at the maximum uint32_t value because we
295 // must also satisfy the following requirements to protect mask_size users:
296 //
297 // R1. Avoid overflows in code that does `mask_size * 8` (e.g., to compute bit positions).
298 // R2. Avoid overflows in code that stores `mask_size * 8` as `int`.
299 // R3. Avoid overflows in legacy callers that cast `mask_size` to `ssize_t`.
300 // R4. Avoid unreasonably large memory allocations for mask storage.
301 // Bug 4534 fix defined 256MB allocations as "reasonable".
302 // R5. Ensure that the digest capacity derived from this mask size can be
303 // sent to legacy installations that assert that the corresponding mask
304 // bit count is less than INT_MAX.
305 //
306 // Some of the current limits below are mathematically redundant (e.g., R5
307 // satisfies R2), but are explicitly listed to assist with safe refactoring.
308 //
309 // For a typical 32-bit `int`, this maxMaskSize is 268'435'454 bytes.
310 const auto maxMaskSize = std::min({
311 static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) / 8, // R1
312 static_cast<uint64_t>(std::numeric_limits<int>::max()) / 8, // R2
313 static_cast<uint64_t>(std::numeric_limits<ssize_t>::max()), // R3
314 static_cast<uint64_t>(256)*1024*1024, // R4
315 static_cast<uint64_t>(INT_MAX - 8) / 8}); // R5
316
317 const auto rawMaskSize = ::UnsafeMaskSize(cap, bpe);
318 return static_cast<uint32_t>(std::min(rawMaskSize, maxMaskSize));
319}
320
321static void
323{
324 const uint32_t bit_count = cd->mask_size * 8;
325 unsigned int tmp_keys[4];
326 /* we must memcpy to ensure alignment */
327 memcpy(tmp_keys, key, sizeof(tmp_keys));
328 hashed_keys[0] = htonl(tmp_keys[0]) % bit_count;
329 hashed_keys[1] = htonl(tmp_keys[1]) % bit_count;
330 hashed_keys[2] = htonl(tmp_keys[2]) % bit_count;
331 hashed_keys[3] = htonl(tmp_keys[3]) % bit_count;
332 debugs(70, 9, "cacheDigestHashKey: " << storeKeyText(key) << " -(" <<
333 bit_count << ")-> " << hashed_keys[0] << " " << hashed_keys[1] <<
334 " " << hashed_keys[2] << " " << hashed_keys[3]);
335}
336
337#endif
338
#define Assure(condition)
Definition Assure.h:35
void cacheDigestGuessStatsReport(const CacheDigestGuessStats *stats, StoreEntry *sentry, const SBuf &label)
void cacheDigestGuessStatsUpdate(CacheDigestGuessStats *stats, int real_hit, int guess_hit)
static uint32_t hashed_keys[4]
static uint64_t UnsafeMaskSize(const uint64_t cap, const uint8_t bpe)
static void cacheDigestStats(const CacheDigest *cd, CacheDigestStats *stats)
void cacheDigestReport(CacheDigest *cd, const SBuf &label, StoreEntry *e)
static void cacheDigestHashKey(const CacheDigest *cd, const cache_key *key)
#define SQUIDSBUFPH
Definition SBuf.h:31
#define SQUIDSBUFPRINT(s)
Definition SBuf.h:32
StatCounters statCounter
#define assert(EX)
Definition assert.h:17
void updateCapacity(uint64_t newCapacity)
changes mask size to fit newCapacity, resets bits to 0
char * mask
Definition CacheDigest.h:58
double usedMaskPercent() const
percentage of mask bits which are used
static uint32_t CalcMaskSize(uint64_t cap, uint8_t bpe)
uint64_t del_count
Definition CacheDigest.h:56
CacheDigest(uint64_t capacity, uint8_t bpe)
uint64_t count
Definition CacheDigest.h:55
uint32_t mask_size
Definition CacheDigest.h:59
void init(uint64_t newCapacity)
void add(const cache_key *key)
CacheDigest * clone() const
produce a new identical copy of the digest object
uint64_t capacity
Definition CacheDigest.h:57
int8_t bits_per_entry
Definition CacheDigest.h:60
bool contains(const cache_key *key) const
void clear()
reset the digest mask and counters
void remove(const cache_key *key)
Definition SBuf.h:94
bool isEmpty() const
Definition SBuf.h:435
struct StatCounters::@110 cd
StatHist on_xition_count
void count(double val)
Definition StatHist.cc:55
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define CBIT_SET(mask, bit)
Definition defines.h:72
#define CBIT_TEST(mask, bit)
Definition defines.h:74
#define SQUID_MD5_DIGEST_LENGTH
Definition md5.h:75
#define xfree
unsigned char cache_key
Store key.
Definition forward.h:29
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition store.cc:855
const char * storeKeyText(const cache_key *key)
#define INT_MAX
Definition types.h:70
#define PRIu64
Definition types.h:114
double xpercent(double part, double whole)
Definition util.cc:40
double xdiv(double nom, double denom)
Definition util.cc:53
int xpercentInt(double part, double whole)
Definition util.cc:46
void * xcalloc(size_t n, size_t sz)
Definition xalloc.cc:71
#define safe_free(x)
Definition xalloc.h:73