3 # Copyright (C) Catalyst IT Ltd. 2019
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program 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
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 from collections import OrderedDict, Counter
24 from pprint import pprint
26 sys.path.insert(0, "bin/python")
30 def unpack_uint(filename, casefold=True):
31 db = tdb.Tdb(filename)
34 v = struct.unpack("I", db[k])[0]
35 k2 = k.decode('utf-8')
38 if k2 in d: # because casefold
45 def unpack_ssize_t_pair(filename, casefold):
46 db = tdb.Tdb(filename)
49 key = struct.unpack("nn", k)
50 v = struct.unpack("I", db[k])[0]
51 pairs.append((v, key))
53 pairs.sort(reverse=True)
55 return [(k, v) for (v, k) in pairs]
59 ('requested', "debug/attr_counts_requested.tdb", unpack_uint,
60 "The attribute was specifically requested."),
61 ('duplicates', "debug/attr_counts_duplicates.tdb", unpack_uint,
62 "Requested more than once in the same request."),
63 ('empty request', "debug/attr_counts_empty_req.tdb", unpack_uint,
64 "No attributes were requested, but these were returned"),
65 ('null request', "debug/attr_counts_null_req.tdb", unpack_uint,
66 "The attribute list was NULL and these were returned."),
67 ('found', "debug/attr_counts_found.tdb", unpack_uint,
68 "The attribute was specifically requested and it was found."),
69 ('not found', "debug/attr_counts_not_found.tdb", unpack_uint,
70 "The attribute was specifically requested but was not found."),
71 ('unwanted', "debug/attr_counts_unwanted.tdb", unpack_uint,
72 "The attribute was not requested and it was found."),
73 ('star match', "debug/attr_counts_star_match.tdb", unpack_uint,
74 'The attribute was not specifically requested but "*" was.'),
75 ('req vs found', "debug/attr_counts_req_vs_found.tdb", unpack_ssize_t_pair,
76 "How many attributes were requested versus how many were returned."),
80 def plot_pair_data(name, data, doc, lim=90):
81 # Note we keep the matplotlib import internal to this function for
83 # 1. Some people won't have matplotlib, but might want to run the
85 # 2. The import takes hundreds of milliseconds, which is a
86 # nuisance if you don't wat graphs.
88 # This plot could be improved!
89 import matplotlib.pylab as plt
90 fig, ax = plt.subplots()
94 if p[0] > lim or p[1] > lim:
95 print("not plotting %s: %s" % (p, c))
98 skipped = len(data) - len(data2)
100 name += " (excluding %d out of range values)" % skipped
102 xy, counts = zip(*data)
107 ax.scatter(x, y, c=counts)
111 def print_pair_data(name, data, doc):
114 t = "%14s | %14s | %14s"
115 print(t % ("requested", "returned", "count"))
116 print(t % (('-' * 14,) * 3))
118 for xy, count in data:
124 print(t % (x, y, count))
127 def print_counts(count_data):
128 all_attrs = Counter()
130 all_attrs.update(c[1])
132 print("found %d attrs" % len(all_attrs))
133 longest = max(len(x) for x in all_attrs)
137 for a, _ in all_attrs.most_common():
140 for col_name, counts, doc in count_data:
141 for attr, row in rows.items():
142 d = counts.get(attr, '')
145 print("%15s: %s" % (col_name, doc))
148 t = "%{}s".format(longest)
150 t += " | %{}s".format(max(len(c[0]), 7))
152 h = t % (("attribute",) + tuple(c[0] for c in count_data))
156 for attr, row in rows.items():
157 print(t % tuple(row))
161 parser = argparse.ArgumentParser()
162 parser.add_argument('LDB_PRIVATE_DIR',
163 help="read attr counts in this directory")
164 parser.add_argument('--plot', action="store_true",
165 help='attempt to draw graphs')
166 parser.add_argument('--no-casefold', action="store_false",
167 default=True, dest="casefold",
168 help='See all the encountered case varients')
169 args = parser.parse_args()
171 if not os.path.isdir(args.LDB_PRIVATE_DIR):
177 for k, filename, unpacker, doc in DATABASES:
178 filename = os.path.join(args.LDB_PRIVATE_DIR, filename)
180 d = unpacker(filename, casefold=args.casefold)
181 except (RuntimeError, IOError) as e:
182 print("could not parse %s: %s" % (filename, e))
184 if unpacker is unpack_ssize_t_pair:
185 pair_data.append((k, d, doc))
187 count_data.append((k, d, doc))
189 for k, v, doc in pair_data:
191 plot_pair_data(k, v, doc)
192 print_pair_data(k, v, doc)
195 print_counts(count_data)