3 * $Id: refresh.cc,v 1.76 2007/05/24 01:45:03 hno Exp $
5 * DEBUG: section 22 Refresh Calculation
6 * AUTHOR: Harvest Derived
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
36 #ifndef USE_POSIX_REGEX
37 #define USE_POSIX_REGEX /* put before includes; always use POSIX */
41 #include "CacheManager.h"
43 #include "MemObject.h"
44 #include "HttpRequest.h"
45 #include "HttpReply.h"
46 #include "SquidTime.h"
70 * This enumerated list assigns specific values, ala HTTP/FTP status
71 * codes. All Fresh codes are in the range 100-199 and all stale
72 * codes are 200-299. We might want to use these codes in logging,
73 * so best to keep them consistent over time.
76 FRESH_REQUEST_MAX_STALE_ALL
= 100,
77 FRESH_REQUEST_MAX_STALE_VALUE
,
81 FRESH_OVERRIDE_EXPIRES
,
82 FRESH_OVERRIDE_LASTMOD
,
83 STALE_MUST_REVALIDATE
= 200,
84 STALE_RELOAD_INTO_IMS
,
86 STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE
,
93 static struct RefreshCounts
97 int status
[STALE_DEFAULT
+ 1];
100 refreshCounts
[rcCount
];
108 #define REFRESH_DEFAULT_MIN (time_t)0
109 #define REFRESH_DEFAULT_PCT 0.20
110 #define REFRESH_DEFAULT_MAX (time_t)259200
112 static const refresh_t
*refreshUncompiledPattern(const char *);
113 static OBJH refreshStats
;
114 static int refreshStaleness(const StoreEntry
*, time_t, time_t, const refresh_t
*, stale_flags
*);
116 static refresh_t DefaultRefresh
;
119 refreshLimits(const char *url
)
123 for (R
= Config
.Refresh
; R
; R
= R
->next
) {
124 if (!regexec(&(R
->compiled_pattern
), url
, 0, 0, 0))
131 static const refresh_t
*
132 refreshUncompiledPattern(const char *pat
)
136 for (R
= Config
.Refresh
; R
; R
= R
->next
) {
137 if (0 == strcmp(R
->pattern
, pat
))
145 * Calculate how stale the response is (or will be at the check_time).
146 * Staleness calculation is based on the following: (1) response
147 * expiration time, (2) age greater than configured maximum, (3)
148 * last-modified factor, and (4) age less than configured minimum.
150 * If the response is fresh, return -1. Otherwise return its
151 * staleness. NOTE return value of 0 means the response is stale.
153 * The 'stale_flags' structure is used to tell the calling function
154 * _why_ this response is fresh or stale. Its used, for example,
155 * when the admin wants to override expiration and last-modified
159 refreshStaleness(const StoreEntry
* entry
, time_t check_time
, time_t age
, const refresh_t
* R
, stale_flags
* sf
)
162 * Check for an explicit expiration time.
165 if (entry
->expires
> -1) {
168 if (entry
->expires
> check_time
) {
169 debugs(22, 3, "FRESH: expires " << entry
->expires
<<
170 " >= check_time " << check_time
<< " ");
174 debugs(22, 3, "STALE: expires " << entry
->expires
<<
175 " < check_time " << check_time
<< " ");
177 return (check_time
- entry
->expires
);
183 * Use local heuristics to determine staleness. Start with the
184 * max age from the refresh_pattern rule.
188 debugs(22, 3, "STALE: age " << age
<< " > max " << R
->max
<< " ");
190 return (age
- R
->max
);
194 * Try the last-modified factor algorithm.
196 if (entry
->lastmod
> -1 && entry
->timestamp
> entry
->lastmod
) {
198 * stale_age is the Age of the response when it became/becomes
199 * stale according to the last-modified factor algorithm.
201 time_t stale_age
= static_cast<time_t>((entry
->timestamp
- entry
->lastmod
) * R
->pct
);
204 if (age
>= stale_age
) {
205 debugs(22, 3, "STALE: age " << age
<< " > stale_age " << stale_age
);
206 return (age
- stale_age
);
208 debugs(22, 3, "FRESH: age " << age
<< " <= stale_age " << stale_age
);
214 * If we are here, staleness is determined by the refresh_pattern
215 * configured minimum age.
218 debugs(22, 3, "FRESH: age " << age
<< " < min " << R
->min
);
223 debugs(22, 3, "STALE: age " << age
<< " >= min " << R
->min
);
224 return (age
- R
->min
);
227 /* return 1 if the entry must be revalidated within delta seconds
230 * note: request maybe null (e.g. for cache digests build)
233 refreshCheck(const StoreEntry
* entry
, HttpRequest
* request
, time_t delta
)
236 const char *uri
= NULL
;
238 time_t check_time
= squid_curtime
+ delta
;
243 uri
= entry
->mem_obj
->url
;
245 uri
= urlCanonical(request
);
247 debugs(22, 3, "refreshCheck: '" << (uri
? uri
: "<none>") << "'");
249 if (check_time
> entry
->timestamp
)
250 age
= check_time
- entry
->timestamp
;
252 R
= uri
? refreshLimits(uri
) : refreshUncompiledPattern(".");
257 memset(&sf
, '\0', sizeof(sf
));
259 staleness
= refreshStaleness(entry
, check_time
, age
, R
, &sf
);
261 debugs(22, 3, "Staleness = " << staleness
);
263 debugs(22, 3, "refreshCheck: Matched '" << R
->pattern
<< " " <<
264 (int) R
->min
<< " " << (int) (100.0 * R
->pct
) << "%% " <<
265 (int) R
->max
<< "'");
268 debugs(22, 3, "refreshCheck: age = " << age
);
270 debugs(22, 3, "\tcheck_time:\t" << mkrfc1123(check_time
));
272 debugs(22, 3, "\tentry->timestamp:\t" << mkrfc1123(entry
->timestamp
));
274 if (EBIT_TEST(entry
->flags
, ENTRY_REVALIDATE
) && staleness
> -1) {
275 debugs(22, 3, "refreshCheck: YES: Must revalidate stale response");
276 return STALE_MUST_REVALIDATE
;
279 /* request-specific checks */
281 HttpHdrCc
*cc
= request
->cache_control
;
283 if (request
->flags
.ims
&& (R
->flags
.refresh_ims
|| Config
.onoff
.refresh_all_ims
)) {
284 /* The clients no-cache header is changed into a IMS query */
285 debugs(22, 3, "refreshCheck: YES: refresh-ims");
286 return STALE_FORCED_RELOAD
;
291 if (!request
->flags
.nocache_hack
) {
293 } else if (R
->flags
.ignore_reload
) {
294 /* The clients no-cache header is ignored */
295 debugs(22, 3, "refreshCheck: MAYBE: ignore-reload");
296 } else if (R
->flags
.reload_into_ims
|| Config
.onoff
.reload_into_ims
) {
297 /* The clients no-cache header is changed into a IMS query */
298 debugs(22, 3, "refreshCheck: YES: reload-into-ims");
299 return STALE_RELOAD_INTO_IMS
;
301 /* The clients no-cache header is not overridden on this request */
302 debugs(22, 3, "refreshCheck: YES: client reload");
303 request
->flags
.nocache
= 1;
304 return STALE_FORCED_RELOAD
;
309 if (cc
->max_age
> -1) {
311 if (R
->flags
.ignore_reload
&& cc
->max_age
== 0) {} else
316 if (cc
->max_age
== 0) {
317 debugs(22, 3, "refreshCheck: YES: client-max-age = 0");
318 return STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE
;
322 if (age
> cc
->max_age
) {
323 debugs(22, 3, "refreshCheck: YES: age > client-max-age");
324 return STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE
;
329 if (EBIT_TEST(cc
->mask
, CC_MAX_STALE
) && staleness
> -1) {
330 if (cc
->max_stale
< 0) {
331 /* max-stale directive without a value */
332 debugs(22, 3, "refreshCheck: NO: max-stale wildcard");
333 return FRESH_REQUEST_MAX_STALE_ALL
;
334 } else if (staleness
< cc
->max_stale
) {
335 debugs(22, 3, "refreshCheck: NO: staleness < max-stale");
336 return FRESH_REQUEST_MAX_STALE_VALUE
;
342 if (-1 == staleness
) {
343 debugs(22, 3, "refreshCheck: object isn't stale..");
345 debugs(22, 3, "refreshCheck: returning FRESH_EXPIRES");
346 return FRESH_EXPIRES
;
352 debugs(22, 3, "refreshCheck: returning FRESH_LMFACTOR_RULE");
353 return FRESH_LMFACTOR_RULE
;
358 debugs(22, 3, "refreshCheck: returning FRESH_MIN_RULE");
359 return FRESH_MIN_RULE
;
363 * At this point the response is stale, unless one of
364 * the override options kicks in.
369 if (R
->flags
.override_expire
&& age
< R
->min
) {
370 debugs(22, 3, "refreshCheck: NO: age < min && override-expire");
371 return FRESH_OVERRIDE_EXPIRES
;
375 return STALE_EXPIRES
;
379 return STALE_MAX_RULE
;
384 if (R
->flags
.override_lastmod
&& age
< R
->min
) {
385 debugs(22, 3, "refreshCheck: NO: age < min && override-lastmod");
386 return FRESH_OVERRIDE_LASTMOD
;
390 return STALE_LMFACTOR_RULE
;
393 debugs(22, 3, "refreshCheck: returning STALE_DEFAULT");
394 return STALE_DEFAULT
;
398 refreshIsCachable(const StoreEntry
* entry
)
401 * Don't look at the request to avoid no-cache and other nuisances.
402 * the object should have a mem_obj so the URL will be found there.
403 * minimum_expiry_time seconds delta (defaults to 60 seconds), to
404 * avoid objects which expire almost immediately, and which can't
407 int reason
= refreshCheck(entry
, NULL
, Config
.minimum_expiry_time
);
408 refreshCounts
[rcStore
].total
++;
409 refreshCounts
[rcStore
].status
[reason
]++;
411 if (reason
< STALE_MUST_REVALIDATE
)
412 /* Does not need refresh. This is certainly cachable */
415 if (entry
->lastmod
< 0)
416 /* Last modified is needed to do a refresh */
419 if (entry
->mem_obj
== NULL
)
423 if (entry
->getReply() == NULL
)
427 if (entry
->getReply()->content_length
== 0)
428 /* No use refreshing (caching?) 0 byte objects */
431 /* This seems to be refreshable. Cache it */
435 /* refreshCheck... functions below are protocol-specific wrappers around
436 * refreshCheck() function above */
439 refreshCheckHTTP(const StoreEntry
* entry
, HttpRequest
* request
)
441 int reason
= refreshCheck(entry
, request
, 0);
442 refreshCounts
[rcHTTP
].total
++;
443 refreshCounts
[rcHTTP
].status
[reason
]++;
444 return (reason
< 200) ? 0 : 1;
448 refreshCheckICP(const StoreEntry
* entry
, HttpRequest
* request
)
450 int reason
= refreshCheck(entry
, request
, 30);
451 refreshCounts
[rcICP
].total
++;
452 refreshCounts
[rcICP
].status
[reason
]++;
453 return (reason
< 200) ? 0 : 1;
458 refreshCheckHTCP(const StoreEntry
* entry
, HttpRequest
* request
)
460 int reason
= refreshCheck(entry
, request
, 10);
461 refreshCounts
[rcHTCP
].total
++;
462 refreshCounts
[rcHTCP
].status
[reason
]++;
463 return (reason
< 200) ? 0 : 1;
468 #if USE_CACHE_DIGESTS
470 refreshCheckDigest(const StoreEntry
* entry
, time_t delta
)
472 int reason
= refreshCheck(entry
,
473 entry
->mem_obj
? entry
->mem_obj
->request
: NULL
,
475 refreshCounts
[rcCDigest
].total
++;
476 refreshCounts
[rcCDigest
].status
[reason
]++;
477 return (reason
< 200) ? 0 : 1;
483 getMaxAge(const char *url
)
486 debugs(22, 3, "getMaxAge: '" << url
<< "'");
488 if ((R
= refreshLimits(url
)))
491 return REFRESH_DEFAULT_MAX
;
496 refreshCountsStats(StoreEntry
* sentry
, struct RefreshCounts
*rc
)
501 storeAppendPrintf(sentry
, "\n\n%s histogram:\n", rc
->proto
);
502 storeAppendPrintf(sentry
, "Count\t%%Total\tCategory\n");
504 #define refreshCountsStatsEntry(code,desc) { \
505 storeAppendPrintf(sentry, "%6d\t%6.2f\t%s\n", \
506 rc->status[code], xpercent(rc->status[code], tot), desc); \
507 sum += rc->status[code]; \
510 refreshCountsStatsEntry(FRESH_REQUEST_MAX_STALE_ALL
,
511 "Fresh: request max-stale wildcard");
512 refreshCountsStatsEntry(FRESH_REQUEST_MAX_STALE_VALUE
,
513 "Fresh: request max-stale value");
514 refreshCountsStatsEntry(FRESH_EXPIRES
,
515 "Fresh: expires time not reached");
516 refreshCountsStatsEntry(FRESH_LMFACTOR_RULE
,
517 "Fresh: refresh_pattern last-mod factor percentage");
518 refreshCountsStatsEntry(FRESH_MIN_RULE
,
519 "Fresh: refresh_pattern min value");
520 refreshCountsStatsEntry(FRESH_OVERRIDE_EXPIRES
,
521 "Fresh: refresh_pattern override expires");
522 refreshCountsStatsEntry(FRESH_OVERRIDE_LASTMOD
,
523 "Fresh: refresh_pattern override lastmod");
524 refreshCountsStatsEntry(STALE_MUST_REVALIDATE
,
525 "Stale: response has must-revalidate");
526 refreshCountsStatsEntry(STALE_RELOAD_INTO_IMS
,
527 "Stale: changed reload into IMS");
528 refreshCountsStatsEntry(STALE_FORCED_RELOAD
,
529 "Stale: request has no-cache directive");
530 refreshCountsStatsEntry(STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE
,
531 "Stale: age exceeds request max-age value");
532 refreshCountsStatsEntry(STALE_EXPIRES
,
533 "Stale: expires time reached");
534 refreshCountsStatsEntry(STALE_MAX_RULE
,
535 "Stale: refresh_pattern max age rule");
536 refreshCountsStatsEntry(STALE_LMFACTOR_RULE
,
537 "Stale: refresh_pattern last-mod factor percentage");
538 refreshCountsStatsEntry(STALE_DEFAULT
,
539 "Stale: by default");
541 tot
= sum
; /* paranoid: "total" line shows 100% if we forgot nothing */
542 storeAppendPrintf(sentry
, "%6d\t%6.2f\tTOTAL\n",
543 rc
->total
, xpercent(rc
->total
, tot
));
545 storeAppendPrintf(sentry
, "\n");
549 refreshStats(StoreEntry
* sentry
)
554 /* get total usage count */
556 for (i
= 0; i
< rcCount
; ++i
)
557 total
+= refreshCounts
[i
].total
;
559 /* protocol usage histogram */
560 storeAppendPrintf(sentry
, "\nRefreshCheck calls per protocol\n\n");
562 storeAppendPrintf(sentry
, "Protocol\t#Calls\t%%Calls\n");
564 for (i
= 0; i
< rcCount
; ++i
)
565 storeAppendPrintf(sentry
, "%10s\t%6d\t%6.2f\n",
566 refreshCounts
[i
].proto
,
567 refreshCounts
[i
].total
,
568 xpercent(refreshCounts
[i
].total
, total
));
570 /* per protocol histograms */
571 storeAppendPrintf(sentry
, "\n\nRefreshCheck histograms for various protocols\n");
573 for (i
= 0; i
< rcCount
; ++i
)
574 refreshCountsStats(sentry
, &refreshCounts
[i
]);
578 refreshRegisterWithCacheManager(void)
580 CacheManager::GetInstance()->
581 registerAction("refresh", "Refresh Algorithm Statistics", refreshStats
, 0, 1);
587 memset(refreshCounts
, 0, sizeof(refreshCounts
));
588 refreshCounts
[rcHTTP
].proto
= "HTTP";
589 refreshCounts
[rcICP
].proto
= "ICP";
592 refreshCounts
[rcHTCP
].proto
= "HTCP";
595 refreshCounts
[rcStore
].proto
= "On Store";
596 #if USE_CACHE_DIGESTS
598 refreshCounts
[rcCDigest
].proto
= "Cache Digests";
601 memset(&DefaultRefresh
, '\0', sizeof(DefaultRefresh
));
602 DefaultRefresh
.pattern
= "<none>";
603 DefaultRefresh
.min
= REFRESH_DEFAULT_MIN
;
604 DefaultRefresh
.pct
= REFRESH_DEFAULT_PCT
;
605 DefaultRefresh
.max
= REFRESH_DEFAULT_MAX
;
607 refreshRegisterWithCacheManager();