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 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 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 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 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 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 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 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 202 static inline void do_test_pr_label(uint32_t opt) 203 { 204 opt |= QDIST_PR_LABELS; 205 do_test_pr(opt); 206 } 207 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 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 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 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 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 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 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 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 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