tdb: implement tdb_summary.
[ccan] / ccan / tdb / summary.c
1  /* 
2    Trivial Database: human-readable summary code
3    Copyright (C) Rusty Russell 2010
4    
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 3 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "tdb_private.h"
19 #include <ccan/tally/tally.h>
20
21 #define SUMMARY_FORMAT \
22         "Size of file: %zu\n" \
23         "Number of records: %zu\n" \
24         "Smallest/average/largest records: %zu/%zu/%zu\n%s" \
25         "Smallest/average/largest padding: %zu/%zu/%zu\n%s" \
26         "Number of dead records: %zu\n" \
27         "Smallest/average/largest dead records: %zu/%zu/%zu\n%s" \
28         "Number of free records: %zu\n" \
29         "Smallest/average/largest free records: %zu/%zu/%zu\n%s" \
30         "Number of hash chains: %zu\n" \
31         "Smallest/average/largest hash chains: %zu/%zu/%zu\n%s" \
32         "Total data = %zu (%.0f%%)\n"
33
34 #define HISTO_WIDTH 70
35 #define HISTO_HEIGHT 20
36
37 /* Slow, but should be very rare. */
38 static size_t dead_space(struct tdb_context *tdb, tdb_off_t off)
39 {
40         size_t len;
41
42         for (len = 0; off + len < tdb->map_size; len++) {
43                 char c;
44                 if (tdb->methods->tdb_read(tdb, off, &c, 1, 0))
45                         return 0;
46                 if (c != 0 && c != 0x42)
47                         break;
48         }
49         return len;
50 }
51
52 static size_t get_hash_length(struct tdb_context *tdb, unsigned int i)
53 {
54         tdb_off_t rec_ptr;
55         size_t count = 0;
56
57         if (tdb_ofs_read(tdb, TDB_HASH_TOP(i), &rec_ptr) == -1)
58                 return 0;
59
60         /* keep looking until we find the right record */
61         while (rec_ptr) {
62                 struct tdb_record r;
63                 ++count;
64                 if (tdb_rec_read(tdb, rec_ptr, &r) == -1)
65                         return 0;
66                 rec_ptr = r.next;
67         }
68         return count;
69 }
70
71 char *tdb_summary(struct tdb_context *tdb, enum tdb_summary_flags flags)
72 {
73         tdb_off_t off;
74         struct tally *freet, *used, *dead, *extra, *hash;
75         char *freeg, *usedg, *deadg, *extrag, *hashg;
76         struct tdb_record rec;
77         char *ret = NULL;
78         bool locked;
79         size_t len;
80
81         /* Read-only databases use no locking at all: it's best-effort.
82          * We may have a write lock already, so skip that case too. */
83         if (tdb->read_only || tdb->allrecord_lock.count != 0) {
84                 locked = false;
85         } else {
86                 if (tdb_lockall_read(tdb) == -1)
87                         return NULL;
88                 locked = true;
89         }
90
91         freet = tally_new(100);
92         used = tally_new(100);
93         dead = tally_new(100);
94         extra = tally_new(100);
95         hash = tally_new(100);
96         if (!freet || !used || !dead || !extra || !hash) {
97                 tdb->ecode = TDB_ERR_OOM;
98                 goto unlock;
99         }
100
101         for (off = TDB_DATA_START(tdb->header.hash_size);
102              off < tdb->map_size - 1;
103              off += sizeof(rec) + rec.rec_len) {
104                 if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
105                                            DOCONV()) == -1)
106                         goto unlock;
107                 switch (rec.magic) {
108                 case TDB_MAGIC:
109                         tally_add(used, rec.key_len + rec.data_len);
110                         tally_add(extra, rec.rec_len - (rec.key_len
111                                                         + rec.data_len));
112                         break;
113                 case TDB_FREE_MAGIC:
114                         tally_add(freet, sizeof(rec) + rec.rec_len);
115                         break;
116                 /* If we crash after ftruncate, we can get zeroes or fill. */
117                 case TDB_RECOVERY_INVALID_MAGIC:
118                 case 0x42424242:
119                         rec.rec_len = dead_space(tdb, off) - sizeof(rec);
120                         /* Fall through */
121                 case TDB_DEAD_MAGIC:
122                         tally_add(dead, sizeof(rec) + rec.rec_len);
123                         break;
124                 default:
125                         TDB_LOG((tdb, TDB_DEBUG_ERROR,
126                                  "Unexpected record magic 0x%x at offset %d\n",
127                                  rec.magic, off));
128                         goto unlock;
129                 }
130         }
131
132         for (off = 0; off < tdb->header.hash_size; off++)
133                 tally_add(hash, get_hash_length(tdb, off));
134
135         if (flags & TDB_SUMMARY_HISTOGRAMS) {
136                 freeg = tally_histogram(freet, HISTO_WIDTH, HISTO_HEIGHT);
137                 usedg = tally_histogram(used, HISTO_WIDTH, HISTO_HEIGHT);
138                 deadg = tally_histogram(dead, HISTO_WIDTH, HISTO_HEIGHT);
139                 extrag = tally_histogram(extra, HISTO_WIDTH, HISTO_HEIGHT);
140                 hashg = tally_histogram(hash, HISTO_WIDTH, HISTO_HEIGHT);
141         } else {
142                 freeg = usedg = deadg = extrag = hashg = NULL;
143         }
144
145         /* 20 is max length of a %zu. */
146         len = strlen(SUMMARY_FORMAT) + 22*20 + 1
147                 + (freeg ? strlen(freeg) : 0)
148                 + (usedg ? strlen(usedg) : 0)
149                 + (deadg ? strlen(deadg) : 0)
150                 + (extrag ? strlen(extrag) : 0)
151                 + (hashg ? strlen(hashg) : 0);
152         ret = malloc(len);
153         if (!ret)
154                 goto unlock;
155
156         sprintf(ret, SUMMARY_FORMAT,
157                 tdb->map_size,
158                 tally_num(used),
159                 tally_min(used), tally_mean(used), tally_max(used),
160                 usedg ? usedg : "",
161                 tally_min(extra), tally_mean(extra), tally_max(extra),
162                 extrag ? extrag : "",
163                 tally_num(dead),
164                 tally_min(dead), tally_mean(dead), tally_max(dead),
165                 deadg ? deadg : "",
166                 tally_num(freet),
167                 tally_min(freet), tally_mean(freet), tally_max(freet),
168                 freeg ? freeg : "",
169                 tally_num(hash),
170                 tally_min(hash), tally_mean(hash), tally_max(hash),
171                 hashg ? hashg : "",
172                 tally_total(used, NULL),
173                 tally_total(used, NULL) * 100.0 / tdb->map_size);
174
175 unlock:
176         free(freeg);
177         free(usedg);
178         free(deadg);
179         free(extrag);
180         free(hashg);
181         free(freet);
182         free(used);
183         free(dead);
184         free(extra);
185         free(hash);
186         if (locked) {
187                 tdb_unlockall_read(tdb);
188         }
189         return ret;
190 }