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