]> git.ipfire.org Git - thirdparty/squid.git/blame - src/test_cache_digest.cc
- Add median service times to cachemgr 'info' page.
[thirdparty/squid.git] / src / test_cache_digest.cc
CommitLineData
1afe05c5 1
c411be12 2/*
b2ab5fd1 3 * $Id: test_cache_digest.cc,v 1.8 1998/04/01 05:02:21 rousskov Exp $
c411be12 4 *
5 * AUTHOR: Alex Rousskov
6 *
7 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
8 * --------------------------------------------------------
9 *
10 * Squid is the result of efforts by numerous individuals from the
11 * Internet community. Development is led by Duane Wessels of the
12 * National Laboratory for Applied Network Research and funded by
13 * the National Science Foundation.
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 *
29 */
30
31/*
32 * Test-suite for playing with cache digests
33 */
34
35#include "squid.h"
36
37typedef struct {
6d80b36f 38 int query_count;
39 int true_hit_count;
40 int true_miss_count;
41 int false_hit_count;
42 int false_miss_count;
43} CacheQueryStats;
44
45typedef struct _Cache Cache;
46struct _Cache {
c411be12 47 const char *name;
48 hash_table *hash;
49 CacheDigest *digest;
6d80b36f 50 Cache *peer;
51 CacheQueryStats qstats;
1afe05c5 52 int count; /* #currently cached entries */
1afe05c5 53 int bad_add_count; /* #duplicate adds */
54 int bad_del_count; /* #dels with no prior add */
6d80b36f 55};
c411be12 56
57
58typedef struct _CacheEntry {
59 const cache_key *key;
60 struct _CacheEntry *next;
c411be12 61 unsigned char key_arr[MD5_DIGEST_CHARS];
6d80b36f 62 /* storeSwapLogData s; */
c411be12 63} CacheEntry;
64
6d80b36f 65/* parsed access log entry */
66typedef struct {
67 cache_key key[MD5_DIGEST_CHARS];
68 time_t timestamp;
69 short int use_icp; /* true/false */
70} RawAccessLogEntry;
71
72typedef enum { frError = -2, frMore = -1, frEof = 0, frOk = 1 } fr_result;
73typedef struct _FileIterator FileIterator;
74typedef fr_result (*FI_READER)(FileIterator *fi);
00b23815 75
6d80b36f 76struct _FileIterator {
00b23815 77 const char *fname;
78 FILE *file;
79 time_t inner_time; /* timestamp of the current entry */
b2ab5fd1 80 time_t time_offset; /* to adjust time set by reader */
00b23815 81 int line_count; /* number of lines scanned */
82 int bad_line_count; /* number of parsing errors */
6d80b36f 83 int time_warp_count;/* number of out-of-order entries in the file */
00b23815 84 FI_READER reader; /* reads next entry and updates inner_time */
85 void *entry; /* buffer for the current entry, freed with xfree() */
6d80b36f 86};
00b23815 87
6d80b36f 88/* globals */
89static time_t cur_time = -1; /* timestamp of the current log entry */
90
91#if 0
00b23815 92
93static int cacheIndexScanCleanPrefix(CacheIndex * idx, const char *fname, FILE * file);
94static int cacheIndexScanAccessLog(CacheIndex * idx, const char *fname, FILE * file);
c411be12 95
6d80b36f 96#endif
97
c411be12 98/* copied from url.c */
99const char *RequestMethodStr[] =
100{
101 "NONE",
102 "GET",
103 "POST",
104 "PUT",
105 "HEAD",
106 "CONNECT",
107 "TRACE",
108 "PURGE"
109};
76e3f5c2 110/* copied from url.c */
111static method_t
6d80b36f 112methodStrToId(const char *s)
76e3f5c2 113{
114 if (strcasecmp(s, "GET") == 0) {
1afe05c5 115 return METHOD_GET;
76e3f5c2 116 } else if (strcasecmp(s, "POST") == 0) {
1afe05c5 117 return METHOD_POST;
76e3f5c2 118 } else if (strcasecmp(s, "PUT") == 0) {
1afe05c5 119 return METHOD_PUT;
76e3f5c2 120 } else if (strcasecmp(s, "HEAD") == 0) {
1afe05c5 121 return METHOD_HEAD;
76e3f5c2 122 } else if (strcasecmp(s, "CONNECT") == 0) {
1afe05c5 123 return METHOD_CONNECT;
76e3f5c2 124 } else if (strcasecmp(s, "TRACE") == 0) {
1afe05c5 125 return METHOD_TRACE;
76e3f5c2 126 } else if (strcasecmp(s, "PURGE") == 0) {
1afe05c5 127 return METHOD_PURGE;
76e3f5c2 128 }
129 return METHOD_NONE;
130}
c411be12 131
00b23815 132/* FileIterator */
133
6d80b36f 134static void fileIteratorAdvance(FileIterator *fi);
135
00b23815 136static FileIterator *
137fileIteratorCreate(const char *fname, FI_READER reader)
138{
139 FileIterator *fi = xcalloc(1, sizeof(FileIterator));
140 assert(fname && reader);
141 fi->fname = fname;
142 fi->reader = reader;
6d80b36f 143 fi->file = fopen(fname, "r");
144 if (!fi->file) {
145 fprintf(stderr, "cannot open %s: %s\n", fname, strerror(errno));
146 return NULL;
147 } else
148 fprintf(stderr, "opened %s\n", fname);
00b23815 149 fileIteratorAdvance(fi);
6d80b36f 150 return fi;
00b23815 151}
152
153static void
154fileIteratorDestroy(FileIterator *fi)
155{
156 assert(fi);
6d80b36f 157 if (fi->file) {
158 fclose(fi->file);
159 fprintf(stderr, "closed %s\n", fi->fname);
160 }
00b23815 161 xfree(fi->entry);
162 xfree(fi);
163}
164
b2ab5fd1 165static void
166fileIteratorSetCurTime(FileIterator *fi, time_t ct)
167{
168 assert(fi);
169 assert(fi->inner_time > 0);
170 fi->time_offset = ct - fi->inner_time;
171}
172
00b23815 173static void
174fileIteratorAdvance(FileIterator *fi)
175{
6d80b36f 176 int res;
00b23815 177 assert(fi);
178 do {
b2ab5fd1 179 const time_t last_time = fi->inner_time;
180 fi->inner_time = -1;
6d80b36f 181 res = fi->reader(fi);
00b23815 182 fi->line_count++;
b2ab5fd1 183 if (fi->inner_time < 0)
184 fi->inner_time = last_time;
185 else
186 fi->inner_time += fi->time_offset;
6d80b36f 187 if (res == frError)
00b23815 188 fi->bad_line_count++;
189 else
b2ab5fd1 190 if (res == frEof) {
191 fprintf(stderr, "exhausted %s (%d entries) at %s",
192 fi->fname, fi->line_count, ctime(&fi->inner_time));
6d80b36f 193 fi->inner_time = -1;
b2ab5fd1 194 } else
6d80b36f 195 if (fi->inner_time < last_time) {
196 assert(last_time >= 0);
197 fi->time_warp_count++;
198 fi->inner_time = last_time;
199 }
200 /* report progress */
201 if (!(fi->line_count % 50000))
b2ab5fd1 202 fprintf(stderr, "%s scanned %d K entries (%d bad) at %s",
203 fi->fname, fi->line_count / 1000, fi->bad_line_count,
204 ctime(&fi->inner_time));
00b23815 205 } while (res < 0);
206}
207
6d80b36f 208/* CacheEntry */
00b23815 209
6d80b36f 210static CacheEntry *
211cacheEntryCreate(const storeSwapLogData * s)
212{
213 CacheEntry *e = xcalloc(1, sizeof(CacheEntry));
214 assert(s);
215 /* e->s = *s; */
216 xmemcpy(e->key_arr, s->key, MD5_DIGEST_CHARS);
217 e->key = &e->key_arr[0];
218 return e;
219}
00b23815 220
6d80b36f 221static void
222cacheEntryDestroy(CacheEntry * e)
c411be12 223{
6d80b36f 224 assert(e);
225 xfree(e);
226}
227
c411be12 228
6d80b36f 229/* Cache */
c411be12 230
6d80b36f 231static Cache *
232cacheCreate(const char *name)
233{
234 Cache *c;
235 assert(name && strlen(name));
236 c = xcalloc(1, sizeof(Cache));
237 c->name = name;
238 c->hash = hash_create(storeKeyHashCmp, 2e6, storeKeyHashHash);
239 return c;
c411be12 240}
241
242static void
6d80b36f 243cacheDestroy(Cache * cache)
c411be12 244{
6d80b36f 245 CacheEntry *e = NULL;
246 hash_table *hash;
247 assert(cache);
248 hash = cache->hash;
249 /* destroy hash table contents */
250 for (e = hash_first(hash); e; e = hash_next(hash)) {
251 hash_remove_link(hash, (hash_link*)e);
252 cacheEntryDestroy(e);
c411be12 253 }
6d80b36f 254 /* destroy the hash table itself */
255 hashFreeMemory(hash);
256 if (cache->digest)
257 cacheDigestDestroy(cache->digest);
258 xfree(cache);
c411be12 259}
260
6d80b36f 261/* re-digests currently hashed entries */
c411be12 262static void
6d80b36f 263cacheResetDigest(Cache * cache)
c411be12 264{
6d80b36f 265 CacheEntry *e = NULL;
266 hash_table *hash;
c411be12 267 struct timeval t_start, t_end;
6d80b36f 268
269 assert(cache);
270 fprintf(stderr, "%s: init-ing digest with %d entries\n", cache->name, cache->count);
271 if (cache->digest)
272 cacheDigestDestroy(cache->digest);
273 hash = cache->hash;
274 cache->digest = cacheDigestCreate(2 * cache->count + 1); /* 50% utilization */
275 if (!cache->count)
276 return;
c411be12 277 gettimeofday(&t_start, NULL);
6d80b36f 278 for (e = hash_first(hash); e; e = hash_next(hash)) {
279 cacheDigestAdd(cache->digest, e->key);
c411be12 280 }
281 gettimeofday(&t_end, NULL);
6d80b36f 282 assert(cache->digest->count == cache->count);
1afe05c5 283 fprintf(stderr, "%s: init-ed digest with %d entries\n",
6d80b36f 284 cache->name, cache->digest->count);
c411be12 285 fprintf(stderr, "%s: init took: %f sec, %f sec/M\n",
6d80b36f 286 cache->name,
c411be12 287 tvSubDsec(t_start, t_end),
6d80b36f 288 (double) 1e6 * tvSubDsec(t_start, t_end) / cache->count);
1c729cf1 289 /* check how long it takes to traverse the hash */
290 gettimeofday(&t_start, NULL);
6d80b36f 291 for (e = hash_first(hash); e; e = hash_next(hash)) {
1c729cf1 292 }
293 gettimeofday(&t_end, NULL);
294 fprintf(stderr, "%s: hash scan took: %f sec, %f sec/M\n",
6d80b36f 295 cache->name,
1c729cf1 296 tvSubDsec(t_start, t_end),
6d80b36f 297 (double) 1e6 * tvSubDsec(t_start, t_end) / cache->count);
298}
299
300static void
301cacheQueryPeer(Cache * cache, const cache_key * key)
302{
303 const int peer_has_it = hash_lookup(cache->peer->hash, key) != NULL;
304 const int we_think_we_have_it = cacheDigestTest(cache->digest, key);
305
306 cache->qstats.query_count++;
307 if (peer_has_it) {
308 if (we_think_we_have_it)
309 cache->qstats.true_hit_count++;
310 else
311 cache->qstats.false_miss_count++;
312 } else {
313 if (we_think_we_have_it)
314 cache->qstats.false_hit_count++;
315 else
316 cache->qstats.true_miss_count++;
317 }
318}
319
320static void
321cacheQueryReport(Cache * cache, CacheQueryStats *stats)
322{
323 fprintf(stdout, "%s: icp: %d\n", cache->name, stats->query_count);
324 fprintf(stdout, "%s: t-hit: %d (%d%%) t-miss: %d (%d%%) t-*: %d (%d%%)\n",
325 cache->name,
326 stats->true_hit_count, xpercentInt(stats->true_hit_count, stats->query_count),
327 stats->true_miss_count, xpercentInt(stats->true_miss_count, stats->query_count),
328 stats->true_hit_count + stats->true_miss_count,
329 xpercentInt(stats->true_hit_count + stats->true_miss_count, stats->query_count)
330 );
331 fprintf(stdout, "%s: f-hit: %d (%d%%) f-miss: %d (%d%%) f-*: %d (%d%%)\n",
332 cache->name,
333 stats->false_hit_count, xpercentInt(stats->false_hit_count, stats->query_count),
334 stats->false_miss_count, xpercentInt(stats->false_miss_count, stats->query_count),
335 stats->false_hit_count + stats->false_miss_count,
336 xpercentInt(stats->false_hit_count + stats->false_miss_count, stats->query_count)
337 );
c411be12 338}
339
6d80b36f 340static void
341cacheFetch(Cache *cache, const RawAccessLogEntry *e)
342{
343 assert(e);
344 if (e->use_icp)
345 cacheQueryPeer(cache, e->key);
346}
347
348static fr_result
349swapStateReader(FileIterator * fi)
350{
351 storeSwapLogData *entry;
352 if (!fi->entry)
353 fi->entry = xcalloc(1, sizeof(storeSwapLogData));
354 entry = fi->entry;
355 if (fread(entry, sizeof(*entry), 1, fi->file) != 1)
356 return frEof;
357 fi->inner_time = entry->lastref;
358 if (entry->op != SWAP_LOG_ADD && entry->op != SWAP_LOG_DEL) {
359 fprintf(stderr, "%s:%d: unknown swap log action\n", fi->fname, fi->line_count);
360 exit(-3);
361 }
362 return frOk;
363}
364
365static fr_result
366accessLogReader(FileIterator * fi)
76e3f5c2 367{
368 static char buf[4096];
6d80b36f 369 RawAccessLogEntry *entry;
370 char *url;
371 char *method;
372 int method_id = METHOD_NONE;
373 char *hier = NULL;
374
375 assert(fi);
376 if (!fi->entry)
377 fi->entry = xcalloc(1, sizeof(RawAccessLogEntry));
378 else
379 memset(fi->entry, 0, sizeof(RawAccessLogEntry));
380 entry = fi->entry;
381 if (!fgets(buf, sizeof(buf), fi->file))
382 return frEof; /* eof */
383 entry->timestamp = fi->inner_time = (time_t)atoi(buf);
384 url = strstr(buf, "://");
385 hier = url ? strstr(url, " - ") : NULL;
386
387 if (!url || !hier) {
388 /*fprintf(stderr, "%s:%d: strange access log entry '%s'\n",
389 * fname, scanned_count, buf); */
390 return frError;
391 }
392 method = url;
393 while (!isdigit(*method)) {
394 if (*method == ' ')
395 *method = '\0';
396 --method;
397 }
398 method += 2;
399 method_id = methodStrToId(method);
400 if (method_id == METHOD_NONE) {
401 /*fprintf(stderr, "%s:%d: invalid method %s in '%s'\n",
402 * fname, scanned_count, method, buf); */
403 return frError;
404 }
405 while (*url) url--;
406 url++;
407 *hier = '\0';
408 hier += 3;
409 *strchr(hier, '/') = '\0';
410 /*fprintf(stdout, "%s:%d: %s %s %s\n",
411 * fname, count, method, url, hier); */
412 entry->use_icp = /* no ICP lookup for these status codes */
413 strcmp(hier, "NONE") &&
414 strcmp(hier, "DIRECT") &&
415 strcmp(hier, "FIREWALL_IP_DIRECT") &&
416 strcmp(hier, "LOCAL_IP_DIRECT") &&
417 strcmp(hier, "NO_DIRECT_FAIL") &&
418 strcmp(hier, "NO_PARENT_DIRECT") &&
419 strcmp(hier, "SINGLE_PARENT") &&
420 strcmp(hier, "PASSTHROUGH_PARENT") &&
421 strcmp(hier, "SSL_PARENT_MISS") &&
422 strcmp(hier, "DEFAULT_PARENT");
423 if (!entry->use_icp)
424 return frMore;
425 memcpy(entry->key, storeKeyPublic(url, method_id), sizeof(entry->key));
426 /*fprintf(stdout, "%s:%d: %s %s %s %s\n",
427 fname, count, method, storeKeyText(entry->key), url, hier); */
428 return frOk;
429}
430
431
432static void
433cachePurge(Cache *cache, storeSwapLogData *s)
434{
435 CacheEntry *olde = (CacheEntry *) hash_lookup(cache->hash, s->key);
436 if (!olde) {
437 cache->bad_del_count++;
438 } else {
439 assert(cache->count);
440 hash_remove_link(cache->hash, (hash_link *) olde);
441 cacheEntryDestroy(olde);
442 cache->count--;
443 }
444}
445
446static void
447cacheStore(Cache *cache, storeSwapLogData *s, int update_digest)
448{
449 CacheEntry *olde = (CacheEntry *) hash_lookup(cache->hash, s->key);
450 if (olde) {
451 cache->bad_add_count++;
452 } else {
453 CacheEntry *e = cacheEntryCreate(s);
454 hash_join(cache->hash, (hash_link *) e);
455 cache->count++;
456 if (update_digest)
457 cacheDigestAdd(cache->digest, e->key);
458 }
459}
460
461static void
462cacheUpdateStore(Cache *cache, storeSwapLogData *s, int update_digest)
463{
464 switch (s->op) {
465 case SWAP_LOG_ADD:
466 cacheStore(cache, s, update_digest);
467 break;
468 case SWAP_LOG_DEL:
469 cachePurge(cache, s);
470 break;
471 default:
472 assert(0);
76e3f5c2 473 }
76e3f5c2 474}
475
c411be12 476static int
477usage(const char *prg_name)
478{
479 fprintf(stderr, "usage: %s <access_log> <swap_state> ...\n",
480 prg_name);
481 return -1;
482}
483
484int
485main(int argc, char *argv[])
486{
00b23815 487 FileIterator **fis = NULL;
488 const int fi_count = argc-1;
6d80b36f 489 int active_fi_count = 0;
b2ab5fd1 490 time_t ready_time;
6d80b36f 491 Cache *them, *us;
492 int i;
c411be12 493
494 if (argc < 3)
495 return usage(argv[0]);
496
6d80b36f 497 them = cacheCreate("them");
498 us = cacheCreate("us");
499 them->peer = us;
500 us->peer = them;
501
00b23815 502 fis = xcalloc(fi_count, sizeof(FileIterator *));
503 /* init iterators with files */
504 fis[0] = fileIteratorCreate(argv[1], accessLogReader);
c411be12 505 for (i = 2; i < argc; ++i) {
00b23815 506 fis[i-1] = fileIteratorCreate(argv[i], swapStateReader);
c411be12 507 }
6d80b36f 508 /* read prefix to get start-up contents of the peer cache */
b2ab5fd1 509 ready_time = -1;
00b23815 510 for (i = 1; i < fi_count; ++i) {
6d80b36f 511 FileIterator *fi = fis[i];
512 while (fi->inner_time > 0) {
b2ab5fd1 513 ready_time = fi->inner_time;
6d80b36f 514 if (((storeSwapLogData*)fi->entry)->op != SWAP_LOG_ADD) {
515 break;
516 } else {
517 cacheStore(them, fi->entry, 0);
518 fileIteratorAdvance(fi);
519 }
520 }
00b23815 521 }
522 /* digest peer cache content */
6d80b36f 523 cacheResetDigest(them);
524 us->digest = cacheDigestClone(them->digest); /* @netw@ */
525
b2ab5fd1 526 /* shift the time in access log to match ready_time */
527 fileIteratorSetCurTime(fis[0], ready_time);
528
6d80b36f 529 /* iterate, use the iterator with the smallest positive inner_time */
00b23815 530 cur_time = -1;
6d80b36f 531 do {
532 int next_i = -1;
533 time_t next_time = -1;
534 active_fi_count = 0;
535 for (i = 0; i < fi_count; ++i) {
536 if (fis[i]->inner_time >= 0) {
537 if (!active_fi_count || fis[i]->inner_time < next_time) {
538 next_i = i;
539 next_time = fis[i]->inner_time;
540 }
541 active_fi_count++;
542 }
00b23815 543 }
6d80b36f 544 if (next_i >= 0) {
b2ab5fd1 545 cur_time = next_time;
546 /*fprintf(stderr, "%2d time: %d %s", next_i, (int)cur_time, ctime(&cur_time));*/
547 if (next_i == 0)
548 cacheFetch(us, fis[next_i]->entry);
549 else
550 cacheUpdateStore(them, fis[next_i]->entry, 0);
6d80b36f 551 fileIteratorAdvance(fis[next_i]);
552 }
553 } while (active_fi_count);
554
555 /* report */
556 cacheQueryReport(us, &us->qstats);
557
558 /* clean */
559 for (i = 0; i < argc-1; ++i) {
00b23815 560 fileIteratorDestroy(fis[i]);
561 }
562 xfree(fis);
6d80b36f 563 cacheDestroy(them);
564 cacheDestroy(us);
76e3f5c2 565 return 0;
c411be12 566}