]> git.ipfire.org Git - thirdparty/squid.git/blame - src/test_cache_digest.cc
dlinkAddTail
[thirdparty/squid.git] / src / test_cache_digest.cc
CommitLineData
1afe05c5 1
c411be12 2/*
6d80b36f 3 * $Id: test_cache_digest.cc,v 1.7 1998/04/01 00:14:03 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 */
80 int line_count; /* number of lines scanned */
81 int bad_line_count; /* number of parsing errors */
6d80b36f 82 int time_warp_count;/* number of out-of-order entries in the file */
00b23815 83 FI_READER reader; /* reads next entry and updates inner_time */
84 void *entry; /* buffer for the current entry, freed with xfree() */
6d80b36f 85};
00b23815 86
6d80b36f 87/* globals */
88static time_t cur_time = -1; /* timestamp of the current log entry */
89
90#if 0
00b23815 91
92static int cacheIndexScanCleanPrefix(CacheIndex * idx, const char *fname, FILE * file);
93static int cacheIndexScanAccessLog(CacheIndex * idx, const char *fname, FILE * file);
c411be12 94
6d80b36f 95#endif
96
c411be12 97/* copied from url.c */
98const char *RequestMethodStr[] =
99{
100 "NONE",
101 "GET",
102 "POST",
103 "PUT",
104 "HEAD",
105 "CONNECT",
106 "TRACE",
107 "PURGE"
108};
76e3f5c2 109/* copied from url.c */
110static method_t
6d80b36f 111methodStrToId(const char *s)
76e3f5c2 112{
113 if (strcasecmp(s, "GET") == 0) {
1afe05c5 114 return METHOD_GET;
76e3f5c2 115 } else if (strcasecmp(s, "POST") == 0) {
1afe05c5 116 return METHOD_POST;
76e3f5c2 117 } else if (strcasecmp(s, "PUT") == 0) {
1afe05c5 118 return METHOD_PUT;
76e3f5c2 119 } else if (strcasecmp(s, "HEAD") == 0) {
1afe05c5 120 return METHOD_HEAD;
76e3f5c2 121 } else if (strcasecmp(s, "CONNECT") == 0) {
1afe05c5 122 return METHOD_CONNECT;
76e3f5c2 123 } else if (strcasecmp(s, "TRACE") == 0) {
1afe05c5 124 return METHOD_TRACE;
76e3f5c2 125 } else if (strcasecmp(s, "PURGE") == 0) {
1afe05c5 126 return METHOD_PURGE;
76e3f5c2 127 }
128 return METHOD_NONE;
129}
c411be12 130
00b23815 131/* FileIterator */
132
6d80b36f 133static void fileIteratorAdvance(FileIterator *fi);
134
00b23815 135static FileIterator *
136fileIteratorCreate(const char *fname, FI_READER reader)
137{
138 FileIterator *fi = xcalloc(1, sizeof(FileIterator));
139 assert(fname && reader);
140 fi->fname = fname;
141 fi->reader = reader;
6d80b36f 142 fi->file = fopen(fname, "r");
143 if (!fi->file) {
144 fprintf(stderr, "cannot open %s: %s\n", fname, strerror(errno));
145 return NULL;
146 } else
147 fprintf(stderr, "opened %s\n", fname);
00b23815 148 fileIteratorAdvance(fi);
6d80b36f 149 return fi;
00b23815 150}
151
152static void
153fileIteratorDestroy(FileIterator *fi)
154{
155 assert(fi);
6d80b36f 156 if (fi->file) {
157 fclose(fi->file);
158 fprintf(stderr, "closed %s\n", fi->fname);
159 }
00b23815 160 xfree(fi->entry);
161 xfree(fi);
162}
163
164static void
165fileIteratorAdvance(FileIterator *fi)
166{
6d80b36f 167 int res;
00b23815 168 assert(fi);
169 do {
6d80b36f 170 time_t last_time = fi->inner_time;
171 res = fi->reader(fi);
00b23815 172 fi->line_count++;
6d80b36f 173 if (res == frError)
00b23815 174 fi->bad_line_count++;
175 else
6d80b36f 176 if (res == frEof)
177 fi->inner_time = -1;
178 else
179 if (fi->inner_time < last_time) {
180 assert(last_time >= 0);
181 fi->time_warp_count++;
182 fi->inner_time = last_time;
183 }
184 /* report progress */
185 if (!(fi->line_count % 50000))
186 fprintf(stderr, "%s scanned %d K entries (%d bad)\n",
187 fi->fname, fi->line_count / 1000, fi->bad_line_count);
00b23815 188 } while (res < 0);
189}
190
6d80b36f 191/* CacheEntry */
00b23815 192
6d80b36f 193static CacheEntry *
194cacheEntryCreate(const storeSwapLogData * s)
195{
196 CacheEntry *e = xcalloc(1, sizeof(CacheEntry));
197 assert(s);
198 /* e->s = *s; */
199 xmemcpy(e->key_arr, s->key, MD5_DIGEST_CHARS);
200 e->key = &e->key_arr[0];
201 return e;
202}
00b23815 203
6d80b36f 204static void
205cacheEntryDestroy(CacheEntry * e)
c411be12 206{
6d80b36f 207 assert(e);
208 xfree(e);
209}
210
c411be12 211
6d80b36f 212/* Cache */
c411be12 213
6d80b36f 214static Cache *
215cacheCreate(const char *name)
216{
217 Cache *c;
218 assert(name && strlen(name));
219 c = xcalloc(1, sizeof(Cache));
220 c->name = name;
221 c->hash = hash_create(storeKeyHashCmp, 2e6, storeKeyHashHash);
222 return c;
c411be12 223}
224
225static void
6d80b36f 226cacheDestroy(Cache * cache)
c411be12 227{
6d80b36f 228 CacheEntry *e = NULL;
229 hash_table *hash;
230 assert(cache);
231 hash = cache->hash;
232 /* destroy hash table contents */
233 for (e = hash_first(hash); e; e = hash_next(hash)) {
234 hash_remove_link(hash, (hash_link*)e);
235 cacheEntryDestroy(e);
c411be12 236 }
6d80b36f 237 /* destroy the hash table itself */
238 hashFreeMemory(hash);
239 if (cache->digest)
240 cacheDigestDestroy(cache->digest);
241 xfree(cache);
c411be12 242}
243
6d80b36f 244/* re-digests currently hashed entries */
c411be12 245static void
6d80b36f 246cacheResetDigest(Cache * cache)
c411be12 247{
6d80b36f 248 CacheEntry *e = NULL;
249 hash_table *hash;
c411be12 250 struct timeval t_start, t_end;
6d80b36f 251
252 assert(cache);
253 fprintf(stderr, "%s: init-ing digest with %d entries\n", cache->name, cache->count);
254 if (cache->digest)
255 cacheDigestDestroy(cache->digest);
256 hash = cache->hash;
257 cache->digest = cacheDigestCreate(2 * cache->count + 1); /* 50% utilization */
258 if (!cache->count)
259 return;
c411be12 260 gettimeofday(&t_start, NULL);
6d80b36f 261 for (e = hash_first(hash); e; e = hash_next(hash)) {
262 cacheDigestAdd(cache->digest, e->key);
c411be12 263 }
264 gettimeofday(&t_end, NULL);
6d80b36f 265 assert(cache->digest->count == cache->count);
1afe05c5 266 fprintf(stderr, "%s: init-ed digest with %d entries\n",
6d80b36f 267 cache->name, cache->digest->count);
c411be12 268 fprintf(stderr, "%s: init took: %f sec, %f sec/M\n",
6d80b36f 269 cache->name,
c411be12 270 tvSubDsec(t_start, t_end),
6d80b36f 271 (double) 1e6 * tvSubDsec(t_start, t_end) / cache->count);
1c729cf1 272 /* check how long it takes to traverse the hash */
273 gettimeofday(&t_start, NULL);
6d80b36f 274 for (e = hash_first(hash); e; e = hash_next(hash)) {
1c729cf1 275 }
276 gettimeofday(&t_end, NULL);
277 fprintf(stderr, "%s: hash scan took: %f sec, %f sec/M\n",
6d80b36f 278 cache->name,
1c729cf1 279 tvSubDsec(t_start, t_end),
6d80b36f 280 (double) 1e6 * tvSubDsec(t_start, t_end) / cache->count);
281}
282
283static void
284cacheQueryPeer(Cache * cache, const cache_key * key)
285{
286 const int peer_has_it = hash_lookup(cache->peer->hash, key) != NULL;
287 const int we_think_we_have_it = cacheDigestTest(cache->digest, key);
288
289 cache->qstats.query_count++;
290 if (peer_has_it) {
291 if (we_think_we_have_it)
292 cache->qstats.true_hit_count++;
293 else
294 cache->qstats.false_miss_count++;
295 } else {
296 if (we_think_we_have_it)
297 cache->qstats.false_hit_count++;
298 else
299 cache->qstats.true_miss_count++;
300 }
301}
302
303static void
304cacheQueryReport(Cache * cache, CacheQueryStats *stats)
305{
306 fprintf(stdout, "%s: icp: %d\n", cache->name, stats->query_count);
307 fprintf(stdout, "%s: t-hit: %d (%d%%) t-miss: %d (%d%%) t-*: %d (%d%%)\n",
308 cache->name,
309 stats->true_hit_count, xpercentInt(stats->true_hit_count, stats->query_count),
310 stats->true_miss_count, xpercentInt(stats->true_miss_count, stats->query_count),
311 stats->true_hit_count + stats->true_miss_count,
312 xpercentInt(stats->true_hit_count + stats->true_miss_count, stats->query_count)
313 );
314 fprintf(stdout, "%s: f-hit: %d (%d%%) f-miss: %d (%d%%) f-*: %d (%d%%)\n",
315 cache->name,
316 stats->false_hit_count, xpercentInt(stats->false_hit_count, stats->query_count),
317 stats->false_miss_count, xpercentInt(stats->false_miss_count, stats->query_count),
318 stats->false_hit_count + stats->false_miss_count,
319 xpercentInt(stats->false_hit_count + stats->false_miss_count, stats->query_count)
320 );
c411be12 321}
322
6d80b36f 323#if 0
324
c411be12 325static int
6d80b36f 326cacheAddLog(Cache * idx, const char *fname)
c411be12 327{
328 FILE *file;
329 int scanned_count = 0;
330 assert(idx);
331 assert(fname && strlen(fname));
332
333 file = fopen(fname, "r");
334 if (!file) {
335 fprintf(stderr, "cannot open %s: %s\n", fname, strerror(errno));
336 return 0;
337 }
338 scanned_count = cacheIndexScanCleanPrefix(idx, fname, file);
339 fclose(file);
340 return scanned_count;
341}
342
343static void
6d80b36f 344cacheInitReport(Cache * cache)
c411be12 345{
346 assert(idx);
347 fprintf(stderr, "%s: bad swap_add: %d\n",
348 idx->name, idx->bad_add_count);
1afe05c5 349 fprintf(stderr, "%s: bad swap_del: %d\n",
c411be12 350 idx->name, idx->bad_del_count);
1afe05c5 351 fprintf(stderr, "%s: scanned lines: %d\n",
c411be12 352 idx->name, idx->scanned_count);
353}
354
355#if 0
356static int
6d80b36f 357cacheGetLogEntry(Cache * idx, storeSwapLogData * s)
c411be12 358{
359 if (!idx->has_log_entry)
360 cacheIndexStepLogEntry();
361 if (idx->has_log_entry) {
362 *s = idx->log_entry_buf;
363 return 1;
364 }
365 return 0;
366}
367
368static int
6d80b36f 369cacheStepLogEntry(Cache * cache)
c411be12 370{
371 if (fread(&idx->log_entry_buf, sizeof(idx->log_entry_buf), 1, idx->log) == 1) {
372 int op = (int) idx->log_entry_buf.op;
373 idx->scanned_count++;
374 idx->has_log_entry = 1;
375 if (op != SWAP_LOG_ADD && op != SWAP_LOG_DEL) {
376 fprintf(stderr, "%s:%d: unknown swap log action %d\n", idx->log_fname, idx->scanned_count, op);
377 exit(-3);
378 }
379 } else
380 idx->has_log_entry = 0;
381}
382
383static int
6d80b36f 384cacheScan(Cache * idx, const char *fname, FILE * file)
c411be12 385{
386 int count = 0;
387 int del_count = 0;
388 storeSwapLogData s;
389 fprintf(stderr, "%s scanning\n", fname);
390 while (fread(&s, sizeof(s), 1, file) == 1) {
391 count++;
392 idx->scanned_count++;
393 if (s.op == SWAP_LOG_ADD) {
394 CacheEntry *olde = (CacheEntry *) hash_lookup(idx->hash, s.key);
395 if (olde) {
396 idx->bad_add_count++;
397 } else {
398 CacheEntry *e = cacheEntryCreate(&s);
1afe05c5 399 hash_join(idx->hash, (hash_link *) e);
c411be12 400 idx->count++;
401 }
1afe05c5 402 } else if (s.op == SWAP_LOG_DEL) {
c411be12 403 CacheEntry *olde = (CacheEntry *) hash_lookup(idx->hash, s.key);
404 if (!olde)
405 idx->bad_del_count++;
406 else {
407 assert(idx->count);
1afe05c5 408 hash_remove_link(idx->hash, (hash_link *) olde);
c411be12 409 cacheEntryDestroy(olde);
410 idx->count--;
411 }
412 del_count++;
413 } else {
414 fprintf(stderr, "%s:%d: unknown swap log action\n", fname, count);
415 exit(-3);
416 }
417 }
418 fprintf(stderr, "%s scanned %d entries, alloc: %d bytes\n",
1afe05c5 419 fname, count,
420 (int) (count * sizeof(CacheEntry)));
c411be12 421 return count;
422}
423#endif
424
76e3f5c2 425/* Us */
426
76e3f5c2 427static int
6d80b36f 428cacheAddAccessLog(Cache * idx, const char *fname)
76e3f5c2 429{
430 FILE *file;
431 int scanned_count = 0;
432 assert(!idx);
433 assert(fname && strlen(fname));
434
435 file = fopen(fname, "r");
436 if (!file) {
437 fprintf(stderr, "cannot open %s: %s\n", fname, strerror(errno));
438 return 0;
439 }
440 scanned_count = cacheIndexScanAccessLog(idx, fname, file);
441 fclose(file);
442 return scanned_count;
443}
444
6d80b36f 445#endif
446
447static void
448cacheFetch(Cache *cache, const RawAccessLogEntry *e)
449{
450 assert(e);
451 if (e->use_icp)
452 cacheQueryPeer(cache, e->key);
453}
454
455static fr_result
456swapStateReader(FileIterator * fi)
457{
458 storeSwapLogData *entry;
459 if (!fi->entry)
460 fi->entry = xcalloc(1, sizeof(storeSwapLogData));
461 entry = fi->entry;
462 if (fread(entry, sizeof(*entry), 1, fi->file) != 1)
463 return frEof;
464 fi->inner_time = entry->lastref;
465 if (entry->op != SWAP_LOG_ADD && entry->op != SWAP_LOG_DEL) {
466 fprintf(stderr, "%s:%d: unknown swap log action\n", fi->fname, fi->line_count);
467 exit(-3);
468 }
469 return frOk;
470}
471
472static fr_result
473accessLogReader(FileIterator * fi)
76e3f5c2 474{
475 static char buf[4096];
6d80b36f 476 RawAccessLogEntry *entry;
477 char *url;
478 char *method;
479 int method_id = METHOD_NONE;
480 char *hier = NULL;
481
482 assert(fi);
483 if (!fi->entry)
484 fi->entry = xcalloc(1, sizeof(RawAccessLogEntry));
485 else
486 memset(fi->entry, 0, sizeof(RawAccessLogEntry));
487 entry = fi->entry;
488 if (!fgets(buf, sizeof(buf), fi->file))
489 return frEof; /* eof */
490 entry->timestamp = fi->inner_time = (time_t)atoi(buf);
491 url = strstr(buf, "://");
492 hier = url ? strstr(url, " - ") : NULL;
493
494 if (!url || !hier) {
495 /*fprintf(stderr, "%s:%d: strange access log entry '%s'\n",
496 * fname, scanned_count, buf); */
497 return frError;
498 }
499 method = url;
500 while (!isdigit(*method)) {
501 if (*method == ' ')
502 *method = '\0';
503 --method;
504 }
505 method += 2;
506 method_id = methodStrToId(method);
507 if (method_id == METHOD_NONE) {
508 /*fprintf(stderr, "%s:%d: invalid method %s in '%s'\n",
509 * fname, scanned_count, method, buf); */
510 return frError;
511 }
512 while (*url) url--;
513 url++;
514 *hier = '\0';
515 hier += 3;
516 *strchr(hier, '/') = '\0';
517 /*fprintf(stdout, "%s:%d: %s %s %s\n",
518 * fname, count, method, url, hier); */
519 entry->use_icp = /* no ICP lookup for these status codes */
520 strcmp(hier, "NONE") &&
521 strcmp(hier, "DIRECT") &&
522 strcmp(hier, "FIREWALL_IP_DIRECT") &&
523 strcmp(hier, "LOCAL_IP_DIRECT") &&
524 strcmp(hier, "NO_DIRECT_FAIL") &&
525 strcmp(hier, "NO_PARENT_DIRECT") &&
526 strcmp(hier, "SINGLE_PARENT") &&
527 strcmp(hier, "PASSTHROUGH_PARENT") &&
528 strcmp(hier, "SSL_PARENT_MISS") &&
529 strcmp(hier, "DEFAULT_PARENT");
530 if (!entry->use_icp)
531 return frMore;
532 memcpy(entry->key, storeKeyPublic(url, method_id), sizeof(entry->key));
533 /*fprintf(stdout, "%s:%d: %s %s %s %s\n",
534 fname, count, method, storeKeyText(entry->key), url, hier); */
535 return frOk;
536}
537
538
539static void
540cachePurge(Cache *cache, storeSwapLogData *s)
541{
542 CacheEntry *olde = (CacheEntry *) hash_lookup(cache->hash, s->key);
543 if (!olde) {
544 cache->bad_del_count++;
545 } else {
546 assert(cache->count);
547 hash_remove_link(cache->hash, (hash_link *) olde);
548 cacheEntryDestroy(olde);
549 cache->count--;
550 }
551}
552
553static void
554cacheStore(Cache *cache, storeSwapLogData *s, int update_digest)
555{
556 CacheEntry *olde = (CacheEntry *) hash_lookup(cache->hash, s->key);
557 if (olde) {
558 cache->bad_add_count++;
559 } else {
560 CacheEntry *e = cacheEntryCreate(s);
561 hash_join(cache->hash, (hash_link *) e);
562 cache->count++;
563 if (update_digest)
564 cacheDigestAdd(cache->digest, e->key);
565 }
566}
567
568static void
569cacheUpdateStore(Cache *cache, storeSwapLogData *s, int update_digest)
570{
571 switch (s->op) {
572 case SWAP_LOG_ADD:
573 cacheStore(cache, s, update_digest);
574 break;
575 case SWAP_LOG_DEL:
576 cachePurge(cache, s);
577 break;
578 default:
579 assert(0);
76e3f5c2 580 }
76e3f5c2 581}
582
c411be12 583static int
584usage(const char *prg_name)
585{
586 fprintf(stderr, "usage: %s <access_log> <swap_state> ...\n",
587 prg_name);
588 return -1;
589}
590
591int
592main(int argc, char *argv[])
593{
00b23815 594 FileIterator **fis = NULL;
595 const int fi_count = argc-1;
6d80b36f 596 int active_fi_count = 0;
597 Cache *them, *us;
598 int i;
c411be12 599
600 if (argc < 3)
601 return usage(argv[0]);
602
6d80b36f 603 them = cacheCreate("them");
604 us = cacheCreate("us");
605 them->peer = us;
606 us->peer = them;
607
00b23815 608 fis = xcalloc(fi_count, sizeof(FileIterator *));
609 /* init iterators with files */
610 fis[0] = fileIteratorCreate(argv[1], accessLogReader);
c411be12 611 for (i = 2; i < argc; ++i) {
00b23815 612 fis[i-1] = fileIteratorCreate(argv[i], swapStateReader);
c411be12 613 }
6d80b36f 614 /* read prefix to get start-up contents of the peer cache */
00b23815 615 for (i = 1; i < fi_count; ++i) {
6d80b36f 616 FileIterator *fi = fis[i];
617 while (fi->inner_time > 0) {
618 if (((storeSwapLogData*)fi->entry)->op != SWAP_LOG_ADD) {
619 break;
620 } else {
621 cacheStore(them, fi->entry, 0);
622 fileIteratorAdvance(fi);
623 }
624 }
00b23815 625 }
626 /* digest peer cache content */
6d80b36f 627 cacheResetDigest(them);
628 us->digest = cacheDigestClone(them->digest); /* @netw@ */
629
630 /* iterate, use the iterator with the smallest positive inner_time */
00b23815 631 cur_time = -1;
6d80b36f 632 do {
633 int next_i = -1;
634 time_t next_time = -1;
635 active_fi_count = 0;
636 for (i = 0; i < fi_count; ++i) {
637 if (fis[i]->inner_time >= 0) {
638 if (!active_fi_count || fis[i]->inner_time < next_time) {
639 next_i = i;
640 next_time = fis[i]->inner_time;
641 }
642 active_fi_count++;
643 }
00b23815 644 }
6d80b36f 645 if (next_i >= 0) {
646 /* skip access log entries recorder before creation of swap.state */
647 if (cur_time > 0 || next_i > 0) {
648 cur_time = next_time;
649 /*fprintf(stderr, "%2d time: %d %s", next_i, (int)cur_time, ctime(&cur_time));*/
650 if (next_i == 0)
651 cacheFetch(us, fis[next_i]->entry);
652 else
653 cacheUpdateStore(them, fis[next_i]->entry, 0);
654 }
655 fileIteratorAdvance(fis[next_i]);
656 }
657 } while (active_fi_count);
658
659 /* report */
660 cacheQueryReport(us, &us->qstats);
661
662 /* clean */
663 for (i = 0; i < argc-1; ++i) {
00b23815 664 fileIteratorDestroy(fis[i]);
665 }
666 xfree(fis);
6d80b36f 667 cacheDestroy(them);
668 cacheDestroy(us);
76e3f5c2 669 return 0;
c411be12 670}