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/>.
19 from __future__ import print_function
24 from collections import OrderedDict, Counter
25 from pprint import pprint
27 sys.path.insert(0, "bin/python")
31 def unpack_uint(filename, casefold=True):
32 db = tdb.Tdb(filename)
35 v = struct.unpack("I", db[k])[0]
36 k2 = k.decode('utf-8')
39 if k2 in d: # because casefold
46 def unpack_ssize_t_pair(filename, casefold):
47 db = tdb.Tdb(filename)
50 key = struct.unpack("nn", k)
51 v = struct.unpack("I", db[k])[0]
52 pairs.append((v, key))
54 pairs.sort(reverse=True)
56 return [(k, v) for (v, k) in pairs]
60 ('requested', "debug/attr_counts_requested.tdb", unpack_uint,
61 "The attribute was specifically requested."),
62 ('duplicates', "debug/attr_counts_duplicates.tdb", unpack_uint,
63 "Requested more than once in the same request."),
64 ('empty request', "debug/attr_counts_empty_req.tdb", unpack_uint,
65 "No attributes were requested, but these were returned"),
66 ('null request', "debug/attr_counts_null_req.tdb", unpack_uint,
67 "The attribute list was NULL and these were returned."),
68 ('found', "debug/attr_counts_found.tdb", unpack_uint,
69 "The attribute was specifically requested and it was found."),
70 ('not found', "debug/attr_counts_not_found.tdb", unpack_uint,
71 "The attribute was specifically requested but was not found."),
72 ('unwanted', "debug/attr_counts_unwanted.tdb", unpack_uint,
73 "The attribute was not requested and it was found."),
74 ('star match', "debug/attr_counts_star_match.tdb", unpack_uint,
75 'The attribute was not specifically requested but "*" was.'),
76 ('req vs found', "debug/attr_counts_req_vs_found.tdb", unpack_ssize_t_pair,
77 "How many attributes were requested versus how many were returned."),
81 def plot_pair_data(name, data, doc, lim=90):
82 # Note we keep the matplotlib import internal to this function for
84 # 1. Some people won't have matplotlib, but might want to run the
86 # 2. The import takes hundreds of milliseconds, which is a
87 # nuisance if you don't wat graphs.
89 # This plot could be improved!
90 import matplotlib.pylab as plt
91 fig, ax = plt.subplots()
95 if p[0] > lim or p[1] > lim:
96 print("not plotting %s: %s" % (p, c))
99 skipped = len(data) - len(data2)
101 name += " (excluding %d out of range values)" % skipped
103 xy, counts = zip(*data)
108 ax.scatter(x, y, c=counts)
112 def print_pair_data(name, data, doc):
115 t = "%14s | %14s | %14s"
116 print(t % ("requested", "returned", "count"))
117 print(t % (('-' * 14,) * 3))
119 for xy, count in data:
125 print(t % (x, y, count))
128 def print_counts(count_data):
129 all_attrs = Counter()
131 all_attrs.update(c[1])
133 print("found %d attrs" % len(all_attrs))
134 longest = max(len(x) for x in all_attrs)
138 for a, _ in all_attrs.most_common():
141 for col_name, counts, doc in count_data:
142 for attr, row in rows.items():
143 d = counts.get(attr, '')
146 print("%15s: %s" % (col_name, doc))
149 t = "%{}s".format(longest)
151 t += " | %{}s".format(max(len(c[0]), 7))
153 h = t % (("attribute",) + tuple(c[0] for c in count_data))
157 for attr, row in rows.items():
158 print(t % tuple(row))
163 parser = argparse.ArgumentParser()
164 parser.add_argument('LDB_PRIVATE_DIR',
165 help="read attr counts in this directory")
166 parser.add_argument('--plot', action="store_true",
167 help='attempt to draw graphs')
168 parser.add_argument('--no-casefold', action="store_false",
169 default=True, dest="casefold",
170 help='See all the encountered case varients')
171 args = parser.parse_args()
173 if not os.path.isdir(args.LDB_PRIVATE_DIR):
179 for k, filename, unpacker, doc in DATABASES:
180 filename = os.path.join(args.LDB_PRIVATE_DIR, filename)
182 d = unpacker(filename, casefold=args.casefold)
183 except (RuntimeError, IOError) as e:
184 print("could not parse %s: %s" % (filename, e))
186 if unpacker is unpack_ssize_t_pair:
187 pair_data.append((k, d, doc))
189 count_data.append((k, d, doc))
191 for k, v, doc in pair_data:
193 plot_pair_data(k, v, doc)
194 print_pair_data(k, v, doc)
197 print_counts(count_data)