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