2 * DEBUG: section 71 Store Digest Manager
3 * AUTHOR: Alex Rousskov
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34 * TODO: We probably do not track all the cases when
35 * storeDigestNoteStoreReady() must be called; this may prevent
36 * storeDigestRebuild/write schedule to be activated
43 #include "mgr/Registration.h"
44 #include "store_digest.h"
47 #include "CacheDigest.h"
48 #include "HttpReply.h"
49 #include "HttpRequest.h"
51 #include "MemObject.h"
52 #include "PeerDigest.h"
54 #include "SquidConfig.h"
55 #include "SquidTime.h"
57 #include "StoreSearch.h"
67 class StoreDigestState
71 StoreDigestCBlock cblock
;
72 int rebuild_lock
; /* bucket number */
73 StoreEntry
* rewrite_lock
; /* points to store entry with the digest */
74 StoreSearchPointer theSearch
;
81 int del_count
; /* #store entries deleted from store_digest */
82 int del_lost_count
; /* #store entries not found in store_digest on delete */
83 int add_count
; /* #store entries accepted to store_digest */
84 int add_coll_count
; /* #accepted entries that collided with existing ones */
85 int rej_count
; /* #store entries not accepted to store_digest */
86 int rej_coll_count
; /* #not accepted entries that collided with existing ones */
90 static StoreDigestState sd_state
;
91 static StoreDigestStats sd_stats
;
93 /* local prototypes */
94 static void storeDigestRebuildStart(void *datanotused
);
95 static void storeDigestRebuildResume(void);
96 static void storeDigestRebuildFinish(void);
97 static void storeDigestRebuildStep(void *datanotused
);
98 static void storeDigestRewriteStart(void *);
99 static void storeDigestRewriteResume(void);
100 static void storeDigestRewriteFinish(StoreEntry
* e
);
101 static EVH storeDigestSwapOutStep
;
102 static void storeDigestCBlockSwapOut(StoreEntry
* e
);
103 static int storeDigestCalcCap(void);
104 static int storeDigestResize(void);
105 static void storeDigestAdd(const StoreEntry
*);
107 #endif /* USE_CACHE_DIGESTS */
110 storeDigestRegisterWithCacheManager(void)
112 Mgr::RegisterAction("store_digest", "Store Digest", storeDigestReport
, 0, 1);
120 storeDigestInit(void)
122 storeDigestRegisterWithCacheManager();
124 #if USE_CACHE_DIGESTS
125 const int cap
= storeDigestCalcCap();
127 if (!Config
.onoff
.digest_generation
) {
129 debugs(71, 3, "Local cache digest generation disabled");
133 store_digest
= cacheDigestCreate(cap
, Config
.digest
.bits_per_entry
);
134 debugs(71, DBG_IMPORTANT
, "Local cache digest enabled; rebuild/rewrite every " <<
135 (int) Config
.digest
.rebuild_period
<< "/" <<
136 (int) Config
.digest
.rewrite_period
<< " sec");
138 memset(&sd_state
, 0, sizeof(sd_state
));
141 debugs(71, 3, "Local cache digest is 'off'");
145 /* called when store_rebuild completes */
147 storeDigestNoteStoreReady(void)
149 #if USE_CACHE_DIGESTS
151 if (Config
.onoff
.digest_generation
) {
152 storeDigestRebuildStart(NULL
);
153 storeDigestRewriteStart(NULL
);
159 //TODO: this seems to be dead code. Is it needed?
161 storeDigestDel(const StoreEntry
* entry
)
163 #if USE_CACHE_DIGESTS
165 if (!Config
.onoff
.digest_generation
) {
169 assert(entry
&& store_digest
);
170 debugs(71, 6, "storeDigestDel: checking entry, key: " << entry
->getMD5Text());
172 if (!EBIT_TEST(entry
->flags
, KEY_PRIVATE
)) {
173 if (!cacheDigestTest(store_digest
, (const cache_key
*)entry
->key
)) {
174 ++sd_stats
.del_lost_count
;
175 debugs(71, 6, "storeDigestDel: lost entry, key: " << entry
->getMD5Text() << " url: " << entry
->url() );
177 ++sd_stats
.del_count
;
178 cacheDigestDel(store_digest
, (const cache_key
*)entry
->key
);
179 debugs(71, 6, "storeDigestDel: deled entry, key: " << entry
->getMD5Text());
182 #endif //USE_CACHE_DIGESTS
186 storeDigestReport(StoreEntry
* e
)
188 #if USE_CACHE_DIGESTS
190 if (!Config
.onoff
.digest_generation
) {
195 cacheDigestReport(store_digest
, "store", e
);
196 storeAppendPrintf(e
, "\t added: %d rejected: %d ( %.2f %%) del-ed: %d\n",
199 xpercent(sd_stats
.rej_count
, sd_stats
.rej_count
+ sd_stats
.add_count
),
201 storeAppendPrintf(e
, "\t collisions: on add: %.2f %% on rej: %.2f %%\n",
202 xpercent(sd_stats
.add_coll_count
, sd_stats
.add_count
),
203 xpercent(sd_stats
.rej_coll_count
, sd_stats
.rej_count
));
205 storeAppendPrintf(e
, "store digest: disabled.\n");
208 #endif //USE_CACHE_DIGESTS
215 #if USE_CACHE_DIGESTS
217 /* should we digest this entry? used by storeDigestAdd() */
219 storeDigestAddable(const StoreEntry
* e
)
221 /* add some stats! XXX */
223 debugs(71, 6, "storeDigestAddable: checking entry, key: " << e
->getMD5Text());
225 /* check various entry flags (mimics StoreEntry::checkCachable XXX) */
227 if (!EBIT_TEST(e
->flags
, ENTRY_CACHABLE
)) {
228 debugs(71, 6, "storeDigestAddable: NO: not cachable");
232 if (EBIT_TEST(e
->flags
, KEY_PRIVATE
)) {
233 debugs(71, 6, "storeDigestAddable: NO: private key");
237 if (EBIT_TEST(e
->flags
, ENTRY_NEGCACHED
)) {
238 debugs(71, 6, "storeDigestAddable: NO: negative cached");
242 if (EBIT_TEST(e
->flags
, RELEASE_REQUEST
)) {
243 debugs(71, 6, "storeDigestAddable: NO: release requested");
247 if (e
->store_status
== STORE_OK
&& EBIT_TEST(e
->flags
, ENTRY_BAD_LENGTH
)) {
248 debugs(71, 6, "storeDigestAddable: NO: wrong content-length");
252 /* do not digest huge objects */
253 if (e
->swap_file_sz
> (uint64_t )Config
.Store
.maxObjectSize
) {
254 debugs(71, 6, "storeDigestAddable: NO: too big");
258 /* still here? check staleness */
259 /* Note: We should use the time of the next rebuild, not (cur_time+period) */
260 if (refreshCheckDigest(e
, Config
.digest
.rebuild_period
)) {
261 debugs(71, 6, "storeDigestAdd: entry expires within " << Config
.digest
.rebuild_period
<< " secs, ignoring");
266 * idea: how about also skipping very fresh (thus, potentially
267 * unstable) entries? Should be configurable through
268 * cd_refresh_pattern, of course.
271 * idea: skip objects that are going to be purged before the next
278 storeDigestAdd(const StoreEntry
* entry
)
280 assert(entry
&& store_digest
);
282 if (storeDigestAddable(entry
)) {
283 ++sd_stats
.add_count
;
285 if (cacheDigestTest(store_digest
, (const cache_key
*)entry
->key
))
286 ++sd_stats
.add_coll_count
;
288 cacheDigestAdd(store_digest
, (const cache_key
*)entry
->key
);
290 debugs(71, 6, "storeDigestAdd: added entry, key: " << entry
->getMD5Text());
292 ++sd_stats
.rej_count
;
294 if (cacheDigestTest(store_digest
, (const cache_key
*)entry
->key
))
295 ++sd_stats
.rej_coll_count
;
299 /* rebuilds digest from scratch */
301 storeDigestRebuildStart(void *datanotused
)
303 assert(store_digest
);
304 /* prevent overlapping if rebuild schedule is too tight */
306 if (sd_state
.rebuild_lock
) {
307 debugs(71, DBG_IMPORTANT
, "storeDigestRebuildStart: overlap detected, consider increasing rebuild period");
311 sd_state
.rebuild_lock
= 1;
312 debugs(71, 2, "storeDigestRebuildStart: rebuild #" << sd_state
.rebuild_count
+ 1);
314 if (sd_state
.rewrite_lock
) {
315 debugs(71, 2, "storeDigestRebuildStart: waiting for Rewrite to finish.");
319 storeDigestRebuildResume();
322 /* called be Rewrite to push Rebuild forward */
324 storeDigestRebuildResume(void)
326 assert(sd_state
.rebuild_lock
);
327 assert(!sd_state
.rewrite_lock
);
328 sd_state
.theSearch
= Store::Root().search(NULL
, NULL
);
329 /* resize or clear */
331 if (!storeDigestResize())
332 cacheDigestClear(store_digest
); /* not clean()! */
334 memset(&sd_stats
, 0, sizeof(sd_stats
));
336 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep
, NULL
, 0.0, 1);
339 /* finishes swap out sequence for the digest; schedules next rebuild */
341 storeDigestRebuildFinish(void)
343 assert(sd_state
.rebuild_lock
);
344 sd_state
.rebuild_lock
= 0;
345 ++sd_state
.rebuild_count
;
346 debugs(71, 2, "storeDigestRebuildFinish: done.");
347 eventAdd("storeDigestRebuildStart", storeDigestRebuildStart
, NULL
, (double)
348 Config
.digest
.rebuild_period
, 1);
349 /* resume pending Rewrite if any */
351 if (sd_state
.rewrite_lock
)
352 storeDigestRewriteResume();
355 /* recalculate a few hash buckets per invocation; schedules next step */
357 storeDigestRebuildStep(void *datanotused
)
359 /* TODO: call Store::Root().size() to determine this.. */
360 int count
= Config
.Store
.objectsPerBucket
* (int) ceil((double) store_hash_buckets
*
361 (double) Config
.digest
.rebuild_chunk_percentage
/ 100.0);
362 assert(sd_state
.rebuild_lock
);
364 debugs(71, 3, "storeDigestRebuildStep: buckets: " << store_hash_buckets
<< " entries to check: " << count
);
366 while (count
-- && !sd_state
.theSearch
->isDone() && sd_state
.theSearch
->next())
367 storeDigestAdd(sd_state
.theSearch
->currentItem());
370 if (sd_state
.theSearch
->isDone())
371 storeDigestRebuildFinish();
373 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep
, NULL
, 0.0, 1);
376 /* starts swap out sequence for the digest */
378 storeDigestRewriteStart(void *datanotused
)
384 assert(store_digest
);
385 /* prevent overlapping if rewrite schedule is too tight */
387 if (sd_state
.rewrite_lock
) {
388 debugs(71, DBG_IMPORTANT
, "storeDigestRewrite: overlap detected, consider increasing rewrite period");
392 debugs(71, 2, "storeDigestRewrite: start rewrite #" << sd_state
.rewrite_count
+ 1);
393 /* make new store entry */
394 url
= internalLocalUri("/squid-internal-periodic/", StoreDigestFileName
);
395 flags
.cachable
= true;
396 e
= storeCreateEntry(url
, url
, flags
, Http::METHOD_GET
);
398 sd_state
.rewrite_lock
= e
;
399 debugs(71, 3, "storeDigestRewrite: url: " << url
<< " key: " << e
->getMD5Text());
400 HttpRequest
*req
= HttpRequest::CreateFromUrl(url
);
401 e
->mem_obj
->request
= req
;
402 HTTPMSGLOCK(e
->mem_obj
->request
);
403 /* wait for rebuild (if any) to finish */
405 if (sd_state
.rebuild_lock
) {
406 debugs(71, 2, "storeDigestRewriteStart: waiting for rebuild to finish.");
410 storeDigestRewriteResume();
414 storeDigestRewriteResume(void)
418 assert(sd_state
.rewrite_lock
);
419 assert(!sd_state
.rebuild_lock
);
420 e
= sd_state
.rewrite_lock
;
421 sd_state
.rewrite_offset
= 0;
422 EBIT_SET(e
->flags
, ENTRY_SPECIAL
);
423 /* setting public key will purge old digest entry if any */
426 HttpReply
*rep
= new HttpReply
;
427 rep
->setHeaders(HTTP_OK
, "Cache Digest OK",
428 "application/cache-digest", (store_digest
->mask_size
+ sizeof(sd_state
.cblock
)),
429 squid_curtime
, (squid_curtime
+ Config
.digest
.rewrite_period
) );
430 debugs(71, 3, "storeDigestRewrite: entry expires on " << rep
->expires
<<
431 " (" << std::showpos
<< (int) (rep
->expires
- squid_curtime
) << ")");
433 e
->replaceHttpReply(rep
);
434 storeDigestCBlockSwapOut(e
);
436 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep
, sd_state
.rewrite_lock
, 0.0, 1, false);
439 /* finishes swap out sequence for the digest; schedules next rewrite */
441 storeDigestRewriteFinish(StoreEntry
* e
)
443 assert(e
== sd_state
.rewrite_lock
);
446 debugs(71, 2, "storeDigestRewriteFinish: digest expires at " << e
->expires
<<
447 " (" << std::showpos
<< (int) (e
->expires
- squid_curtime
) << ")");
448 /* is this the write order? @?@ */
449 e
->mem_obj
->unlinkRequest();
451 sd_state
.rewrite_lock
= NULL
;
452 ++sd_state
.rewrite_count
;
453 eventAdd("storeDigestRewriteStart", storeDigestRewriteStart
, NULL
, (double)
454 Config
.digest
.rewrite_period
, 1);
455 /* resume pending Rebuild if any */
457 if (sd_state
.rebuild_lock
)
458 storeDigestRebuildResume();
461 /* swaps out one digest "chunk" per invocation; schedules next swap out */
463 storeDigestSwapOutStep(void *data
)
465 StoreEntry
*e
= static_cast<StoreEntry
*>(data
);
466 int chunk_size
= Config
.digest
.swapout_chunk_size
;
467 assert(e
== sd_state
.rewrite_lock
);
469 /* _add_ check that nothing bad happened while we were waiting @?@ @?@ */
471 if (sd_state
.rewrite_offset
+ chunk_size
> store_digest
->mask_size
)
472 chunk_size
= store_digest
->mask_size
- sd_state
.rewrite_offset
;
474 e
->append(store_digest
->mask
+ sd_state
.rewrite_offset
, chunk_size
);
476 debugs(71, 3, "storeDigestSwapOutStep: size: " << store_digest
->mask_size
<<
477 " offset: " << sd_state
.rewrite_offset
<< " chunk: " <<
478 chunk_size
<< " bytes");
480 sd_state
.rewrite_offset
+= chunk_size
;
483 if (sd_state
.rewrite_offset
>= store_digest
->mask_size
)
484 storeDigestRewriteFinish(e
);
486 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep
, data
, 0.0, 1, false);
490 storeDigestCBlockSwapOut(StoreEntry
* e
)
492 memset(&sd_state
.cblock
, 0, sizeof(sd_state
.cblock
));
493 sd_state
.cblock
.ver
.current
= htons(CacheDigestVer
.current
);
494 sd_state
.cblock
.ver
.required
= htons(CacheDigestVer
.required
);
495 sd_state
.cblock
.capacity
= htonl(store_digest
->capacity
);
496 sd_state
.cblock
.count
= htonl(store_digest
->count
);
497 sd_state
.cblock
.del_count
= htonl(store_digest
->del_count
);
498 sd_state
.cblock
.mask_size
= htonl(store_digest
->mask_size
);
499 sd_state
.cblock
.bits_per_entry
= (unsigned char)
500 Config
.digest
.bits_per_entry
;
501 sd_state
.cblock
.hash_func_count
= (unsigned char) CacheDigestHashFuncCount
;
502 e
->append((char *) &sd_state
.cblock
, sizeof(sd_state
.cblock
));
505 /* calculates digest capacity */
507 storeDigestCalcCap(void)
510 * To-Do: Bloom proved that the optimal filter utilization is 50% (half of
511 * the bits are off). However, we do not have a formula to calculate the
512 * number of _entries_ we want to pre-allocate for.
514 const int hi_cap
= Store::Root().maxSize() / Config
.Store
.avgObjectSize
;
515 const int lo_cap
= 1 + Store::Root().currentSize() / Config
.Store
.avgObjectSize
;
516 const int e_count
= StoreEntry::inUseCount();
517 int cap
= e_count
? e_count
:hi_cap
;
518 debugs(71, 2, "storeDigestCalcCap: have: " << e_count
<< ", want " << cap
<<
519 " entries; limits: [" << lo_cap
<< ", " << hi_cap
<< "]");
524 /* do not enforce hi_cap limit, average-based estimation may be wrong
531 /* returns true if we actually resized the digest */
533 storeDigestResize(void)
535 const int cap
= storeDigestCalcCap();
537 assert(store_digest
);
538 diff
= abs(cap
- store_digest
->capacity
);
539 debugs(71, 2, "storeDigestResize: " <<
540 store_digest
->capacity
<< " -> " << cap
<< "; change: " <<
541 diff
<< " (" << xpercentInt(diff
, store_digest
->capacity
) << "%)" );
542 /* avoid minor adjustments */
544 if (diff
<= store_digest
->capacity
/ 10) {
545 debugs(71, 2, "storeDigestResize: small change, will not resize.");
548 debugs(71, 2, "storeDigestResize: big change, resizing.");
549 cacheDigestChangeCap(store_digest
, cap
);
554 #endif /* USE_CACHE_DIGESTS */