]> git.ipfire.org Git - thirdparty/squid.git/blame - src/test_cache_digest.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / test_cache_digest.cc
CommitLineData
c411be12 1/*
bde978a6 2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
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.
c411be12 7 */
8
9/*
10 * Test-suite for playing with cache digests
11 */
12
582c2af2 13#include "squid.h"
b814e8d4 14#include "CacheDigest.h"
fb548aaf 15#include "store_key_md5.h"
c411be12 16
1a30fdf5 17#include <cerrno>
21d845b1 18
26ac0430 19typedef struct {
6d80b36f 20 int query_count;
21 int true_hit_count;
22 int true_miss_count;
23 int false_hit_count;
24 int false_miss_count;
2fadd50d 25} CacheQueryStats;
6d80b36f 26
27typedef struct _Cache Cache;
62e76326 28
26ac0430 29struct _Cache {
c411be12 30 const char *name;
31 hash_table *hash;
32 CacheDigest *digest;
6d80b36f 33 Cache *peer;
34 CacheQueryStats qstats;
f53969cc
SM
35 int count; /* #currently cached entries */
36 int req_count; /* #requests to this cache */
37 int bad_add_count; /* #duplicate adds */
38 int bad_del_count; /* #dels with no prior add */
6d80b36f 39};
c411be12 40
26ac0430 41typedef struct _CacheEntry {
c411be12 42 const cache_key *key;
62e76326 43
c411be12 44 struct _CacheEntry *next;
c3031d67 45 unsigned char key_arr[SQUID_MD5_DIGEST_LENGTH];
6d80b36f 46 /* storeSwapLogData s; */
2fadd50d 47} CacheEntry;
c411be12 48
6d80b36f 49/* parsed access log entry */
62e76326 50
26ac0430 51typedef struct {
c3031d67 52 cache_key key[SQUID_MD5_DIGEST_LENGTH];
6d80b36f 53 time_t timestamp;
f53969cc 54 short int use_icp; /* true/false */
2fadd50d 55} RawAccessLogEntry;
6d80b36f 56
b644367b 57typedef enum {
58 frError = -2, frMore = -1, frEof = 0, frOk = 1
59} fr_result;
62e76326 60
6d80b36f 61typedef struct _FileIterator FileIterator;
b644367b 62typedef fr_result(*FI_READER) (FileIterator * fi);
00b23815 63
26ac0430 64struct _FileIterator {
00b23815 65 const char *fname;
66 FILE *file;
f53969cc
SM
67 time_t inner_time; /* timestamp of the current entry */
68 time_t time_offset; /* to adjust time set by reader */
69 int line_count; /* number of lines scanned */
70 int bad_line_count; /* number of parsing errors */
71 int time_warp_count; /* number of out-of-order entries in the file */
72 FI_READER reader; /* reads next entry and updates inner_time */
73 void *entry; /* buffer for the current entry, freed with xfree() */
6d80b36f 74};
00b23815 75
6d80b36f 76/* globals */
f53969cc 77static time_t cur_time = -1; /* timestamp of the current log entry */
6d80b36f 78
76e3f5c2 79/* copied from url.c */
60745f24 80static HttpRequestMethod
6d80b36f 81methodStrToId(const char *s)
76e3f5c2 82{
83 if (strcasecmp(s, "GET") == 0) {
62e76326 84 return METHOD_GET;
76e3f5c2 85 } else if (strcasecmp(s, "POST") == 0) {
62e76326 86 return METHOD_POST;
76e3f5c2 87 } else if (strcasecmp(s, "PUT") == 0) {
62e76326 88 return METHOD_PUT;
76e3f5c2 89 } else if (strcasecmp(s, "HEAD") == 0) {
62e76326 90 return METHOD_HEAD;
76e3f5c2 91 } else if (strcasecmp(s, "CONNECT") == 0) {
62e76326 92 return METHOD_CONNECT;
76e3f5c2 93 } else if (strcasecmp(s, "TRACE") == 0) {
62e76326 94 return METHOD_TRACE;
76e3f5c2 95 } else if (strcasecmp(s, "PURGE") == 0) {
62e76326 96 return METHOD_PURGE;
76e3f5c2 97 }
62e76326 98
76e3f5c2 99 return METHOD_NONE;
100}
c411be12 101
00b23815 102/* FileIterator */
103
b644367b 104static void fileIteratorAdvance(FileIterator * fi);
6d80b36f 105
00b23815 106static FileIterator *
107fileIteratorCreate(const char *fname, FI_READER reader)
108{
e6ccf245 109 FileIterator *fi = (FileIterator *)xcalloc(1, sizeof(FileIterator));
00b23815 110 assert(fname && reader);
111 fi->fname = fname;
112 fi->reader = reader;
6d80b36f 113 fi->file = fopen(fname, "r");
62e76326 114
6d80b36f 115 if (!fi->file) {
62e76326 116 fprintf(stderr, "cannot open %s: %s\n", fname, strerror(errno));
117 return NULL;
6d80b36f 118 } else
62e76326 119 fprintf(stderr, "opened %s\n", fname);
120
00b23815 121 fileIteratorAdvance(fi);
62e76326 122
6d80b36f 123 return fi;
00b23815 124}
125
126static void
b644367b 127fileIteratorDestroy(FileIterator * fi)
00b23815 128{
129 assert(fi);
62e76326 130
6d80b36f 131 if (fi->file) {
62e76326 132 fclose(fi->file);
133 fprintf(stderr, "closed %s\n", fi->fname);
6d80b36f 134 }
62e76326 135
00b23815 136 xfree(fi->entry);
137 xfree(fi);
138}
139
b2ab5fd1 140static void
b644367b 141fileIteratorSetCurTime(FileIterator * fi, time_t ct)
b2ab5fd1 142{
143 assert(fi);
144 assert(fi->inner_time > 0);
145 fi->time_offset = ct - fi->inner_time;
146}
147
00b23815 148static void
b644367b 149fileIteratorAdvance(FileIterator * fi)
00b23815 150{
6d80b36f 151 int res;
00b23815 152 assert(fi);
62e76326 153
00b23815 154 do {
62e76326 155 const time_t last_time = fi->inner_time;
156 fi->inner_time = -1;
157 res = fi->reader(fi);
5db6bf73 158 ++ fi->line_count;
62e76326 159
160 if (fi->inner_time < 0)
161 fi->inner_time = last_time;
162 else
163 fi->inner_time += fi->time_offset;
164
165 if (res == frError)
5db6bf73 166 ++ fi->bad_line_count;
62e76326 167 else if (res == frEof) {
168 fprintf(stderr, "exhausted %s (%d entries) at %s",
169 fi->fname, fi->line_count, ctime(&fi->inner_time));
170 fi->inner_time = -1;
171 } else if (fi->inner_time < last_time) {
172 assert(last_time >= 0);
5db6bf73 173 ++ fi->time_warp_count;
62e76326 174 fi->inner_time = last_time;
175 }
176
177 /* report progress */
178 if (!(fi->line_count % 50000))
179 fprintf(stderr, "%s scanned %d K entries (%d bad) at %s",
180 fi->fname, fi->line_count / 1000, fi->bad_line_count,
181 ctime(&fi->inner_time));
00b23815 182 } while (res < 0);
183}
184
6d80b36f 185/* CacheEntry */
00b23815 186
6d80b36f 187static CacheEntry *
188cacheEntryCreate(const storeSwapLogData * s)
189{
e6ccf245 190 CacheEntry *e = (CacheEntry *)xcalloc(1, sizeof(CacheEntry));
6d80b36f 191 assert(s);
192 /* e->s = *s; */
41d00cd3 193 memcpy(e->key_arr, s->key, SQUID_MD5_DIGEST_LENGTH);
6d80b36f 194 e->key = &e->key_arr[0];
195 return e;
196}
00b23815 197
6d80b36f 198static void
199cacheEntryDestroy(CacheEntry * e)
c411be12 200{
6d80b36f 201 assert(e);
202 xfree(e);
203}
204
6d80b36f 205/* Cache */
c411be12 206
6d80b36f 207static Cache *
208cacheCreate(const char *name)
209{
210 Cache *c;
211 assert(name && strlen(name));
e6ccf245 212 c = (Cache *)xcalloc(1, sizeof(Cache));
6d80b36f 213 c->name = name;
e6ccf245 214 c->hash = hash_create(storeKeyHashCmp, (int)2e6, storeKeyHashHash);
6d80b36f 215 return c;
c411be12 216}
217
218static void
6d80b36f 219cacheDestroy(Cache * cache)
c411be12 220{
6d80b36f 221 CacheEntry *e = NULL;
222 hash_table *hash;
223 assert(cache);
224 hash = cache->hash;
225 /* destroy hash table contents */
0f6bebac 226 hash_first(hash);
62e76326 227
e6ccf245 228 while ((e = (CacheEntry *)hash_next(hash))) {
62e76326 229 hash_remove_link(hash, (hash_link *) e);
230 cacheEntryDestroy(e);
c411be12 231 }
62e76326 232
6d80b36f 233 /* destroy the hash table itself */
b644367b 234 hashFreeMemory(hash);
62e76326 235
6d80b36f 236 if (cache->digest)
62e76326 237 cacheDigestDestroy(cache->digest);
238
6d80b36f 239 xfree(cache);
c411be12 240}
241
6d80b36f 242/* re-digests currently hashed entries */
c411be12 243static void
6d80b36f 244cacheResetDigest(Cache * cache)
c411be12 245{
6d80b36f 246 CacheEntry *e = NULL;
247 hash_table *hash;
62e76326 248
c411be12 249 struct timeval t_start, t_end;
6d80b36f 250
251 assert(cache);
252 fprintf(stderr, "%s: init-ing digest with %d entries\n", cache->name, cache->count);
62e76326 253
6d80b36f 254 if (cache->digest)
62e76326 255 cacheDigestDestroy(cache->digest);
256
6d80b36f 257 hash = cache->hash;
62e76326 258
4b4cd312 259 cache->digest = cacheDigestCreate(cache->count + 1, 6);
62e76326 260
6d80b36f 261 if (!cache->count)
62e76326 262 return;
263
c411be12 264 gettimeofday(&t_start, NULL);
62e76326 265
0f6bebac 266 hash_first(hash);
62e76326 267
e6ccf245 268 while ((e = (CacheEntry *)hash_next(hash))) {
62e76326 269 cacheDigestAdd(cache->digest, e->key);
c411be12 270 }
62e76326 271
c411be12 272 gettimeofday(&t_end, NULL);
6d80b36f 273 assert(cache->digest->count == cache->count);
1afe05c5 274 fprintf(stderr, "%s: init-ed digest with %d entries\n",
62e76326 275 cache->name, cache->digest->count);
c411be12 276 fprintf(stderr, "%s: init took: %f sec, %f sec/M\n",
62e76326 277 cache->name,
278 tvSubDsec(t_start, t_end),
279 (double) 1e6 * tvSubDsec(t_start, t_end) / cache->count);
1c729cf1 280 /* check how long it takes to traverse the hash */
281 gettimeofday(&t_start, NULL);
e6ccf245 282 hash_first(hash);
62e76326 283
284 for (e = (CacheEntry *)hash_next(hash); e; e = (CacheEntry *)hash_next(hash)) {}
285
1c729cf1 286 gettimeofday(&t_end, NULL);
287 fprintf(stderr, "%s: hash scan took: %f sec, %f sec/M\n",
62e76326 288 cache->name,
289 tvSubDsec(t_start, t_end),
290 (double) 1e6 * tvSubDsec(t_start, t_end) / cache->count);
6d80b36f 291}
292
293static void
294cacheQueryPeer(Cache * cache, const cache_key * key)
295{
296 const int peer_has_it = hash_lookup(cache->peer->hash, key) != NULL;
297 const int we_think_we_have_it = cacheDigestTest(cache->digest, key);
298
5db6bf73 299 ++ cache->qstats.query_count;
62e76326 300
6d80b36f 301 if (peer_has_it) {
62e76326 302 if (we_think_we_have_it)
5db6bf73 303 ++ cache->qstats.true_hit_count;
62e76326 304 else
5db6bf73 305 ++ cache->qstats.false_miss_count;
6d80b36f 306 } else {
62e76326 307 if (we_think_we_have_it)
5db6bf73 308 ++ cache->qstats.false_hit_count;
62e76326 309 else
5db6bf73 310 ++ cache->qstats.true_miss_count;
6d80b36f 311 }
312}
313
314static void
b644367b 315cacheQueryReport(Cache * cache, CacheQueryStats * stats)
6d80b36f 316{
b644367b 317 fprintf(stdout, "%s: peer queries: %d (%d%%)\n",
62e76326 318 cache->name,
319 stats->query_count, xpercentInt(stats->query_count, cache->req_count)
320 );
6d80b36f 321 fprintf(stdout, "%s: t-hit: %d (%d%%) t-miss: %d (%d%%) t-*: %d (%d%%)\n",
62e76326 322 cache->name,
323 stats->true_hit_count, xpercentInt(stats->true_hit_count, stats->query_count),
324 stats->true_miss_count, xpercentInt(stats->true_miss_count, stats->query_count),
325 stats->true_hit_count + stats->true_miss_count,
326 xpercentInt(stats->true_hit_count + stats->true_miss_count, stats->query_count)
327 );
6d80b36f 328 fprintf(stdout, "%s: f-hit: %d (%d%%) f-miss: %d (%d%%) f-*: %d (%d%%)\n",
62e76326 329 cache->name,
330 stats->false_hit_count, xpercentInt(stats->false_hit_count, stats->query_count),
331 stats->false_miss_count, xpercentInt(stats->false_miss_count, stats->query_count),
332 stats->false_hit_count + stats->false_miss_count,
333 xpercentInt(stats->false_hit_count + stats->false_miss_count, stats->query_count)
334 );
c411be12 335}
336
c2186446 337static void
338cacheReport(Cache * cache)
339{
b644367b 340 fprintf(stdout, "%s: entries: %d reqs: %d bad-add: %d bad-del: %d\n",
62e76326 341 cache->name, cache->count, cache->req_count,
342 cache->bad_add_count, cache->bad_del_count);
c2186446 343
c2186446 344}
345
6d80b36f 346static void
b644367b 347cacheFetch(Cache * cache, const RawAccessLogEntry * e)
6d80b36f 348{
349 assert(e);
5db6bf73 350 ++ cache->req_count;
62e76326 351
6d80b36f 352 if (e->use_icp)
62e76326 353 cacheQueryPeer(cache, e->key);
6d80b36f 354}
355
356static fr_result
357swapStateReader(FileIterator * fi)
358{
359 storeSwapLogData *entry;
62e76326 360
6d80b36f 361 if (!fi->entry)
62e76326 362 fi->entry = xcalloc(1, sizeof(storeSwapLogData));
363
e6ccf245 364 entry = (storeSwapLogData *)fi->entry;
62e76326 365
6d80b36f 366 if (fread(entry, sizeof(*entry), 1, fi->file) != 1)
62e76326 367 return frEof;
368
6d80b36f 369 fi->inner_time = entry->lastref;
62e76326 370
6d80b36f 371 if (entry->op != SWAP_LOG_ADD && entry->op != SWAP_LOG_DEL) {
62e76326 372 fprintf(stderr, "%s:%d: unknown swap log action\n", fi->fname, fi->line_count);
373 exit(-3);
6d80b36f 374 }
62e76326 375
6d80b36f 376 return frOk;
377}
378
379static fr_result
380accessLogReader(FileIterator * fi)
76e3f5c2 381{
382 static char buf[4096];
6d80b36f 383 RawAccessLogEntry *entry;
384 char *url;
385 char *method;
60745f24 386 HttpRequestMethod method_id = METHOD_NONE;
6d80b36f 387 char *hier = NULL;
388
389 assert(fi);
62e76326 390
6d80b36f 391 if (!fi->entry)
62e76326 392 fi->entry = xcalloc(1, sizeof(RawAccessLogEntry));
6d80b36f 393 else
62e76326 394 memset(fi->entry, 0, sizeof(RawAccessLogEntry));
395
e6ccf245 396 entry = (RawAccessLogEntry*)fi->entry;
62e76326 397
6d80b36f 398 if (!fgets(buf, sizeof(buf), fi->file))
f53969cc 399 return frEof; /* eof */
62e76326 400
b644367b 401 entry->timestamp = fi->inner_time = (time_t) atoi(buf);
62e76326 402
6d80b36f 403 url = strstr(buf, "://");
62e76326 404
6d80b36f 405 hier = url ? strstr(url, " - ") : NULL;
406
407 if (!url || !hier) {
62e76326 408 /*fprintf(stderr, "%s:%d: strange access log entry '%s'\n",
409 * fname, scanned_count, buf); */
410 return frError;
6d80b36f 411 }
62e76326 412
6d80b36f 413 method = url;
62e76326 414
ec451a0f 415 while (!xisdigit(*method)) {
62e76326 416 if (*method == ' ')
417 *method = '\0';
418
419 --method;
6d80b36f 420 }
62e76326 421
6d80b36f 422 method += 2;
423 method_id = methodStrToId(method);
62e76326 424
6d80b36f 425 if (method_id == METHOD_NONE) {
62e76326 426 /*fprintf(stderr, "%s:%d: invalid method %s in '%s'\n",
427 * fname, scanned_count, method, buf); */
428 return frError;
6d80b36f 429 }
62e76326 430
b644367b 431 while (*url)
5e263176 432 --url;
62e76326 433
5db6bf73 434 ++url;
62e76326 435
6d80b36f 436 *hier = '\0';
62e76326 437
6d80b36f 438 hier += 3;
62e76326 439
6d80b36f 440 *strchr(hier, '/') = '\0';
62e76326 441
6d80b36f 442 /*fprintf(stdout, "%s:%d: %s %s %s\n",
443 * fname, count, method, url, hier); */
b644367b 444 entry->use_icp = strcmp(hier, "NONE");
62e76326 445
b644367b 446 /* no ICP lookup for these status codes */
62e76326 447 /* strcmp(hier, "NONE") &&
448 * strcmp(hier, "DIRECT") &&
449 * strcmp(hier, "FIREWALL_IP_DIRECT") &&
450 * strcmp(hier, "LOCAL_IP_DIRECT") &&
451 * strcmp(hier, "NO_DIRECT_FAIL") &&
452 * strcmp(hier, "NO_PARENT_DIRECT") &&
453 * strcmp(hier, "SINGLE_PARENT") &&
454 * strcmp(hier, "PASSTHROUGH_PARENT") &&
455 * strcmp(hier, "SSL_PARENT_MISS") &&
456 * strcmp(hier, "DEFAULT_PARENT");
457 */
41d00cd3 458 memcpy(entry->key, storeKeyPublic(url, method_id), sizeof(entry->key));
62e76326 459
6d80b36f 460 /*fprintf(stdout, "%s:%d: %s %s %s %s\n",
b644367b 461 * fname, count, method, storeKeyText(entry->key), url, hier); */
6d80b36f 462 return frOk;
463}
464
6d80b36f 465static void
b644367b 466cachePurge(Cache * cache, storeSwapLogData * s, int update_digest)
6d80b36f 467{
468 CacheEntry *olde = (CacheEntry *) hash_lookup(cache->hash, s->key);
62e76326 469
6d80b36f 470 if (!olde) {
5db6bf73 471 ++ cache->bad_del_count;
6d80b36f 472 } else {
62e76326 473 assert(cache->count);
474 hash_remove_link(cache->hash, (hash_link *) olde);
475
476 if (update_digest)
477 cacheDigestDel(cache->digest, s->key);
478
479 cacheEntryDestroy(olde);
480
5e263176 481 -- cache->count;
6d80b36f 482 }
483}
484
485static void
b644367b 486cacheStore(Cache * cache, storeSwapLogData * s, int update_digest)
6d80b36f 487{
488 CacheEntry *olde = (CacheEntry *) hash_lookup(cache->hash, s->key);
62e76326 489
6d80b36f 490 if (olde) {
5db6bf73 491 ++ cache->bad_add_count;
6d80b36f 492 } else {
62e76326 493 CacheEntry *e = cacheEntryCreate(s);
494 hash_join(cache->hash, (hash_link *)&e->key);
5db6bf73 495 ++ cache->count;
62e76326 496
497 if (update_digest)
498 cacheDigestAdd(cache->digest, e->key);
6d80b36f 499 }
500}
501
502static void
b644367b 503cacheUpdateStore(Cache * cache, storeSwapLogData * s, int update_digest)
6d80b36f 504{
505 switch (s->op) {
62e76326 506
b644367b 507 case SWAP_LOG_ADD:
62e76326 508 cacheStore(cache, s, update_digest);
509 break;
510
b644367b 511 case SWAP_LOG_DEL:
62e76326 512 cachePurge(cache, s, update_digest);
513 break;
514
b644367b 515 default:
62e76326 516 assert(0);
76e3f5c2 517 }
76e3f5c2 518}
519
c411be12 520static int
521usage(const char *prg_name)
522{
523 fprintf(stderr, "usage: %s <access_log> <swap_state> ...\n",
62e76326 524 prg_name);
c411be12 525 return -1;
526}
527
528int
529main(int argc, char *argv[])
530{
00b23815 531 FileIterator **fis = NULL;
b644367b 532 const int fi_count = argc - 1;
6d80b36f 533 int active_fi_count = 0;
b2ab5fd1 534 time_t ready_time;
6d80b36f 535 Cache *them, *us;
536 int i;
c411be12 537
538 if (argc < 3)
62e76326 539 return usage(argv[0]);
c411be12 540
6d80b36f 541 them = cacheCreate("them");
62e76326 542
6d80b36f 543 us = cacheCreate("us");
62e76326 544
6d80b36f 545 them->peer = us;
62e76326 546
6d80b36f 547 us->peer = them;
548
e6ccf245 549 fis = (FileIterator **)xcalloc(fi_count, sizeof(FileIterator *));
62e76326 550
00b23815 551 /* init iterators with files */
552 fis[0] = fileIteratorCreate(argv[1], accessLogReader);
62e76326 553
37eeba76 554 for (i = 2; i < argc; ++i)
62e76326 555 fis[i - 1] = fileIteratorCreate(argv[i], swapStateReader);
556
37eeba76 557 /* check that all files were found */
558 for (i = 0; i < fi_count; ++i)
62e76326 559 if (!fis[i])
560 return -2;
561
6d80b36f 562 /* read prefix to get start-up contents of the peer cache */
b2ab5fd1 563 ready_time = -1;
62e76326 564
00b23815 565 for (i = 1; i < fi_count; ++i) {
62e76326 566 FileIterator *fi = fis[i];
567
568 while (fi->inner_time > 0) {
569 if (((storeSwapLogData *) fi->entry)->op == SWAP_LOG_DEL) {
570 cachePurge(them, (storeSwapLogData *)fi->entry, 0);
571
572 if (ready_time < 0)
573 ready_time = fi->inner_time;
574 } else {
575 if (ready_time > 0 && fi->inner_time > ready_time)
576 break;
577
578 cacheStore(them, (storeSwapLogData *)fi->entry, 0);
579 }
580
581 fileIteratorAdvance(fi);
582 }
00b23815 583 }
62e76326 584
00b23815 585 /* digest peer cache content */
6d80b36f 586 cacheResetDigest(them);
62e76326 587
f53969cc 588 us->digest = cacheDigestClone(them->digest); /* @netw@ */
6d80b36f 589
b2ab5fd1 590 /* shift the time in access log to match ready_time */
591 fileIteratorSetCurTime(fis[0], ready_time);
592
6d80b36f 593 /* iterate, use the iterator with the smallest positive inner_time */
00b23815 594 cur_time = -1;
62e76326 595
6d80b36f 596 do {
62e76326 597 int next_i = -1;
598 time_t next_time = -1;
599 active_fi_count = 0;
600
601 for (i = 0; i < fi_count; ++i) {
602 if (fis[i]->inner_time >= 0) {
603 if (!active_fi_count || fis[i]->inner_time < next_time) {
604 next_i = i;
605 next_time = fis[i]->inner_time;
606 }
607
5db6bf73 608 ++active_fi_count;
62e76326 609 }
610 }
611
612 if (next_i >= 0) {
613 cur_time = next_time;
614 /*fprintf(stderr, "%2d time: %d %s", next_i, (int)cur_time, ctime(&cur_time)); */
615
616 if (next_i == 0)
617 cacheFetch(us, (RawAccessLogEntry *)fis[next_i]->entry);
618 else
619 cacheUpdateStore(them, (storeSwapLogData *)fis[next_i]->entry, 1);
620
621 fileIteratorAdvance(fis[next_i]);
622 }
6d80b36f 623 } while (active_fi_count);
624
625 /* report */
c2186446 626 cacheReport(them);
62e76326 627
c2186446 628 cacheReport(us);
62e76326 629
6d80b36f 630 cacheQueryReport(us, &us->qstats);
631
632 /* clean */
b644367b 633 for (i = 0; i < argc - 1; ++i) {
62e76326 634 fileIteratorDestroy(fis[i]);
00b23815 635 }
62e76326 636
00b23815 637 xfree(fis);
6d80b36f 638 cacheDestroy(them);
639 cacheDestroy(us);
76e3f5c2 640 return 0;
c411be12 641}
f53969cc 642