chiark / gitweb /
debugging for thing that crashed
[innduct.git] / frontends / ovdb_stat.c
1 /*
2  * ovdb_stat.c
3  * print information about ovdb database
4  */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include <errno.h>
9 #include <signal.h>
10 #include <syslog.h>
11 #include <time.h>
12
13 #include "inn/innconf.h"
14 #include "inn/messages.h"
15 #include "libinn.h"
16 #include "ov.h"
17 #include "paths.h"
18 #include "storage.h"
19
20 #include "../storage/ovdb/ovdb.h"
21 #include "../storage/ovdb/ovdb-private.h"
22
23
24 #ifndef USE_BERKELEY_DB
25
26 int main(int argc UNUSED, char **argv UNUSED)
27 {
28     die("BerkeleyDB support not compiled");
29 }
30
31 #else /* USE_BERKELEY_DB */
32
33 static int signalled = 0;
34 static void sigfunc(int signum UNUSED)
35 {
36     signalled = 1;
37 }
38
39 static int html = 0;
40
41 typedef enum {
42         END,
43         INT32,  /* 'a' points to u_int32_t */
44         HEX32,  /* 'a' printed in hex */
45         DIFF32, /* 'a' - 'b' - 'c' */
46         PCT32,  /* 100 * 'a' / ('a' + 'b') */
47         FF,     /* 'a' = freebytes, 'b' = npages, 'c' = pagesize */
48         BYTES,  /* 'a' = bytes, 'b' = mbytes, 'c' = gbytes */
49         MODE,   /* 'a' points to int, printed as octal mode */
50         TIME,   /* 'a' points to time_t, printed as date/time */
51         LSN,    /* 'a' points to DB_LSN */
52         STR,    /* 'a' points to char* */
53         SIZE    /* 'a' points to size_t */
54 } DATATYPE;
55
56 struct datatab {
57     DATATYPE type;
58     ssize_t a;
59     ssize_t b;
60     ssize_t c;
61     const char *desc;
62 };
63
64 static void display_heading(const char *str)
65 {
66     if(html)
67         printf("<h2>%s<h2>\n", str);
68     else
69         printf("%s\n", str);
70 }
71
72
73 static void getval(int i, void *p, struct datatab *tab, char *val, char *sufx)
74 {
75     int mode = 0;
76     u_int32_t a = 0, b = 0, c = 0, bytes = 0, mbytes = 0, gbytes = 0;
77     char *cp = p;
78     char *tmp = NULL;
79     time_t tm = 0;
80     size_t sz = 0;
81     DB_LSN *dl = NULL;
82
83     val[0] = 0;
84     sufx[0] = 0;
85
86     switch(tab[i].type) {
87     case INT32: /* 'a' points to u_int32_t */
88         memcpy(&a, cp + tab[i].a, sizeof(a));
89         sprintf(val, "%u", a);
90         break;
91     case HEX32: /* 'a' printed in hex */
92         memcpy(&a, cp + tab[i].a, sizeof(a));
93         sprintf(val, "%x", a);
94         break;
95     case DIFF32:        /* 'a' - 'b' - 'c' */
96         memcpy(&a, cp + tab[i].a, sizeof(a));
97         memcpy(&b, cp + tab[i].b, sizeof(b));
98         if(tab[i].c != -1) {
99             memcpy(&c, cp + tab[i].c, sizeof(c));
100             sprintf(val, "%d", a - b - c);
101         } else {
102             sprintf(val, "%d", a - b);
103         }
104         break;
105     case PCT32: /* 100 * 'a' / ('a' + 'b') */
106         memcpy(&a, cp + tab[i].a, sizeof(a));
107         memcpy(&b, cp + tab[i].b, sizeof(b));
108         sprintf(val, "%.0f", (double) a / (a + b) * 100.0);
109         strcpy(sufx, "%");
110         break;
111     case FF:    /* 'a' = freebytes, 'b' = npages, 'c' = pagesize */
112         memcpy(&a, cp + tab[i].a, sizeof(a));
113         memcpy(&b, cp + tab[i].b, sizeof(b));
114         memcpy(&c, cp + tab[i].c, sizeof(c));
115         if(b == 0) {
116             sprintf(val, "%.0f", 0.0);
117         } else {
118             sprintf(val, "%.0f", (double)((b * c) - a) / (b * c) * 100);
119         }
120         strcpy(sufx, "%");
121         break;
122     case BYTES: /* 'a' = bytes, 'b' = mbytes, 'c' = gbytes */
123         if(tab[i].a != -1)
124             memcpy(&bytes, cp + tab[i].a, sizeof(bytes));
125         else
126             bytes = 0;
127         if(tab[i].b != -1)
128             memcpy(&mbytes, cp + tab[i].b, sizeof(mbytes));
129         else
130             mbytes = 0;
131         if(tab[i].c != -1)
132             memcpy(&gbytes, cp + tab[i].c, sizeof(gbytes));
133         else
134             gbytes = 0;
135         if(gbytes > 0 || mbytes > 0) {
136             mbytes += gbytes * 1024;
137             if(bytes > (1024*1024))
138                 mbytes += bytes / (1024*1024);
139             sprintf(val, "%u", mbytes);
140             strcpy(sufx, "MB");
141         } else {
142             sprintf(val, "%u", bytes);
143         }
144         break;
145     case MODE:  /* 'a' points to int, printed as octal mode */
146         memcpy(&mode, cp + tab[i].a, sizeof(mode));
147         sprintf(val, "%04o", mode);
148         break;  
149     case TIME:  /* 'a' points to time_t, printed as date/time */
150         memcpy(&tm, cp + tab[i].a, sizeof(tm));
151         if(tm == 0) {
152             strcpy(val, "none");
153         } else {
154             strftime(val, SMBUF, "%Y-%m-%d %T %Z", localtime(&tm));
155         }
156         break;
157     case LSN:   /* 'a' points to DB_LSN */
158         dl = (DB_LSN *)(cp + tab[i].a);
159         if(dl->file == 0) {
160             strcpy(val, "none");
161         } else {
162             sprintf(val, "%u/%u", dl->file, dl->offset);
163         }
164         break;
165     case STR:   /* 'a' points to char* */
166         memcpy(&tmp, cp + tab[i].a, sizeof(tmp));
167         strcpy(val, tmp);
168         break;
169     case SIZE:  /* 'a' points to size_t */
170         memcpy(&sz, cp + tab[i].a, sizeof(sz));
171         sprintf(val, "%lu", (unsigned long) sz);
172         break;
173     case END:
174         break;
175     }
176 }
177
178 static char *myctime(time_t *tm)
179 {
180     static char val[SMBUF];
181     strftime(val, SMBUF, "%Y-%m-%d %T %Z", localtime(tm));
182     return val;
183 }
184
185 static void display_data(void *p, struct datatab *tab)
186 {
187     int i;
188     char val[SMBUF], sufx[SMBUF];
189     if(html)
190         puts("<table border=0 cellpadding=1>");
191     for(i = 0; tab[i].type != END; i++) {
192         getval(i, p, tab, val, sufx);
193         if(html)
194             printf("<tr><td align=right>%s<td>%s<td>%s\n", val, sufx, tab[i].desc);
195         else
196             printf("%16s%-2s %s\n", val, sufx, tab[i].desc);
197     }
198     if(html)
199         puts("</table><p>");
200 }
201
202 static void start_table(const char *label, struct datatab *tab)
203 {
204     int i;
205     if(html) {
206         printf("<h2>%s</h2>\n", label);
207         puts("<table border=0 cellpadding=1>\n<tr bgcolor=#3399aa>");
208         for(i = 0; tab[i].type != END; i++)
209             printf("<th colspan=2>%s\n", tab[i].desc);
210     }
211 }
212
213 static void display_row(void *p, struct datatab *tab)
214 {
215     int i;
216     char val[SMBUF], sufx[SMBUF];
217     if(html) {
218         puts("<tr>");
219         for(i = 0; tab[i].type != END; i++) {
220             getval(i, p, tab, val, sufx);
221             printf("<td align=right>%s<td>%s\n", val, sufx);
222         }
223     } else {
224         puts("---------------------------------------------");
225         display_data(p, tab);
226     }
227 }
228
229 static void end_table(void)
230 {
231     if(html)
232         puts("</table><p>");
233 }
234
235 #define OFFSETOF(type, f) ((char *)&(((type *)0)->f) - (char *)0)
236
237 #define F(f) OFFSETOF(DB_LOCK_STAT, f)
238
239 static struct datatab LOCK_tab[] = {
240 #if DB_VERSION_MAJOR >= 3
241  { INT32,
242 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
243    F(st_id),
244 #else
245    F(st_lastid),
246 #endif
247    -1, -1,           "Last allocated locker ID" },
248 #endif
249  { INT32, F(st_maxlocks),      -1, -1,           "Maximum number of locks possible" },
250 #if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 2)
251  { INT32, F(st_maxlockers),    -1, -1,           "Maximum number of lockers possible" },
252  { INT32, F(st_maxobjects),    -1, -1,           "Maximum number of objects possible" },
253 #endif
254  { INT32, F(st_nmodes),        -1, -1,           "Lock modes" },
255 #if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 2)
256  { INT32, F(st_nlocks),        -1, -1,           "Current locks" },
257  { INT32, F(st_maxnlocks),     -1, -1,           "Maximum locks" },
258 #endif
259  { INT32, F(st_nlockers),      -1, -1,           "Current lockers" },
260 #if DB_VERSION_MAJOR >= 3
261  { INT32, F(st_maxnlockers),   -1, -1,           "Maximum lockers" },
262 #else
263  { INT32, F(st_numobjs),       -1, -1,           "Lock objects" },
264 #endif
265 #if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 2)
266  { INT32, F(st_nobjects),      -1, -1,           "Current objects" },
267  { INT32, F(st_maxnobjects),   -1, -1,           "Maximum objects" },
268 #endif
269 #if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 4
270  { INT32, F(st_lock_wait),     -1, -1,           "Lock conflicts" },
271 #else
272  { INT32, F(st_nconflicts),    -1, -1,           "Lock conflicts" },
273 #endif
274  { INT32, F(st_nrequests),     -1, -1,           "Lock requests" },
275  { INT32, F(st_nreleases),     -1, -1,           "Lock releases" },
276  { DIFF32, F(st_nrequests), F(st_nreleases), F(st_ndeadlocks), "Outstanding locks" },
277 #if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 4
278  { INT32, F(st_lock_nowait),   -1, -1,           "Lock conflicts w/o subsequent wait" },
279 #elif DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR > 0)
280  { INT32, F(st_nnowaits),      -1, -1,           "Lock conflicts w/o subsequent wait" },
281 #endif
282  { INT32, F(st_ndeadlocks),    -1, -1,           "Deadlocks" },
283 #if DB_VERSION_MAJOR >= 4
284  { INT32, F(st_nlocktimeouts), -1, -1,           "Lock timeouts" },
285  { INT32, F(st_ntxntimeouts),  -1, -1,           "Transaction timeouts" },
286 #endif
287  { INT32, F(st_region_nowait), -1, -1,           "Region locks granted without waiting" },
288  { INT32, F(st_region_wait),   -1, -1,           "Region locks granted after waiting" },
289  { BYTES, F(st_regsize),       -1, -1,           "Lock region size" },
290  { END, -1, -1, -1, NULL }
291 };
292
293 static int display_lock(void)
294 {
295     DB_LOCK_STAT *sp;
296
297 #if DB_VERSION_MAJOR == 2
298     if(lock_stat(OVDBenv->lk_info, &sp, NULL) != 0)
299 #elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
300     if(lock_stat(OVDBenv, &sp, NULL) != 0)
301 #elif DB_VERSION_MAJOR == 3
302     if(lock_stat(OVDBenv, &sp) != 0)
303 #else
304     if(OVDBenv->lock_stat(OVDBenv, &sp, 0) != 0)
305 #endif
306         return 1;
307
308     display_heading("Lock Region Statistics");
309     display_data(sp, LOCK_tab);
310
311     free(sp);
312     return 0;
313 }
314
315
316 #undef F
317 #define F(f) OFFSETOF(DB_LOG_STAT, f)
318
319 static struct datatab LOG_tab[] = {
320  { HEX32, F(st_magic),             -1, -1, "Log magic number" },
321  { INT32, F(st_version),           -1, -1, "Log version number" },
322  { MODE,  F(st_mode),              -1, -1, "Log file mode" },
323 #if DB_VERSION_MAJOR >= 3
324  { BYTES, F(st_lg_bsize),          -1, -1, "Log record cache size" },
325 #endif
326 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
327  { BYTES, F(st_lg_size),           -1, -1, "The current log file size" },
328 #else
329  { BYTES, F(st_lg_max),            -1, -1, "Max log file size" },
330 #endif
331  { BYTES, F(st_w_bytes), F(st_w_mbytes), -1, "Log bytes written" },
332  { BYTES, F(st_wc_bytes), F(st_wc_mbytes), -1, "Log bytes written since last checkpoint" },
333  { INT32, F(st_wcount),            -1, -1, "Total log writes" },
334 #if DB_VERSION_MAJOR >= 3
335  { INT32, F(st_wcount_fill),       -1, -1, "Total log writes due to overflow" },
336 #endif
337  { INT32, F(st_scount),            -1, -1, "Total log flushes" },
338  { INT32, F(st_region_nowait),     -1, -1, "Region locks granted without waiting" },
339  { INT32, F(st_region_wait),       -1, -1, "Region locks granted after waiting" },
340  { INT32, F(st_cur_file),          -1, -1, "Current log file number" },
341  { INT32, F(st_cur_offset),        -1, -1, "Current log file offset" },
342 #if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 3)
343  { INT32, F(st_disk_file),         -1, -1, "Known on disk log file number" },
344  { INT32, F(st_disk_offset),       -1, -1, "Known on disk log file offset" },
345 #endif
346  { BYTES, F(st_regsize),           -1, -1, "Log region size" },
347 #if DB_VERSION_MAJOR >= 4
348 #if DB_VERSION_MINOR < 1
349  { INT32, F(st_flushcommit),       -1, -1, "Flushes containing a commit"},
350 #endif
351  { INT32, F(st_maxcommitperflush), -1, -1, "Max number of commits in a flush"},
352  { INT32, F(st_mincommitperflush), -1, -1, "Min number of commits in a flush"},
353 #endif
354  { END, -1, -1, -1, NULL }
355 };
356
357 static int display_log(void)
358 {
359     DB_LOG_STAT *sp;
360
361 #if DB_VERSION_MAJOR == 2
362     if(log_stat(OVDBenv->lg_info, &sp, NULL) != 0)
363 #elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
364     if(log_stat(OVDBenv, &sp, NULL) != 0)
365 #elif DB_VERSION_MAJOR == 3
366     if(log_stat(OVDBenv, &sp) != 0)
367 #else
368     if(OVDBenv->log_stat(OVDBenv, &sp, 0) != 0)
369 #endif
370         return 1;
371
372     display_heading("Log Region Statistics");
373     display_data(sp, LOG_tab);
374
375     free(sp);
376     return 0;
377 }
378
379
380 #undef F
381 #define F(f) OFFSETOF(DB_MPOOL_STAT, f)
382
383 static struct datatab MEM_tab[] = {
384  { INT32, F(st_cache_hit),  -1, -1,       "Cache hits"},
385  { INT32, F(st_cache_miss), -1, -1,       "Cache misses"},
386  { PCT32, F(st_cache_hit), F(st_cache_miss), -1, "Cache hit percentage"},
387 #if DB_VERSION_MAJOR == 2
388  { INT32, F(st_cachesize),  -1, -1,       "Total cache size"},
389  { INT32, F(st_regsize),    -1, -1,       "Pool region size"},
390 #else
391  { BYTES, F(st_bytes), -1, F(st_gbytes), "Total cache size"},
392 #if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
393  { INT32, F(st_regsize),    -1, -1,       "Pool region size"},
394 #else
395  { INT32, F(st_ncache),     -1, -1,       "Number of caches"},
396  { INT32, F(st_regsize),    -1, -1,       "Pool individual cache size"},
397 #endif
398 #endif
399  { INT32, F(st_map),           -1, -1, "Memory mapped pages"},
400  { INT32, F(st_page_create),   -1, -1, "Pages created in the cache"},
401  { INT32, F(st_page_in),       -1, -1, "Pages read into the cache"},
402  { INT32, F(st_page_out),      -1, -1, "Pages written from the cache to the backing file"},
403  { INT32, F(st_ro_evict),      -1, -1, "Clean pages forced from the cache"},
404  { INT32, F(st_rw_evict),      -1, -1, "Dirty pages forced from the cache"},
405  { INT32, F(st_hash_buckets),  -1, -1, "Hash buckets used for page location"},
406  { INT32, F(st_hash_searches), -1, -1, "Total hash chain searches"},
407  { INT32, F(st_hash_longest),  -1, -1, "Longest hash chain searched"},
408  { INT32, F(st_hash_examined), -1, -1, "Total hash entries searched"},
409  { INT32, F(st_page_trickle),  -1, -1, "Dirty buffers written by trickle-sync thread"},
410  { INT32, F(st_page_clean),    -1, -1, "Current clean buffer count"},
411  { INT32, F(st_page_dirty),    -1, -1, "Current dirty buffer count"},
412  { INT32, F(st_region_nowait), -1, -1, "Region locks granted without waiting"},
413  { INT32, F(st_region_wait),   -1, -1, "Region locks granted after waiting"},
414  { END, -1, -1, -1, NULL }
415 };
416
417 #undef F
418 #define F(f) OFFSETOF(DB_MPOOL_FSTAT, f)
419
420 static struct datatab MEMF_tab[] = {
421  { STR,    F(file_name),        -1, -1, "Database"},
422  { SIZE,   F(st_pagesize),      -1, -1, "Page size"},
423  { INT32,  F(st_cache_hit),     -1, -1, "Cache hits"},
424  { INT32,  F(st_cache_miss),    -1, -1, "Cache misses"},
425  { PCT32,  F(st_cache_hit), F(st_cache_miss), -1, "Cache hit percentage"},
426  { INT32,  F(st_map),           -1, -1, "Memory mapped pages"},
427  { INT32,  F(st_page_create),   -1, -1, "Pages created in the cache"},
428  { INT32,  F(st_page_in),       -1, -1, "Pages read into the cache"},
429  { INT32,  F(st_page_out),      -1, -1, "Pages written from the cache to the backing file"},
430  { END, -1, -1, -1, NULL }
431 };
432
433 static int display_mem(int all)
434 {
435     DB_MPOOL_FSTAT **fsp;
436     DB_MPOOL_STAT *gsp;
437
438 #if DB_VERSION_MAJOR == 2
439     if(memp_stat(OVDBenv->mp_info, &gsp, &fsp, NULL) != 0)
440 #elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
441     if(memp_stat(OVDBenv, &gsp, &fsp, NULL) != 0)
442 #elif DB_VERSION_MAJOR == 3
443     if(memp_stat(OVDBenv, &gsp, &fsp) != 0)
444 #else
445     if(OVDBenv->memp_stat(OVDBenv, &gsp, &fsp, 0) != 0)
446 #endif
447         return 1;
448
449     display_heading("Memory Pool Statistics");
450     display_data(gsp, MEM_tab);
451
452     if(all) {
453         DB_MPOOL_FSTAT **p = fsp;
454
455         start_table("Per-database Memory Pool Statistics", MEMF_tab);
456         for(; p != NULL && *p != NULL; ++p) {
457             display_row(*p, MEMF_tab);
458         }
459         end_table();
460     }
461
462     free(fsp);
463     free(gsp);
464     return 0;
465 }
466
467 static int txn_compare(const void *a, const void *b)
468 {
469     if (((const DB_TXN_ACTIVE *)a)->txnid > ((const DB_TXN_ACTIVE *)b)->txnid)
470         return 1;
471     if (((const DB_TXN_ACTIVE *)a)->txnid < ((const DB_TXN_ACTIVE *)b)->txnid)
472         return -1;
473     return 0;
474 }
475
476 #undef F
477 #define F(f) OFFSETOF(DB_TXN_STAT, f)
478
479 static struct datatab TXN_tab[] = {
480  { LSN, F(st_last_ckp),     -1, -1, "File/offset for last checkpoint LSN" },
481 #if DB_VERSION_MAJOR < 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 1)
482  { LSN, F(st_pending_ckp),  -1, -1, "File/offset for last pending checkpoint LSN" },
483 #endif
484  { TIME, F(st_time_ckp),    -1, -1, "Checkpoint timestamp" },
485  { HEX32, F(st_last_txnid), -1, -1, "Last transaction ID allocated" },
486  { INT32, F(st_maxtxns),    -1, -1, "Maximum active transactions possible" },
487  { INT32, F(st_nactive),    -1, -1, "Active transactions" },
488 #if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 3)
489  { INT32, F(st_nrestores),  -1, -1, "Restored transactions after recovery" },
490 #endif
491 #if DB_VERSION_MAJOR >= 3
492  { INT32, F(st_maxnactive), -1, -1, "Maximum active transactions" },
493 #endif
494  { INT32, F(st_nbegins),    -1, -1, "Transactions started" },
495  { INT32, F(st_ncommits),   -1, -1, "Transactions committed" },
496  { INT32, F(st_naborts),    -1, -1, "Transactions aborted" },
497  { INT32, F(st_region_nowait), -1, -1, "Region locks granted without waiting"},
498  { INT32, F(st_region_wait),   -1, -1, "Region locks granted after waiting"},
499  { BYTES, F(st_regsize),    -1, -1, "Transaction region size" },
500  { END, -1, -1, -1, NULL }
501 };
502
503 #undef F
504 #define F(f) OFFSETOF(DB_TXN_ACTIVE, f)
505
506 static struct datatab TXNA_tab[] = {
507  { INT32, F(txnid),    -1, -1, "Transaction ID" },
508 #if DB_VERSION_MAJOR >= 3
509  { INT32, F(parentid), -1, -1, "Parent Transaction ID" },
510 #endif
511  { LSN,   F(lsn),      -1, -1, "Initial LSN file/offset" },
512  { END, -1, -1, -1, NULL }
513 };
514
515 static int display_txn(void)
516 {
517     DB_TXN_STAT *sp;
518     u_int32_t i;
519
520 #if DB_VERSION_MAJOR == 2
521     if(txn_stat(OVDBenv->tx_info, &sp, NULL) != 0)
522 #elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
523     if(txn_stat(OVDBenv, &sp, NULL) != 0)
524 #elif DB_VERSION_MAJOR == 3
525     if(txn_stat(OVDBenv, &sp) != 0)
526 #else
527     if(OVDBenv->txn_stat(OVDBenv, &sp, 0) != 0)
528 #endif
529         return 1;
530
531     display_heading("Transaction Region Statistics");
532     display_data(sp, TXN_tab);
533
534     if(sp->st_nactive) {
535         qsort(sp->st_txnarray, sp->st_nactive, sizeof(sp->st_txnarray[0]), txn_compare);
536         start_table("Active Transactions", TXNA_tab);
537         for (i = 0; i < sp->st_nactive; ++i)
538             display_row(&(sp->st_txnarray[i]), TXNA_tab);
539         end_table();
540     }
541     free(sp);
542     return 0;
543 }
544
545 static int display_ver(void)
546 {
547     if(html) puts("<p>");
548     printf("ovdb data version: %d\n", DATA_VERSION);
549     if(html) puts("<br>");
550     printf("BerkeleyDB version: %s\n", db_version(NULL,NULL,NULL));
551     if(html) puts("<p>");
552     return 0;
553 }
554
555 #undef F
556 #define F(f) OFFSETOF(DB_BTREE_STAT, f)
557
558 static struct datatab BTREE_tab[] = {
559  { HEX32, F(bt_magic), -1, -1, "Btree magic number" },
560  { INT32, F(bt_version), -1, -1, "Btree version number" },
561  { INT32, F(bt_minkey), -1, -1, "Minimum keys per page (minkey)" },
562  { INT32, F(bt_pagesize), -1, -1, "Database page size" },
563  { INT32, F(bt_levels), -1, -1, "Levels in the tree" },
564 #if DB_VERSION_MAJOR == 2 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0)
565  { INT32, F(bt_nrecs), -1, -1, "Keys in the tree" },
566 #else
567  { INT32, F(bt_nkeys), -1, -1, "Unique keys in the tree" },
568  { INT32, F(bt_ndata), -1, -1, "Data items in the tree" },
569 #endif
570  { INT32, F(bt_int_pg), -1, -1, "Tree internal pages" },
571  { BYTES, F(bt_int_pgfree), -1, -1, "Bytes free in internal pages" },
572  { FF,    F(bt_int_pgfree), F(bt_int_pg), F(bt_pagesize), "Internal page fill factor" },
573
574  { INT32, F(bt_leaf_pg), -1, -1, "Tree leaf pages" },
575  { BYTES, F(bt_leaf_pgfree), -1, -1, "Bytes free in leaf pages" },
576  { FF,    F(bt_leaf_pgfree), F(bt_leaf_pg), F(bt_pagesize), "Leaf page fill factor" },
577
578  { INT32, F(bt_dup_pg), -1, -1, "Tree duplicate pages" },
579  { BYTES, F(bt_dup_pgfree), -1, -1, "Bytes free in duplicate pages" },
580  { FF,    F(bt_dup_pgfree), F(bt_dup_pg), F(bt_pagesize), "Duplicate page fill factor" },
581
582  { INT32, F(bt_over_pg), -1, -1, "Tree overflow pages" },
583  { BYTES, F(bt_over_pgfree), -1, -1, "Bytes free overflow pages" },
584  { FF,    F(bt_over_pgfree), F(bt_over_pg), F(bt_pagesize), "Overflow page fill factor" },
585
586 #if DB_VERSION_MAJOR >= 3
587  { INT32, F(bt_free), -1, -1, "Pages on the free list" },
588 #endif
589  { END, -1, -1, -1, NULL }
590 };
591
592 static int display_btree(DB *db)
593 {
594     DB_BTREE_STAT *sp;
595
596 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
597     if(db->stat(db, NULL, &sp, 0))
598 #else
599 #if DB_VERSION_MAJOR == 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 3)
600     if(db->stat(db, &sp, 0))
601 #else
602     if(db->stat(db, &sp, NULL, 0))
603 #endif
604 #endif
605         return 1;
606
607     display_heading("Btree Statistics");
608     display_data(sp, BTREE_tab);
609
610     free(sp);
611     return 0;
612 }
613
614
615 #if DB_VERSION_MAJOR >= 3
616
617 #undef F
618 #define F(f) OFFSETOF(DB_HASH_STAT, f)
619
620 static struct datatab HASH_tab[] = {
621  { HEX32, F(hash_magic), -1, -1, "Hash magic number" },
622  { INT32, F(hash_version), -1, -1, "Hash version number" },
623  { INT32, F(hash_pagesize), -1, -1, "Database page size" },
624 #if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
625  { INT32, F(hash_nrecs), -1, -1, "Keys in the database" },
626 #else
627  { INT32, F(hash_nkeys), -1, -1, "Keys in the database" },
628  { INT32, F(hash_ndata), -1, -1, "Data items in the database" },
629 #endif
630  { INT32, F(hash_buckets), -1, -1, "Hash buckets" },
631  { BYTES, F(hash_bfree), -1, -1, "Bytes free on bucket pages" },
632  { FF,    F(hash_buckets), F(hash_bfree), F(hash_pagesize), "Bucket page fill factor" },
633
634  { INT32, F(hash_bigpages), -1, -1, "Overflow pages" },
635  { BYTES, F(hash_big_bfree), -1, -1, "Bytes free on Overflow pages" },
636  { FF,    F(hash_bigpages), F(hash_big_bfree), F(hash_pagesize), "Overflow page fill factor" },
637
638  { INT32, F(hash_overflows), -1, -1, "Bucket overflow pages" },
639  { BYTES, F(hash_ovfl_free), -1, -1, "Bytes free on bucket overflow pages" },
640  { FF,    F(hash_overflows), F(hash_ovfl_free), F(hash_pagesize), "Bucket overflow page fill factor" },
641
642  { INT32, F(hash_dup), -1, -1, "Duplicate pages" },
643  { BYTES, F(hash_dup_free), -1, -1, "Bytes free in duplicate pages" },
644  { FF,    F(hash_dup), F(hash_dup_free), F(hash_pagesize), "Duplicate page fill factor" },
645
646  { INT32, F(hash_free), -1, -1, "Pages on the free list"},
647  { END, -1, -1, -1, NULL }
648 };
649 #endif
650
651 static int display_hash(DB *db UNUSED)
652 {
653 #if DB_VERSION_MAJOR == 2
654     printf("Hash statistics not available.\n");
655     return 0;
656 #else
657     DB_HASH_STAT *sp;
658
659 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
660     if(db->stat(db, NULL, &sp, 0))
661 #else
662 #if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
663     if(db->stat(db, &sp, NULL, 0))
664 #else
665     if(db->stat(db, &sp, 0))
666 #endif
667 #endif
668         return 1;
669
670     display_heading("Hash Information");
671     display_data(sp, HASH_tab);
672
673     return 0;
674 #endif
675 }
676
677 static int display_db(char *dbfile)
678 {
679     int ret;
680     DB *db;
681
682 #if DB_VERSION_MAJOR == 2
683     if(db_open(dbfile, DB_UNKNOWN, DB_RDONLY, 0, OVDBenv, NULL, &db))
684         return 1;
685 #else
686     if(db_create(&db, OVDBenv, 0))
687         return 1;
688 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
689     if(db->open(db, NULL, dbfile, NULL, DB_UNKNOWN, DB_RDONLY, 0))
690 #else
691     if(db->open(db, dbfile, NULL, DB_UNKNOWN, DB_RDONLY, 0))
692 #endif
693         return 1;
694 #endif
695
696     switch(db->type) {
697     case DB_BTREE:
698     case DB_RECNO:
699         ret = display_btree(db);
700         break;
701     case DB_HASH:
702         ret = display_hash(db);
703         break;
704     default:
705         ret = 1;
706         break;
707     }
708     db->close(db, 0);
709     return ret;
710 }
711
712 static int parse_artrange(char *str, ARTNUM *start, ARTNUM *stop)
713 {
714     char *c;
715     int i;
716
717     c = strchr(str, '-');
718     if(c == NULL) {
719         i = atoi(str);
720         if(i == 0) {
721             return 1;
722         }
723         *start = *stop = i;
724         return 0;
725     }
726     if(c == str) {
727         *start = 0;
728         *stop = atoi(str+1);
729         return (*stop == 0);
730     }
731     if (strlen(str) == (size_t)(c - str + 1)) {
732         *start = atoi(str);
733         *stop = 0xffffffff;
734         return (*start == 0);
735     }
736     *start = atoi(str);
737     *stop = atoi(c+1);
738     if(*start == 0 || *stop == 0 || *start > *stop)
739         return 1;
740
741     return 0;
742 }
743
744 static void htwrite(char *data, int len)
745 {
746     int i;
747     for(i = 0; i < len; i++) {
748         switch(data[i]) {
749         case '<':
750         case '>':
751         case '&':
752             printf("&#%d;", (int)data[i]);
753             break;
754         default:
755             putchar(data[i]);
756         }
757     }
758 }
759
760 int main(int argc, char *argv[])
761 {
762     void *s;
763     ARTNUM a, start=0, stop=0, low, high;
764     char *data, *disp_db = NULL;
765     int len, c, count, flag, lowi, highi;
766     int getgs=0, getcount=0, getinfo=0, err=0, gotone=0;
767     int disp_lock=0, disp_log=0, disp_mem=0, disp_mem_all=0, disp_txn=0, disp_ver=0;
768     int needng=0, o;
769
770     openlog("ovdb_stat", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
771     message_program_name = "ovdb_stat";
772
773     if (!innconf_read(NULL))
774         exit(1);
775
776     if(!ovdb_check_user())
777         die("command must be run as user " NEWSUSER);
778     if(!ovdb_getlock(OVDB_LOCK_ADMIN))
779         sysdie("cannot lock database");
780     if(!ovdb_open(OV_READ|OVDB_SERVER))
781         sysdie("cannot open overview; check syslog for OVDB messages");
782
783     xsignal(SIGINT, sigfunc);
784     xsignal(SIGTERM, sigfunc);
785     xsignal(SIGHUP, sigfunc);
786
787     while((c = getopt(argc, argv, ":Hgcir:klmMtvd:")) != -1) {
788         switch(c) {
789         case 'H':
790             html = 1;
791             break;
792         case 'g':
793             getgs = 1;
794             needng = 1;
795             gotone++;
796             break;
797         case 'c':
798             getcount = 1;
799             needng = 1;
800             gotone++;
801             break;
802         case 'i':
803             getinfo = 1;
804             needng = 1;
805             gotone++;
806             break;
807         case 'r':
808             if(parse_artrange(optarg, &start, &stop))
809                 err++;
810             needng = 1;
811             gotone++;
812             break;
813         case 'k':
814             disp_lock = 1;
815             gotone++;
816             break;
817         case 'l':
818             disp_log = 1;
819             gotone++;
820             break;
821         case 'm':
822             disp_mem = 1;
823             gotone++;
824             break;
825         case 'M':
826             disp_mem = 1;
827             disp_mem_all = 1;
828             gotone++;
829             break;
830         case 't':
831             disp_txn = 1;
832             gotone++;
833             break;
834         case 'v':
835             disp_ver = 1;
836             gotone++;
837             break;
838         case 'd':
839             disp_db = optarg;
840             gotone++;
841             break;
842         case ':':
843             warn("option -%c requires an argument", optopt);
844             err++;
845             break;
846         case '?':
847             warn("unrecognized option -%c", optopt);
848             err++;
849             break;
850         }
851     }
852     if(!gotone) {
853         err++;
854     } else if(optind == argc && needng) {
855         warn("missing newsgroup argument(s)");
856         err++;
857     }
858     if(err) {
859         fprintf(stderr, "\
860 Usage:\n\
861    ovdb_stat -Hgci [-r artnum] newsgroup [newsgroup ...]\n\
862       -H              : output in HTML\n\
863       -g              : show groupstats info\n\
864       -c              : show groupstats info by counting actual records\n\
865       -i              : show additional group info\n\
866       -r artnum-range : retrieve OV records for article number range\n\
867 \n\
868    ovdb_stat -Hklmtv [-d <database>]\n\
869       -H          : output in HTML\n\
870       -k          : Display lock region statistics\n\
871       -l          : Display log region statistics\n\
872       -m          : Display global memory cache statistics\n\
873       -M          : Display all memory cache statistics\n\
874       -t          : Display transaction statistics\n\
875       -v          : Display version information\n\
876       -d database : Display statistics of specified database\n");
877
878         goto out;
879     }
880
881     if(html)
882         puts("<html><head><title>ovdb_stat</title></head><body><p>");
883     if(disp_lock)
884         display_lock();
885     if(disp_log)
886         display_log();
887     if(disp_mem)
888         display_mem(disp_mem_all);
889     if(disp_txn)
890         display_txn();
891     if(disp_ver)
892         display_ver();
893     if(disp_db)
894         display_db(disp_db);
895
896     if(getgs || getcount || getinfo) {
897         if(html) {
898             puts("<table border=0 cellpadding=1 width=90%>\n<tr bgcolor=#3399aa>");
899             puts("<th rowspan=2>Group");
900             if(getgs)
901                 puts("<th colspan=4>Groupstats");
902             if(getcount)
903                 puts("<th colspan=3>Counted");
904             if(getinfo)
905                 puts("<th>Status<th colspan=2>Current<th colspan=2>Pending");
906             puts("<th rowspan=2>Expired<th rowspan=2>Expire PID<tr bgcolor=#3399aa>");
907             if(getgs)
908                 puts("<th>Low<th>High<th>Count<th>Flag");
909             if(getcount)
910                 puts("<th>Low<th>High<th>Count");
911             if(getinfo)
912                 puts("<th>Flags<th>GroupID<th>DB<th>GroupID<th>DB");
913         }
914         for(o = optind ; o < argc; o++) {
915             if(html)
916                 printf("<tr><td>%s", argv[o]);
917             if(getgs) {
918                 if(ovdb_groupstats(argv[o], &lowi, &highi, &count, &flag)) {
919                     if(html)
920                         printf("<td>%d<td>%d<td>%d<td>%c", lowi, highi, count, flag);
921                     else
922                         printf("%s: groupstats: low: %d, high: %d, count: %d, flag: %c\n",
923                                 argv[o], lowi, highi, count, flag);
924                 }
925             }
926             if(getcount) {
927                 low = high = count = 0;
928                 s = ovdb_opensearch(argv[o], 1, 0xffffffff);
929                 if (s != NULL) {
930                     while(ovdb_search(s, &a, NULL, NULL, NULL, NULL)) {
931                         if(low == 0 || a < low)
932                             low = a;
933                         if(a > high)
934                             high = a;
935                         count++;
936                         if(signalled)
937                             break;
938                     }
939                     ovdb_closesearch(s);
940                     if(signalled)
941                         goto out;
942                     if(html)
943                         printf("<td>%ld<td>%ld<td>%d", low, high, count);
944                     else
945                         printf("%s:    counted: low: %ld, high: %ld, count: %d\n",
946                                 argv[o], low, high, count);
947                 }
948             }
949             if(getinfo) {
950                 int ret;
951                 struct groupinfo gi;
952
953                 ret = ovdb_getgroupinfo(argv[o], &gi, false, NULL, 0);
954                 if (ret != 0) {
955                     warn("%s: ovdb_getgroupinfo error: %s", argv[o],
956                          db_strerror(ret));
957                     continue;
958                 }
959                 if(html) {
960                     printf("<td>%s%s%s%s",
961                         (gi.status & GROUPINFO_DELETED) ? "D ":"",
962                         (gi.status & GROUPINFO_EXPIRING) ? "E ":"",
963                         (gi.status & GROUPINFO_MOVING) ? "M":"",
964                         (gi.status == 0) ? "&nbsp;":"");
965                     printf("<td>%d<td>ov%05d", gi.current_gid, gi.current_db);
966                     if(gi.status & GROUPINFO_MOVING)
967                         printf("<td>%d<td>ov%05d", gi.new_gid, gi.new_db);
968                     else
969                         printf("<td>&nbsp;<td>&nbsp;");
970                     if(gi.expired)
971                         printf("<td>%s<td>%lu", myctime(&gi.expired),
972                                (unsigned long) gi.expiregrouppid);
973                     else
974                         printf("<td>&nbsp;<td>&nbsp;");
975                     putchar('\n');
976                 } else {
977                     printf("%s: flags: %s%s%s%s\n", argv[o],
978                         (gi.status & GROUPINFO_DELETED) ? "DELETED ":"",
979                         (gi.status & GROUPINFO_EXPIRING) ? "EXPIRING ":"",
980                         (gi.status & GROUPINFO_MOVING) ? "MOVING":"",
981                         (gi.status == 0) ? "none":"");
982
983                     printf("%s: gid: %d;  Stored in: ov%05d\n", argv[o], gi.current_gid, gi.current_db);
984                     if(gi.status & GROUPINFO_MOVING)
985                         printf("%s: pending gid: %d;  pending db: ov%05d\n", argv[o], gi.new_gid, gi.new_db);
986                     if(gi.expired) {
987                         printf("%s: last expired: %s\n", argv[o], myctime(&gi.expired));
988                         printf("%s: by process id: %lu\n", argv[o],
989                                (unsigned long) gi.expiregrouppid);
990                     }
991                 }
992             }
993             if(signalled)
994                 goto out;
995         }
996         if(html)
997             puts("</table><p>");
998     }
999     if(start || stop) {
1000         if(html)
1001             puts("<pre>");
1002         for(o = optind ; o < argc; o++) {
1003             s = ovdb_opensearch(argv[o], start, stop);
1004             if (s != NULL) {
1005                 while(ovdb_search(s, &a, &data, &len, NULL, NULL)) {
1006                     if(html)
1007                         htwrite(data, len);
1008                     else
1009                         fwrite(data, len, 1, stdout);
1010                     if(signalled)
1011                         break;
1012                 }
1013                 ovdb_closesearch(s);
1014                 if(signalled)
1015                     goto out;
1016             }
1017             if(signalled)
1018                 goto out;
1019         }
1020         if(html)
1021             puts("</pre>");
1022     }
1023 out:
1024     if(html)
1025         puts("<p></body></html>");
1026     ovdb_close();
1027     return 0;
1028 }
1029
1030 #endif /* USE_BERKELEY_DB */
1031