]> git.ipfire.org Git - thirdparty/squid.git/blob - src/refresh.cc
Maintenance: Removed most NULLs using modernize-use-nullptr (#1075)
[thirdparty/squid.git] / src / refresh.cc
1 /*
2 * Copyright (C) 1996-2022 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 22 Refresh Calculation */
10
11 #ifndef USE_POSIX_REGEX
12 #define USE_POSIX_REGEX /* put before includes; always use POSIX */
13 #endif
14
15 #include "squid.h"
16 #include "base/PackableStream.h"
17 #include "HttpHdrCc.h"
18 #include "HttpReply.h"
19 #include "HttpRequest.h"
20 #include "MemObject.h"
21 #include "mgr/Registration.h"
22 #include "refresh.h"
23 #include "RefreshPattern.h"
24 #include "SquidConfig.h"
25 #include "Store.h"
26 #include "util.h"
27
28 typedef enum {
29 rcHTTP,
30 rcICP,
31 #if USE_HTCP
32 rcHTCP,
33 #endif
34 #if USE_CACHE_DIGESTS
35 rcCDigest,
36 #endif
37 rcStore,
38 rcCount
39 } refreshCountsEnum;
40
41 /**
42 * Flags indicating which staleness algorithm has been applied.
43 */
44 typedef struct {
45 bool expires; ///< Expires: header absolute timestamp limit
46 bool min; ///< Heuristic minimum age limited
47 bool lmfactor; ///< Last-Modified with heuristic determines limit
48 bool max; ///< Configured maximum age limit
49 } stale_flags;
50
51 /*
52 * This enumerated list assigns specific values, ala HTTP/FTP status
53 * codes. All Fresh codes are in the range 100-199 and all stale
54 * codes are 200-299. We might want to use these codes in logging,
55 * so best to keep them consistent over time.
56 */
57 enum {
58 FRESH_REQUEST_MAX_STALE_ALL = 100,
59 FRESH_REQUEST_MAX_STALE_VALUE,
60 FRESH_EXPIRES,
61 FRESH_LMFACTOR_RULE,
62 FRESH_MIN_RULE,
63 FRESH_OVERRIDE_EXPIRES,
64 FRESH_OVERRIDE_LASTMOD,
65 STALE_MUST_REVALIDATE = 200,
66 STALE_RELOAD_INTO_IMS,
67 STALE_FORCED_RELOAD,
68 STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE,
69 STALE_EXPIRES,
70 STALE_MAX_RULE,
71 STALE_LMFACTOR_RULE,
72 STALE_MAX_STALE,
73 STALE_DEFAULT = 299
74 };
75
76 static struct RefreshCounts {
77 const char *proto;
78 int total;
79 int status[STALE_DEFAULT + 1];
80 } refreshCounts[rcCount];
81
82 static OBJH refreshStats;
83 static int refreshStaleness(const StoreEntry * entry, time_t check_time, const time_t age, const RefreshPattern * R, stale_flags * sf);
84
85 static RefreshPattern DefaultRefresh(nullptr);
86
87 /** Locate the first refresh_pattern rule that matches the given URL by regex.
88 *
89 * \return A pointer to the refresh_pattern parameters to use, or nullptr if there is no match.
90 */
91 const RefreshPattern *
92 refreshLimits(const char *url)
93 {
94 for (auto R = Config.Refresh; R; R = R->next) {
95 ++(R->stats.matchTests);
96 if (R->regex().match(url)) {
97 ++(R->stats.matchCount);
98 return R;
99 }
100 }
101
102 return nullptr;
103 }
104
105 /// the first explicit refresh_pattern rule that uses a "." regex (or nil)
106 static const RefreshPattern *
107 refreshFirstDotRule()
108 {
109 for (auto R = Config.Refresh; R; R = R->next) {
110 if (R->regex().isDot())
111 return R;
112 }
113
114 return nullptr;
115 }
116
117 /**
118 * Calculate how stale the response is (or will be at the check_time).
119 *
120 * We try the following ways until one gives a result:
121 *
122 * 1. response expiration time, if one was set
123 * 2. age greater than configured maximum
124 * 3. last-modified factor algorithm
125 * 4. age less than configured minimum
126 * 5. default (stale)
127 *
128 * \param entry the StoreEntry being examined
129 * \param check_time the time (maybe future) at which we want to know whether $
130 * \param age the age of the entry at check_time
131 * \param R the refresh_pattern rule that matched this entry
132 * \param sf small struct to indicate reason for stale/fresh decision
133 *
134 * \retval -1 If the response is fresh.
135 * \retval >0 The amount of staleness.
136 * \retval 0 NOTE return value of 0 means the response is stale.
137 */
138 static int
139 refreshStaleness(const StoreEntry * entry, time_t check_time, const time_t age, const RefreshPattern * R, stale_flags * sf)
140 {
141 // 1. If the cached object has an explicit expiration time, then we rely on this and
142 // completely ignore the Min, Percent and Max values in the refresh_pattern.
143 if (entry->expires > -1) {
144 sf->expires = true;
145
146 if (entry->expires > check_time) {
147 debugs(22, 3, "FRESH: expires " << entry->expires <<
148 " >= check_time " << check_time << " ");
149
150 return -1;
151 } else {
152 debugs(22, 3, "STALE: expires " << entry->expires <<
153 " < check_time " << check_time << " ");
154
155 return (check_time - entry->expires);
156 }
157 }
158
159 debugs(22, 3, "No explicit expiry given, using heuristics to determine freshness");
160
161 // 2. If the entry is older than the maximum age in the refresh_pattern, it is STALE.
162 if (age > R->max) {
163 debugs(22, 3, "STALE: age " << age << " > max " << R->max << " ");
164 sf->max = true;
165 return (age - R->max);
166 }
167
168 // 3. If there is a Last-Modified header, try the last-modified factor algorithm.
169 const time_t lastmod_delta = entry->timestamp - entry->lastModified();
170 if (lastmod_delta > 0) {
171 /* stale_age is the age of the response when it became/becomes stale according to
172 * the last-modified factor algorithm. It's how long we can consider the response
173 * fresh from the time we cached it.
174 */
175 time_t stale_age = static_cast<time_t>(lastmod_delta * R->pct);
176
177 debugs(22,3, "Last modified " << lastmod_delta << " sec before we cached it, L-M factor " <<
178 (100.0 * R->pct) << "% = " << stale_age << " sec freshness lifetime");
179 sf->lmfactor = true;
180
181 if (age >= stale_age) {
182 debugs(22, 3, "STALE: age " << age << " > stale_age " << stale_age);
183 return (age - stale_age);
184 } else {
185 debugs(22, 3, "FRESH: age " << age << " <= stale_age " << stale_age);
186 return -1;
187 }
188 }
189
190 // 4. If the entry is not as old as the minimum age in the refresh_pattern, it is FRESH.
191 if (age < R->min) {
192 debugs(22, 3, "FRESH: age (" << age << " sec) is less than configured minimum (" << R->min << " sec)");
193 sf->min = true;
194 return -1;
195 }
196
197 // 5. default is stale, by the amount we missed the minimum by
198 debugs(22, 3, "STALE: No explicit expiry, no last modified, and older than configured minimum.");
199 return (age - R->min);
200 }
201
202 /** Checks whether a store entry is fresh or stale, and why.
203 *
204 * This is where all aspects of request, response and squid configuration
205 * meet to decide whether a response is cacheable or not:
206 *
207 * 1. Client request headers that affect cacheability, e.g.
208 * - Cache-Control: no-cache
209 * - Cache-Control: max-age=N
210 * - Cache-Control: max-stale[=N]
211 * - Pragma: no-cache
212 *
213 * 2. Server response headers that affect cacheability, e.g.
214 * - Age:
215 * - Cache-Control: proxy-revalidate
216 * - Cache-Control: must-revalidate
217 * - Cache-Control: no-cache
218 * - Cache-Control: max-age=N
219 * - Cache-Control: s-maxage=N
220 * - Date:
221 * - Expires:
222 * - Last-Modified:
223 *
224 * 3. Configuration options, e.g.
225 * - reload-into-ims (refresh_pattern)
226 * - ignore-reload (refresh_pattern)
227 * - refresh-ims (refresh_pattern)
228 * - override-lastmod (refresh_pattern)
229 * - override-expire (refresh_pattern)
230 * - reload_into_ims (global option)
231 * - refresh_all_ims (global option)
232 *
233 * \returns a status code (from enum above):
234 * - FRESH_REQUEST_MAX_STALE_ALL
235 * - FRESH_REQUEST_MAX_STALE_VALUE
236 * - FRESH_EXPIRES
237 * - FRESH_LMFACTOR_RULE
238 * - FRESH_MIN_RULE
239 * - FRESH_OVERRIDE_EXPIRES
240 * - FRESH_OVERRIDE_LASTMOD
241 * - STALE_MUST_REVALIDATE
242 * - STALE_RELOAD_INTO_IMS
243 * - STALE_FORCED_RELOAD
244 * - STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE
245 * - STALE_EXPIRES
246 * - STALE_MAX_RULE
247 * - STALE_LMFACTOR_RULE
248 * - STALE_MAX_STALE
249 * - STALE_DEFAULT
250 *
251 * \note request may be NULL (e.g. for cache digests build)
252 *
253 * \note the store entry being examined is not necessarily cached (e.g. if
254 * this response is being evaluated for the first time)
255 */
256 static int
257 refreshCheck(const StoreEntry * entry, HttpRequest * request, time_t delta)
258 {
259 time_t age = 0;
260 time_t check_time = squid_curtime + delta;
261 int staleness;
262 stale_flags sf;
263
264 // get the URL of this entry, if there is one
265 static const SBuf nilUri("<none>");
266 SBuf uri = nilUri;
267 if (entry->mem_obj)
268 uri = entry->mem_obj->storeId();
269 else if (request)
270 uri = request->effectiveRequestUri();
271
272 debugs(22, 3, "checking freshness of URI: " << uri);
273
274 // age is not necessarily the age now, but the age at the given check_time
275 if (check_time > entry->timestamp)
276 age = check_time - entry->timestamp;
277
278 // XXX: what to do when age < 0 or counter overflow?
279 assert(age >= 0);
280
281 /* We need a refresh rule. In order of preference:
282 *
283 * 1. the rule that matches this URI by regex
284 * 2. the "." rule from the config file
285 * 3. the default "." rule
286 */
287 // XXX: performance regression. c_str() reallocates
288 const RefreshPattern *R = (uri != nilUri) ? refreshLimits(uri.c_str()) : refreshFirstDotRule();
289 if (nullptr == R)
290 R = &DefaultRefresh;
291
292 debugs(22, 3, "Matched '" << *R << '\'');
293
294 debugs(22, 3, "\tage:\t" << age);
295
296 debugs(22, 3, "\tcheck_time:\t" << Time::FormatRfc1123(check_time));
297
298 debugs(22, 3, "\tentry->timestamp:\t" << Time::FormatRfc1123(entry->timestamp));
299
300 if (request && !request->flags.ignoreCc) {
301 const HttpHdrCc *const cc = request->cache_control;
302 int minFresh = -1;
303 if (cc && cc->hasMinFresh(&minFresh)) {
304 debugs(22, 3, "\tage + min-fresh:\t" << age << " + " <<
305 minFresh << " = " << age + minFresh);
306 debugs(22, 3, "\tcheck_time + min-fresh:\t" << check_time << " + "
307 << minFresh << " = " <<
308 Time::FormatRfc1123(check_time + minFresh));
309 age += minFresh;
310 check_time += minFresh;
311 }
312 }
313
314 memset(&sf, '\0', sizeof(sf));
315
316 staleness = refreshStaleness(entry, check_time, age, R, &sf);
317
318 debugs(22, 3, "Staleness = " << staleness);
319
320 const auto reply = entry->hasFreshestReply(); // may be nil
321
322 // stale-if-error requires any failure be passed thru when its period is over.
323 int staleIfError = -1;
324 if (request && reply && reply->cache_control &&
325 reply->cache_control->hasStaleIfError(&staleIfError) &&
326 staleIfError < staleness) {
327
328 debugs(22, 3, "stale-if-error period expired. Will produce error if validation fails.");
329 request->flags.failOnValidationError = true;
330 }
331
332 /* If the origin server specified either of:
333 * Cache-Control: must-revalidate
334 * Cache-Control: proxy-revalidate
335 * the spec says the response must always be revalidated if stale.
336 */
337 const bool revalidateAlways = EBIT_TEST(entry->flags, ENTRY_REVALIDATE_ALWAYS);
338 if (revalidateAlways || (staleness > -1 &&
339 EBIT_TEST(entry->flags, ENTRY_REVALIDATE_STALE))) {
340 debugs(22, 3, "YES: Must revalidate stale object (origin set " <<
341 (revalidateAlways ? "no-cache or private" :
342 "must-revalidate, proxy-revalidate or s-maxage") << ")");
343 if (request)
344 request->flags.failOnValidationError = true;
345 return STALE_MUST_REVALIDATE;
346 }
347
348 /* request-specific checks */
349 if (request && !request->flags.ignoreCc) {
350 HttpHdrCc *cc = request->cache_control;
351
352 /* If the request is an IMS request, and squid is configured NOT to service this from cache
353 * (either by 'refresh-ims' in the refresh pattern or 'refresh_all_ims on' globally)
354 * then force a reload from the origin.
355 */
356 if (request->flags.ims && (R->flags.refresh_ims || Config.onoff.refresh_all_ims)) {
357 // The client's no-cache header is changed into a IMS query
358 debugs(22, 3, "YES: Client IMS request forcing revalidation of object (refresh-ims option)");
359 return STALE_FORCED_RELOAD;
360 }
361
362 #if USE_HTTP_VIOLATIONS
363 /* Normally a client reload request ("Cache-Control: no-cache" or "Pragma: no-cache")
364 * means we must treat this response as STALE and fetch a new one.
365 *
366 * However, some options exist to override this behaviour. For example, we might just
367 * revalidate our existing response, or even just serve it up without revalidating it.
368 *
369 * ---- Note on the meaning of nocache_hack -----
370 *
371 * The nocache_hack flag has a very specific and complex meaning:
372 *
373 * (a) this is a reload request ("Cache-Control: no-cache" or "Pragma: no-cache" header)
374 * and (b) the configuration file either has at least one refresh_pattern with
375 * ignore-reload or reload-into-ims (not necessarily the rule matching this request) or
376 * the global reload_into_ims is set to on
377 *
378 * In other words: this is a client reload, and we might need to override
379 * the default behaviour (but we might not).
380 *
381 * "nocache_hack" is a pretty deceptive name for such a complicated meaning.
382 */
383 if (request->flags.noCacheHack()) {
384
385 if (R->flags.ignore_reload) {
386 /* The client's no-cache header is ignored completely - we'll try to serve
387 * what we have (assuming it's still fresh, etc.)
388 */
389 debugs(22, 3, "MAYBE: Ignoring client reload request - trying to serve from cache (ignore-reload option)");
390 } else if (R->flags.reload_into_ims || Config.onoff.reload_into_ims) {
391 /* The client's no-cache header is not honoured completely - we'll just try
392 * to revalidate our cached copy (IMS to origin) instead of fetching a new
393 * copy with an unconditional GET.
394 */
395 debugs(22, 3, "YES: Client reload request - cheating, only revalidating with origin (reload-into-ims option)");
396 return STALE_RELOAD_INTO_IMS;
397 } else {
398 /* The client's no-cache header is honoured - we fetch a new copy from origin */
399 debugs(22, 3, "YES: Client reload request - fetching new copy from origin");
400 request->flags.noCache = true;
401 return STALE_FORCED_RELOAD;
402 }
403 }
404 #endif
405
406 // Check the Cache-Control client request header
407 if (nullptr != cc) {
408
409 // max-age directive
410 int maxAge = -1;
411 if (cc->hasMaxAge(&maxAge)) {
412
413 // RFC 8246: reply contains CC:immutable then ignore client CC:max-age=N
414 if (reply && reply->cache_control && reply->cache_control->hasImmutable()) {
415 debugs(22, 3, "MAYBE: Ignoring client CC:max-age=" << maxAge << " request - 'Cache-Control: immutable'");
416
417 #if USE_HTTP_VIOLATIONS
418 // Ignore of client "Cache-Control: max-age=0" header
419 } else if (R->flags.ignore_reload && maxAge == 0) {
420 debugs(22, 3, "MAYBE: Ignoring client reload request - trying to serve from cache (ignore-reload option)");
421 #endif
422
423 // Honour client "Cache-Control: max-age=x" header
424 } else if (age > maxAge || maxAge == 0) {
425 debugs(22, 3, "YES: Revalidating object - client 'Cache-Control: max-age=" << maxAge << "'");
426 return STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE;
427 }
428 }
429
430 // max-stale directive
431 int maxStale = -1;
432 if (cc->hasMaxStale(&maxStale) && staleness > -1) {
433 if (maxStale==HttpHdrCc::MAX_STALE_ANY) {
434 debugs(22, 3, "NO: Client accepts a stale response of any age - 'Cache-Control: max-stale'");
435 return FRESH_REQUEST_MAX_STALE_ALL;
436 } else if (staleness < maxStale) {
437 debugs(22, 3, "NO: Client accepts a stale response - 'Cache-Control: max-stale=" << maxStale << "'");
438 return FRESH_REQUEST_MAX_STALE_VALUE;
439 }
440 }
441 }
442 }
443
444 // If the object is fresh, return the right FRESH_ code
445 if (-1 == staleness) {
446 debugs(22, 3, "Object isn't stale..");
447 if (sf.expires) {
448 debugs(22, 3, "returning FRESH_EXPIRES");
449 return FRESH_EXPIRES;
450 }
451
452 assert(!sf.max);
453
454 if (sf.lmfactor) {
455 debugs(22, 3, "returning FRESH_LMFACTOR_RULE");
456 return FRESH_LMFACTOR_RULE;
457 }
458
459 assert(sf.min);
460
461 debugs(22, 3, "returning FRESH_MIN_RULE");
462 return FRESH_MIN_RULE;
463 }
464
465 /*
466 * At this point the response is stale, unless one of
467 * the override options kicks in.
468 * NOTE: max-stale config blocks the overrides.
469 */
470 int max_stale = (R->max_stale >= 0 ? R->max_stale : Config.maxStale);
471 if ( max_stale >= 0 && staleness > max_stale) {
472 debugs(22, 3, "YES: refresh_pattern max-stale=N limit from squid.conf");
473 if (request)
474 request->flags.failOnValidationError = true;
475 return STALE_MAX_STALE;
476 }
477
478 if (sf.expires) {
479 #if USE_HTTP_VIOLATIONS
480
481 if (R->flags.override_expire && age < R->min) {
482 debugs(22, 3, "NO: Serving from cache - even though explicit expiry has passed, we enforce Min value (override-expire option)");
483 return FRESH_OVERRIDE_EXPIRES;
484 }
485
486 #endif
487 return STALE_EXPIRES;
488 }
489
490 if (sf.max)
491 return STALE_MAX_RULE;
492
493 if (sf.lmfactor) {
494 #if USE_HTTP_VIOLATIONS
495 if (R->flags.override_lastmod && age < R->min) {
496 debugs(22, 3, "NO: Serving from cache - even though L-M factor says the object is stale, we enforce Min value (override-lastmod option)");
497 return FRESH_OVERRIDE_LASTMOD;
498 }
499 #endif
500 debugs(22, 3, "YES: L-M factor says the object is stale'");
501 return STALE_LMFACTOR_RULE;
502 }
503
504 debugs(22, 3, "returning STALE_DEFAULT");
505 return STALE_DEFAULT;
506 }
507
508 /**
509 * This is called by http.cc once it has received and parsed the origin server's
510 * response headers. It uses the result as part of its algorithm to decide whether a
511 * response should be cached.
512 *
513 * \retval true if the entry is cacheable, regardless of whether FRESH or STALE
514 * \retval false if the entry is not cacheable
515 *
516 * TODO: this algorithm seems a bit odd and might not be quite right. Verify against HTTPbis.
517 */
518 bool
519 refreshIsCachable(const StoreEntry * entry)
520 {
521 /*
522 * Don't look at the request to avoid no-cache and other nuisances.
523 * the object should have a mem_obj so the URL will be found there.
524 * minimum_expiry_time seconds delta (defaults to 60 seconds), to
525 * avoid objects which expire almost immediately, and which can't
526 * be refreshed.
527 */
528 int reason = refreshCheck(entry, nullptr, Config.minimum_expiry_time);
529 ++ refreshCounts[rcStore].total;
530 ++ refreshCounts[rcStore].status[reason];
531
532 if (reason < STALE_MUST_REVALIDATE)
533 /* Does not need refresh. This is certainly cachable */
534 return true;
535
536 if (entry->lastModified() < 0)
537 /* We should know entry's modification time to do a refresh */
538 return false;
539
540 if (entry->mem_obj == nullptr)
541 /* no mem_obj? */
542 return true;
543
544 if (entry->mem_obj->baseReply().content_length == 0)
545 /* No use refreshing (caching?) 0 byte objects */
546 return false;
547
548 /* This seems to be refreshable. Cache it */
549 return true;
550 }
551
552 /// whether reply is stale if it is a hit
553 static bool
554 refreshIsStaleIfHit(const int reason)
555 {
556 switch (reason) {
557 case FRESH_MIN_RULE:
558 case FRESH_LMFACTOR_RULE:
559 case FRESH_EXPIRES:
560 return false;
561 default:
562 return true;
563 }
564 }
565
566 /**
567 * Protocol-specific wrapper around refreshCheck() function.
568 *
569 * Note the reason for STALE/FRESH then return true/false respectively.
570 *
571 * \retval 1 if STALE
572 * \retval 0 if FRESH
573 */
574 int
575 refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
576 {
577 int reason = refreshCheck(entry, request, 0);
578 ++ refreshCounts[rcHTTP].total;
579 ++ refreshCounts[rcHTTP].status[reason];
580 request->flags.staleIfHit = refreshIsStaleIfHit(reason);
581 // TODO: Treat collapsed responses as fresh but second-hand.
582 return (Config.onoff.offline || reason < 200) ? 0 : 1;
583 }
584
585 /// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
586 int
587 refreshCheckICP(const StoreEntry * entry, HttpRequest * request)
588 {
589 int reason = refreshCheck(entry, request, 30);
590 ++ refreshCounts[rcICP].total;
591 ++ refreshCounts[rcICP].status[reason];
592 return (reason < 200) ? 0 : 1;
593 }
594
595 #if USE_HTCP
596 /// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
597 int
598 refreshCheckHTCP(const StoreEntry * entry, HttpRequest * request)
599 {
600 int reason = refreshCheck(entry, request, 10);
601 ++ refreshCounts[rcHTCP].total;
602 ++ refreshCounts[rcHTCP].status[reason];
603 return (reason < 200) ? 0 : 1;
604 }
605
606 #endif
607
608 #if USE_CACHE_DIGESTS
609 /// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
610 int
611 refreshCheckDigest(const StoreEntry * entry, time_t delta)
612 {
613 int reason = refreshCheck(entry,
614 entry->mem_obj ? entry->mem_obj->request.getRaw() : nullptr,
615 delta);
616 ++ refreshCounts[rcCDigest].total;
617 ++ refreshCounts[rcCDigest].status[reason];
618 return (reason < 200) ? 0 : 1;
619 }
620 #endif
621
622 /**
623 * Get the configured maximum caching time for objects with this URL
624 * according to refresh_pattern.
625 *
626 * Used by http.cc when generating a upstream requests to ensure that
627 * responses it is given are fresh enough to be worth caching.
628 *
629 * \retval pattern-max if there is a refresh_pattern matching the URL configured.
630 * \retval REFRESH_DEFAULT_MAX if there are no explicit limits configured
631 */
632 time_t
633 getMaxAge(const char *url)
634 {
635 const RefreshPattern *R;
636 debugs(22, 3, "getMaxAge: '" << url << "'");
637
638 if ((R = refreshLimits(url)))
639 return R->max;
640 else
641 return REFRESH_DEFAULT_MAX;
642 }
643
644 static int
645 refreshCountsStatsEntry(StoreEntry * sentry, struct RefreshCounts &rc, int code, const char *desc)
646 {
647 storeAppendPrintf(sentry, "%6d\t%6.2f\t%s\n", rc.status[code], xpercent(rc.status[code], rc.total), desc);
648 return rc.status[code];
649 }
650
651 static void
652 refreshCountsStats(StoreEntry * sentry, struct RefreshCounts &rc)
653 {
654 if (!rc.total)
655 return;
656
657 storeAppendPrintf(sentry, "\n\n%s histogram:\n", rc.proto);
658 storeAppendPrintf(sentry, "Count\t%%Total\tCategory\n");
659
660 int sum = 0;
661 sum += refreshCountsStatsEntry(sentry, rc, FRESH_REQUEST_MAX_STALE_ALL, "Fresh: request max-stale wildcard");
662 sum += refreshCountsStatsEntry(sentry, rc, FRESH_REQUEST_MAX_STALE_VALUE, "Fresh: request max-stale value");
663 sum += refreshCountsStatsEntry(sentry, rc, FRESH_EXPIRES, "Fresh: expires time not reached");
664 sum += refreshCountsStatsEntry(sentry, rc, FRESH_LMFACTOR_RULE, "Fresh: refresh_pattern last-mod factor percentage");
665 sum += refreshCountsStatsEntry(sentry, rc, FRESH_MIN_RULE, "Fresh: refresh_pattern min value");
666 sum += refreshCountsStatsEntry(sentry, rc, FRESH_OVERRIDE_EXPIRES, "Fresh: refresh_pattern override-expires");
667 sum += refreshCountsStatsEntry(sentry, rc, FRESH_OVERRIDE_LASTMOD, "Fresh: refresh_pattern override-lastmod");
668 sum += refreshCountsStatsEntry(sentry, rc, STALE_MUST_REVALIDATE, "Stale: response has must-revalidate");
669 sum += refreshCountsStatsEntry(sentry, rc, STALE_RELOAD_INTO_IMS, "Stale: changed reload into IMS");
670 sum += refreshCountsStatsEntry(sentry, rc, STALE_FORCED_RELOAD, "Stale: request has no-cache directive");
671 sum += refreshCountsStatsEntry(sentry, rc, STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE, "Stale: age exceeds request max-age value");
672 sum += refreshCountsStatsEntry(sentry, rc, STALE_EXPIRES, "Stale: expires time reached");
673 sum += refreshCountsStatsEntry(sentry, rc, STALE_MAX_RULE, "Stale: refresh_pattern max age rule");
674 sum += refreshCountsStatsEntry(sentry, rc, STALE_LMFACTOR_RULE, "Stale: refresh_pattern last-mod factor percentage");
675 sum += refreshCountsStatsEntry(sentry, rc, STALE_DEFAULT, "Stale: by default");
676 storeAppendPrintf(sentry, "\n");
677 }
678
679 static void
680 refreshStats(StoreEntry * sentry)
681 {
682 // display per-rule counts of usage and tests
683 storeAppendPrintf(sentry, "\nRefresh pattern usage:\n\n");
684 storeAppendPrintf(sentry, " Used \tChecks \t%% Matches\tPattern\n");
685 for (const RefreshPattern *R = Config.Refresh; R; R = R->next) {
686 storeAppendPrintf(sentry, " %10" PRIu64 "\t%10" PRIu64 "\t%6.2f\t",
687 R->stats.matchCount,
688 R->stats.matchTests,
689 xpercent(R->stats.matchCount, R->stats.matchTests));
690 PackableStream os(*sentry);
691 R->printPattern(os);
692 os << "\n";
693 }
694
695 int i;
696 int total = 0;
697
698 /* get total usage count */
699
700 for (i = 0; i < rcCount; ++i)
701 total += refreshCounts[i].total;
702
703 /* protocol usage histogram */
704 storeAppendPrintf(sentry, "\nRefreshCheck calls per protocol\n\n");
705
706 storeAppendPrintf(sentry, "Protocol\t#Calls\t%%Calls\n");
707
708 for (i = 0; i < rcCount; ++i)
709 storeAppendPrintf(sentry, "%10s\t%6d\t%6.2f\n",
710 refreshCounts[i].proto,
711 refreshCounts[i].total,
712 xpercent(refreshCounts[i].total, total));
713
714 /* per protocol histograms */
715 storeAppendPrintf(sentry, "\n\nRefreshCheck histograms for various protocols\n");
716
717 for (i = 0; i < rcCount; ++i)
718 refreshCountsStats(sentry, refreshCounts[i]);
719 }
720
721 const RegexPattern &
722 RefreshPattern::regex() const
723 {
724 assert(regex_);
725 return *regex_;
726 }
727
728 void
729 RefreshPattern::printPattern(std::ostream &os) const
730 {
731 if (regex_)
732 regex_->print(os, nullptr); // refresh lines do not inherit line flags
733 else
734 os << "<none>";
735 }
736
737 void
738 RefreshPattern::printHead(std::ostream &os) const
739 {
740 printPattern(os);
741 os <<
742 // these adjustments are safe: raw values were configured using integers
743 ' ' << intmax_t(min/60) << // to minutes
744 ' ' << intmax_t(100.0 * pct + 0.5) << '%' << // to percentage points
745 ' ' << intmax_t(max/60); // to minutes
746 }
747
748 static void
749 refreshRegisterWithCacheManager(void)
750 {
751 Mgr::RegisterAction("refresh", "Refresh Algorithm Statistics", refreshStats, 0, 1);
752 }
753
754 void
755 refreshInit(void)
756 {
757 memset(refreshCounts, 0, sizeof(refreshCounts));
758 refreshCounts[rcHTTP].proto = "HTTP";
759 refreshCounts[rcICP].proto = "ICP";
760 #if USE_HTCP
761
762 refreshCounts[rcHTCP].proto = "HTCP";
763 #endif
764
765 refreshCounts[rcStore].proto = "On Store";
766 #if USE_CACHE_DIGESTS
767
768 refreshCounts[rcCDigest].proto = "Cache Digests";
769 #endif
770
771 refreshRegisterWithCacheManager();
772 }
773