1 /*
2 * Copyright (C) 2016, Emilio G. Cota <cota@braap.org>
3 *
4 * License: GNU GPL, version 2 or later.
5 * See the COPYING file in the top-level directory.
6 */
7 #include "qemu/osdep.h"
8 #include "qemu/qdist.h"
9
10 #include <math.h>
11
12 struct entry_desc {
13 double x;
14 unsigned long count;
15
16 /* 0 prints a space, 1-8 prints from qdist_blocks[] */
17 int fill_code;
18 };
19
20 /* See: https://en.wikipedia.org/wiki/Block_Elements */
21 static const gunichar qdist_blocks[] = {
22 0x2581,
23 0x2582,
24 0x2583,
25 0x2584,
26 0x2585,
27 0x2586,
28 0x2587,
29 0x2588
30 };
31
32 #define QDIST_NR_BLOCK_CODES ARRAY_SIZE(qdist_blocks)
33
pr_hist(const struct entry_desc * darr,size_t n)34 static char *pr_hist(const struct entry_desc *darr, size_t n)
35 {
36 GString *s = g_string_new("");
37 size_t i;
38
39 for (i = 0; i < n; i++) {
40 int fill = darr[i].fill_code;
41
42 if (fill) {
43 assert(fill <= QDIST_NR_BLOCK_CODES);
44 g_string_append_unichar(s, qdist_blocks[fill - 1]);
45 } else {
46 g_string_append_c(s, ' ');
47 }
48 }
49 return g_string_free(s, FALSE);
50 }
51
52 static void
histogram_check(const struct qdist * dist,const struct entry_desc * darr,size_t n,size_t n_bins)53 histogram_check(const struct qdist *dist, const struct entry_desc *darr,
54 size_t n, size_t n_bins)
55 {
56 char *pr = qdist_pr_plain(dist, n_bins);
57 char *str = pr_hist(darr, n);
58
59 g_assert_cmpstr(pr, ==, str);
60 g_free(pr);
61 g_free(str);
62 }
63
histogram_check_single_full(const struct qdist * dist,size_t n_bins)64 static void histogram_check_single_full(const struct qdist *dist, size_t n_bins)
65 {
66 struct entry_desc desc = { .fill_code = 8 };
67
68 histogram_check(dist, &desc, 1, n_bins);
69 }
70
71 static void
entries_check(const struct qdist * dist,const struct entry_desc * darr,size_t n)72 entries_check(const struct qdist *dist, const struct entry_desc *darr, size_t n)
73 {
74 size_t i;
75
76 for (i = 0; i < n; i++) {
77 struct qdist_entry *e = &dist->entries[i];
78
79 g_assert_cmpuint(e->count, ==, darr[i].count);
80 }
81 }
82
83 static void
entries_insert(struct qdist * dist,const struct entry_desc * darr,size_t n)84 entries_insert(struct qdist *dist, const struct entry_desc *darr, size_t n)
85 {
86 size_t i;
87
88 for (i = 0; i < n; i++) {
89 qdist_add(dist, darr[i].x, darr[i].count);
90 }
91 }
92
do_test_bin(const struct entry_desc * a,size_t n_a,const struct entry_desc * b,size_t n_b)93 static void do_test_bin(const struct entry_desc *a, size_t n_a,
94 const struct entry_desc *b, size_t n_b)
95 {
96 struct qdist qda;
97 struct qdist qdb;
98
99 qdist_init(&qda);
100
101 entries_insert(&qda, a, n_a);
102 qdist_inc(&qda, a[0].x);
103 qdist_add(&qda, a[0].x, -1);
104
105 g_assert_cmpuint(qdist_unique_entries(&qda), ==, n_a);
106 g_assert_cmpfloat(qdist_xmin(&qda), ==, a[0].x);
107 g_assert_cmpfloat(qdist_xmax(&qda), ==, a[n_a - 1].x);
108 histogram_check(&qda, a, n_a, 0);
109 histogram_check(&qda, a, n_a, n_a);
110
111 qdist_bin__internal(&qdb, &qda, n_b);
112 g_assert_cmpuint(qdb.n, ==, n_b);
113 entries_check(&qdb, b, n_b);
114 g_assert_cmpuint(qdist_sample_count(&qda), ==, qdist_sample_count(&qdb));
115 /*
116 * No histogram_check() for $qdb, since we'd rebin it and that is a bug.
117 * Instead, regenerate it from $qda.
118 */
119 histogram_check(&qda, b, n_b, n_b);
120
121 qdist_destroy(&qdb);
122 qdist_destroy(&qda);
123 }
124
do_test_pr(uint32_t opt)125 static void do_test_pr(uint32_t opt)
126 {
127 static const struct entry_desc desc[] = {
128 [0] = { 1, 900, 8 },
129 [1] = { 2, 1, 1 },
130 [2] = { 3, 2, 1 }
131 };
132 static const char border[] = "|";
133 const char *llabel = NULL;
134 const char *rlabel = NULL;
135 struct qdist dist;
136 GString *s;
137 char *str;
138 char *pr;
139 size_t n;
140
141 n = ARRAY_SIZE(desc);
142 qdist_init(&dist);
143
144 entries_insert(&dist, desc, n);
145 histogram_check(&dist, desc, n, 0);
146
147 s = g_string_new("");
148
149 if (opt & QDIST_PR_LABELS) {
150 unsigned int lopts = opt & (QDIST_PR_NODECIMAL |
151 QDIST_PR_PERCENT |
152 QDIST_PR_100X |
153 QDIST_PR_NOBINRANGE);
154
155 if (lopts == 0) {
156 llabel = "[1.0,1.7)";
157 rlabel = "[2.3,3.0]";
158 } else if (lopts == QDIST_PR_NODECIMAL) {
159 llabel = "[1,2)";
160 rlabel = "[2,3]";
161 } else if (lopts == (QDIST_PR_PERCENT | QDIST_PR_NODECIMAL)) {
162 llabel = "[1,2)%";
163 rlabel = "[2,3]%";
164 } else if (lopts == QDIST_PR_100X) {
165 llabel = "[100.0,166.7)";
166 rlabel = "[233.3,300.0]";
167 } else if (lopts == (QDIST_PR_NOBINRANGE | QDIST_PR_NODECIMAL)) {
168 llabel = "1";
169 rlabel = "3";
170 } else {
171 g_assert_cmpstr("BUG", ==, "This is not meant to be exhaustive");
172 }
173 }
174
175 if (llabel) {
176 g_string_append(s, llabel);
177 }
178 if (opt & QDIST_PR_BORDER) {
179 g_string_append(s, border);
180 }
181
182 str = pr_hist(desc, n);
183 g_string_append(s, str);
184 g_free(str);
185
186 if (opt & QDIST_PR_BORDER) {
187 g_string_append(s, border);
188 }
189 if (rlabel) {
190 g_string_append(s, rlabel);
191 }
192
193 str = g_string_free(s, FALSE);
194 pr = qdist_pr(&dist, n, opt);
195 g_assert_cmpstr(pr, ==, str);
196 g_free(pr);
197 g_free(str);
198
199 qdist_destroy(&dist);
200 }
201
do_test_pr_label(uint32_t opt)202 static inline void do_test_pr_label(uint32_t opt)
203 {
204 opt |= QDIST_PR_LABELS;
205 do_test_pr(opt);
206 }
207
test_pr(void)208 static void test_pr(void)
209 {
210 do_test_pr(0);
211
212 do_test_pr(QDIST_PR_BORDER);
213
214 /* 100X should be ignored because we're not setting LABELS */
215 do_test_pr(QDIST_PR_100X);
216
217 do_test_pr_label(0);
218 do_test_pr_label(QDIST_PR_NODECIMAL);
219 do_test_pr_label(QDIST_PR_PERCENT | QDIST_PR_NODECIMAL);
220 do_test_pr_label(QDIST_PR_100X);
221 do_test_pr_label(QDIST_PR_NOBINRANGE | QDIST_PR_NODECIMAL);
222 }
223
test_bin_shrink(void)224 static void test_bin_shrink(void)
225 {
226 static const struct entry_desc a[] = {
227 [0] = { 0.0, 42922, 7 },
228 [1] = { 0.25, 47834, 8 },
229 [2] = { 0.50, 26628, 0 },
230 [3] = { 0.625, 597, 4 },
231 [4] = { 0.75, 10298, 1 },
232 [5] = { 0.875, 22, 2 },
233 [6] = { 1.0, 2771, 1 }
234 };
235 static const struct entry_desc b[] = {
236 [0] = { 0.0, 42922, 7 },
237 [1] = { 0.25, 47834, 8 },
238 [2] = { 0.50, 27225, 3 },
239 [3] = { 0.75, 13091, 1 }
240 };
241
242 return do_test_bin(a, ARRAY_SIZE(a), b, ARRAY_SIZE(b));
243 }
244
test_bin_expand(void)245 static void test_bin_expand(void)
246 {
247 static const struct entry_desc a[] = {
248 [0] = { 0.0, 11713, 5 },
249 [1] = { 0.25, 20294, 0 },
250 [2] = { 0.50, 17266, 8 },
251 [3] = { 0.625, 1506, 0 },
252 [4] = { 0.75, 10355, 6 },
253 [5] = { 0.833, 2, 1 },
254 [6] = { 0.875, 99, 4 },
255 [7] = { 1.0, 4301, 2 }
256 };
257 static const struct entry_desc b[] = {
258 [0] = { 0.0, 11713, 5 },
259 [1] = { 0.0, 0, 0 },
260 [2] = { 0.0, 20294, 8 },
261 [3] = { 0.0, 0, 0 },
262 [4] = { 0.0, 0, 0 },
263 [5] = { 0.0, 17266, 6 },
264 [6] = { 0.0, 1506, 1 },
265 [7] = { 0.0, 10355, 4 },
266 [8] = { 0.0, 101, 1 },
267 [9] = { 0.0, 4301, 2 }
268 };
269
270 return do_test_bin(a, ARRAY_SIZE(a), b, ARRAY_SIZE(b));
271 }
272
test_bin_precision(void)273 static void test_bin_precision(void)
274 {
275 static const struct entry_desc a[] = {
276 [0] = { 0, 213549, 8 },
277 [1] = { 1, 70, 1 },
278 };
279 static const struct entry_desc b[] = {
280 [0] = { 0, 213549, 8 },
281 [1] = { 0, 70, 1 },
282 };
283
284 return do_test_bin(a, ARRAY_SIZE(a), b, ARRAY_SIZE(b));
285 }
286
test_bin_simple(void)287 static void test_bin_simple(void)
288 {
289 static const struct entry_desc a[] = {
290 [0] = { 10, 101, 8 },
291 [1] = { 11, 0, 0 },
292 [2] = { 12, 2, 1 }
293 };
294 static const struct entry_desc b[] = {
295 [0] = { 0, 101, 8 },
296 [1] = { 0, 0, 0 },
297 [2] = { 0, 0, 0 },
298 [3] = { 0, 0, 0 },
299 [4] = { 0, 2, 1 }
300 };
301
302 return do_test_bin(a, ARRAY_SIZE(a), b, ARRAY_SIZE(b));
303 }
304
test_single_full(void)305 static void test_single_full(void)
306 {
307 struct qdist dist;
308
309 qdist_init(&dist);
310
311 qdist_add(&dist, 3, 102);
312 g_assert_cmpfloat(qdist_avg(&dist), ==, 3);
313 g_assert_cmpfloat(qdist_xmin(&dist), ==, 3);
314 g_assert_cmpfloat(qdist_xmax(&dist), ==, 3);
315
316 histogram_check_single_full(&dist, 0);
317 histogram_check_single_full(&dist, 1);
318 histogram_check_single_full(&dist, 10);
319
320 qdist_destroy(&dist);
321 }
322
test_single_empty(void)323 static void test_single_empty(void)
324 {
325 struct qdist dist;
326 char *pr;
327
328 qdist_init(&dist);
329
330 qdist_add(&dist, 3, 0);
331 g_assert_cmpuint(qdist_sample_count(&dist), ==, 0);
332 g_assert(isnan(qdist_avg(&dist)));
333 g_assert_cmpfloat(qdist_xmin(&dist), ==, 3);
334 g_assert_cmpfloat(qdist_xmax(&dist), ==, 3);
335
336 pr = qdist_pr_plain(&dist, 0);
337 g_assert_cmpstr(pr, ==, " ");
338 g_free(pr);
339
340 pr = qdist_pr_plain(&dist, 1);
341 g_assert_cmpstr(pr, ==, " ");
342 g_free(pr);
343
344 pr = qdist_pr_plain(&dist, 2);
345 g_assert_cmpstr(pr, ==, " ");
346 g_free(pr);
347
348 qdist_destroy(&dist);
349 }
350
test_none(void)351 static void test_none(void)
352 {
353 struct qdist dist;
354 char *pr;
355
356 qdist_init(&dist);
357
358 g_assert(isnan(qdist_avg(&dist)));
359 g_assert(isnan(qdist_xmin(&dist)));
360 g_assert(isnan(qdist_xmax(&dist)));
361
362 pr = qdist_pr_plain(&dist, 0);
363 g_assert_cmpstr(pr, ==, "(empty)");
364 g_free(pr);
365
366 pr = qdist_pr_plain(&dist, 2);
367 g_assert_cmpstr(pr, ==, "(empty)");
368 g_free(pr);
369
370 pr = qdist_pr(&dist, 0, QDIST_PR_BORDER);
371 g_assert_cmpstr(pr, ==, "(empty)");
372 g_free(pr);
373
374 qdist_destroy(&dist);
375 }
376
main(int argc,char * argv[])377 int main(int argc, char *argv[])
378 {
379 g_test_init(&argc, &argv, NULL);
380 g_test_add_func("/qdist/none", test_none);
381 g_test_add_func("/qdist/single/empty", test_single_empty);
382 g_test_add_func("/qdist/single/full", test_single_full);
383 g_test_add_func("/qdist/binning/simple", test_bin_simple);
384 g_test_add_func("/qdist/binning/precision", test_bin_precision);
385 g_test_add_func("/qdist/binning/expand", test_bin_expand);
386 g_test_add_func("/qdist/binning/shrink", test_bin_shrink);
387 g_test_add_func("/qdist/pr", test_pr);
388 return g_test_run();
389 }
390