tdb: fix logging of offets and lengths.
[obnox/samba/samba-obnox.git] / lib / tdb / common / 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
20 #define SUMMARY_FORMAT \
21         "Size of file/data: %u/%zu\n" \
22         "Number of records: %zu\n" \
23         "Incompatible hash: %s\n" \
24         "Smallest/average/largest keys: %zu/%zu/%zu\n" \
25         "Smallest/average/largest data: %zu/%zu/%zu\n" \
26         "Smallest/average/largest padding: %zu/%zu/%zu\n" \
27         "Number of dead records: %zu\n" \
28         "Smallest/average/largest dead records: %zu/%zu/%zu\n" \
29         "Number of free records: %zu\n" \
30         "Smallest/average/largest free records: %zu/%zu/%zu\n" \
31         "Number of hash chains: %zu\n" \
32         "Smallest/average/largest hash chains: %zu/%zu/%zu\n" \
33         "Number of uncoalesced records: %zu\n" \
34         "Smallest/average/largest uncoalesced runs: %zu/%zu/%zu\n" \
35         "Percentage keys/data/padding/free/dead/rechdrs&tailers/hashes: %.0f/%.0f/%.0f/%.0f/%.0f/%.0f/%.0f\n"
36
37 /* We don't use tally module, to keep upstream happy. */
38 struct tally {
39         size_t min, max, total;
40         size_t num;
41 };
42
43 static void tally_init(struct tally *tally)
44 {
45         tally->total = 0;
46         tally->num = 0;
47         tally->min = tally->max = 0;
48 }
49
50 static void tally_add(struct tally *tally, size_t len)
51 {
52         if (tally->num == 0)
53                 tally->max = tally->min = len;
54         else if (len > tally->max)
55                 tally->max = len;
56         else if (len < tally->min)
57                 tally->min = len;
58         tally->num++;
59         tally->total += len;
60 }
61
62 static size_t tally_mean(const struct tally *tally)
63 {
64         if (!tally->num)
65                 return 0;
66         return tally->total / tally->num;
67 }
68
69 static size_t get_hash_length(struct tdb_context *tdb, unsigned int i)
70 {
71         tdb_off_t rec_ptr;
72         size_t count = 0;
73
74         if (tdb_ofs_read(tdb, TDB_HASH_TOP(i), &rec_ptr) == -1)
75                 return 0;
76
77         /* keep looking until we find the right record */
78         while (rec_ptr) {
79                 struct tdb_record r;
80                 ++count;
81                 if (tdb_rec_read(tdb, rec_ptr, &r) == -1)
82                         return 0;
83                 rec_ptr = r.next;
84         }
85         return count;
86 }
87
88 _PUBLIC_ char *tdb_summary(struct tdb_context *tdb)
89 {
90         tdb_off_t off, rec_off;
91         struct tally freet, keys, data, dead, extra, hash, uncoal;
92         struct tdb_record rec;
93         char *ret = NULL;
94         bool locked;
95         size_t len, unc = 0;
96         struct tdb_record recovery;
97
98         /* Read-only databases use no locking at all: it's best-effort.
99          * We may have a write lock already, so skip that case too. */
100         if (tdb->read_only || tdb->allrecord_lock.count != 0) {
101                 locked = false;
102         } else {
103                 if (tdb_lockall_read(tdb) == -1)
104                         return NULL;
105                 locked = true;
106         }
107
108         if (tdb_recovery_area(tdb, tdb->methods, &rec_off, &recovery) != 0) {
109                 goto unlock;
110         }
111
112         tally_init(&freet);
113         tally_init(&keys);
114         tally_init(&data);
115         tally_init(&dead);
116         tally_init(&extra);
117         tally_init(&hash);
118         tally_init(&uncoal);
119
120         for (off = TDB_DATA_START(tdb->hash_size);
121              off < tdb->map_size - 1;
122              off += sizeof(rec) + rec.rec_len) {
123                 if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
124                                            DOCONV()) == -1)
125                         goto unlock;
126                 switch (rec.magic) {
127                 case TDB_MAGIC:
128                         tally_add(&keys, rec.key_len);
129                         tally_add(&data, rec.data_len);
130                         tally_add(&extra, rec.rec_len - (rec.key_len
131                                                          + rec.data_len));
132                         if (unc > 1)
133                                 tally_add(&uncoal, unc - 1);
134                         unc = 0;
135                         break;
136                 case TDB_FREE_MAGIC:
137                         tally_add(&freet, rec.rec_len);
138                         unc++;
139                         break;
140                 /* If we crash after ftruncate, we can get zeroes or fill. */
141                 case TDB_RECOVERY_INVALID_MAGIC:
142                 case 0x42424242:
143                         unc++;
144                         /* If it's a valid recovery, we can trust rec_len. */
145                         if (off != rec_off) {
146                                 rec.rec_len = tdb_dead_space(tdb, off)
147                                         - sizeof(rec);
148                         }
149                         /* Fall through */
150                 case TDB_DEAD_MAGIC:
151                         tally_add(&dead, rec.rec_len);
152                         break;
153                 default:
154                         TDB_LOG((tdb, TDB_DEBUG_ERROR,
155                                  "Unexpected record magic 0x%x at offset %u\n",
156                                  rec.magic, off));
157                         goto unlock;
158                 }
159         }
160         if (unc > 1)
161                 tally_add(&uncoal, unc - 1);
162
163         for (off = 0; off < tdb->hash_size; off++)
164                 tally_add(&hash, get_hash_length(tdb, off));
165
166         /* 20 is max length of a %zu. */
167         len = strlen(SUMMARY_FORMAT) + 35*20 + 1;
168         ret = (char *)malloc(len);
169         if (!ret)
170                 goto unlock;
171
172         snprintf(ret, len, SUMMARY_FORMAT,
173                  tdb->map_size, keys.total+data.total,
174                  keys.num,
175                  (tdb->hash_fn == tdb_jenkins_hash)?"yes":"no",
176                  keys.min, tally_mean(&keys), keys.max,
177                  data.min, tally_mean(&data), data.max,
178                  extra.min, tally_mean(&extra), extra.max,
179                  dead.num,
180                  dead.min, tally_mean(&dead), dead.max,
181                  freet.num,
182                  freet.min, tally_mean(&freet), freet.max,
183                  hash.num,
184                  hash.min, tally_mean(&hash), hash.max,
185                  uncoal.total,
186                  uncoal.min, tally_mean(&uncoal), uncoal.max,
187                  keys.total * 100.0 / tdb->map_size,
188                  data.total * 100.0 / tdb->map_size,
189                  extra.total * 100.0 / tdb->map_size,
190                  freet.total * 100.0 / tdb->map_size,
191                  dead.total * 100.0 / tdb->map_size,
192                  (keys.num + freet.num + dead.num)
193                  * (sizeof(struct tdb_record) + sizeof(uint32_t))
194                  * 100.0 / tdb->map_size,
195                  tdb->hash_size * sizeof(tdb_off_t)
196                  * 100.0 / tdb->map_size);
197
198 unlock:
199         if (locked) {
200                 tdb_unlockall_read(tdb);
201         }
202         return ret;
203 }