1*c2d18a95Schristos /* $NetBSD: histo.c,v 1.3 2025/01/27 02:16:05 christos Exp $ */ 29689912eSchristos 39689912eSchristos /* 49689912eSchristos * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 59689912eSchristos * 69689912eSchristos * SPDX-License-Identifier: MPL-2.0 79689912eSchristos * 89689912eSchristos * This Source Code Form is subject to the terms of the Mozilla Public 99689912eSchristos * License, v. 2.0. If a copy of the MPL was not distributed with this 109689912eSchristos * file, you can obtain one at https://mozilla.org/MPL/2.0/. 119689912eSchristos * 129689912eSchristos * See the COPYRIGHT file distributed with this work for additional 139689912eSchristos * information regarding copyright ownership. 149689912eSchristos */ 159689912eSchristos 169689912eSchristos #include <assert.h> 179689912eSchristos #include <errno.h> 189689912eSchristos #include <math.h> 199689912eSchristos #include <stdbool.h> 209689912eSchristos #include <stdint.h> 219689912eSchristos #include <stdio.h> 229689912eSchristos #include <stdlib.h> 239689912eSchristos #include <string.h> 249689912eSchristos 259689912eSchristos #include <isc/atomic.h> 269689912eSchristos #include <isc/histo.h> 279689912eSchristos #include <isc/magic.h> 289689912eSchristos #include <isc/mem.h> 299689912eSchristos #include <isc/tid.h> 309689912eSchristos 319689912eSchristos #define HISTO_MAGIC ISC_MAGIC('H', 's', 't', 'o') 329689912eSchristos #define HISTO_VALID(p) ISC_MAGIC_VALID(p, HISTO_MAGIC) 339689912eSchristos #define HISTOMULTI_MAGIC ISC_MAGIC('H', 'g', 'M', 't') 349689912eSchristos #define HISTOMULTI_VALID(p) ISC_MAGIC_VALID(p, HISTOMULTI_MAGIC) 359689912eSchristos 369689912eSchristos /* 379689912eSchristos * Natural logarithms of 2 and 10 for converting precisions between 389689912eSchristos * binary and decimal significant figures 399689912eSchristos */ 409689912eSchristos #define LN_2 0.693147180559945309 419689912eSchristos #define LN_10 2.302585092994045684 429689912eSchristos 439689912eSchristos /* 449689912eSchristos * The chunks array has a static size for simplicity, fixed as the 459689912eSchristos * number of bits in a value. That means we waste a little extra space 469689912eSchristos * that could be saved by omitting the exponents that are covered by 479689912eSchristos * `sigbits`. The following macros calculate (at run time) the exact 489689912eSchristos * number of buckets when we need to do accurate bounds checks. 499689912eSchristos * 509689912eSchristos * For a discussion of the floating point terminology, see the 519689912eSchristos * commmentary on `value_to_key()` below. 529689912eSchristos * 539689912eSchristos * We often use the variable names `c` for chunk and `b` for bucket. 549689912eSchristos */ 559689912eSchristos #define CHUNKS 64 569689912eSchristos 579689912eSchristos #define DENORMALS(hg) ((hg)->sigbits - 1) 589689912eSchristos #define MANTISSAS(hg) (1 << (hg)->sigbits) 599689912eSchristos #define EXPONENTS(hg) (CHUNKS - DENORMALS(hg)) 609689912eSchristos #define BUCKETS(hg) (EXPONENTS(hg) * MANTISSAS(hg)) 619689912eSchristos 629689912eSchristos #define MAXCHUNK(hg) EXPONENTS(hg) 639689912eSchristos #define CHUNKSIZE(hg) MANTISSAS(hg) 649689912eSchristos 65*c2d18a95Schristos #ifdef _LP64 669689912eSchristos typedef atomic_uint_fast64_t hg_bucket_t; 67*c2d18a95Schristos #else 68*c2d18a95Schristos typedef atomic_uint_fast32_t hg_bucket_t; 69*c2d18a95Schristos #endif 709689912eSchristos typedef atomic_ptr(hg_bucket_t) hg_chunk_t; 719689912eSchristos 729689912eSchristos struct isc_histo { 739689912eSchristos uint magic; 749689912eSchristos uint sigbits; 759689912eSchristos isc_mem_t *mctx; 769689912eSchristos hg_chunk_t chunk[CHUNKS]; 779689912eSchristos }; 789689912eSchristos 799689912eSchristos struct isc_histomulti { 809689912eSchristos uint magic; 819689912eSchristos uint size; 829689912eSchristos isc_histo_t *hg[]; 839689912eSchristos }; 849689912eSchristos 859689912eSchristos /**********************************************************************/ 869689912eSchristos 879689912eSchristos void 889689912eSchristos isc_histo_create(isc_mem_t *mctx, uint sigbits, isc_histo_t **hgp) { 899689912eSchristos REQUIRE(sigbits >= ISC_HISTO_MINBITS); 909689912eSchristos REQUIRE(sigbits <= ISC_HISTO_MAXBITS); 919689912eSchristos REQUIRE(hgp != NULL); 929689912eSchristos REQUIRE(*hgp == NULL); 939689912eSchristos 949689912eSchristos isc_histo_t *hg = isc_mem_get(mctx, sizeof(*hg)); 959689912eSchristos *hg = (isc_histo_t){ 969689912eSchristos .magic = HISTO_MAGIC, 979689912eSchristos .sigbits = sigbits, 989689912eSchristos }; 999689912eSchristos isc_mem_attach(mctx, &hg->mctx); 1009689912eSchristos 1019689912eSchristos *hgp = hg; 1029689912eSchristos } 1039689912eSchristos 1049689912eSchristos void 1059689912eSchristos isc_histo_destroy(isc_histo_t **hgp) { 1069689912eSchristos REQUIRE(hgp != NULL); 1079689912eSchristos REQUIRE(HISTO_VALID(*hgp)); 1089689912eSchristos 1099689912eSchristos isc_histo_t *hg = *hgp; 1109689912eSchristos *hgp = NULL; 1119689912eSchristos 1129689912eSchristos for (uint c = 0; c < CHUNKS; c++) { 1139689912eSchristos if (hg->chunk[c] != NULL) { 1149689912eSchristos isc_mem_cput(hg->mctx, hg->chunk[c], CHUNKSIZE(hg), 1159689912eSchristos sizeof(hg_bucket_t)); 1169689912eSchristos } 1179689912eSchristos } 1189689912eSchristos isc_mem_putanddetach(&hg->mctx, hg, sizeof(*hg)); 1199689912eSchristos } 1209689912eSchristos 1219689912eSchristos /**********************************************************************/ 1229689912eSchristos 1239689912eSchristos uint 1249689912eSchristos isc_histo_sigbits(isc_histo_t *hg) { 1259689912eSchristos REQUIRE(HISTO_VALID(hg)); 1269689912eSchristos return hg->sigbits; 1279689912eSchristos } 1289689912eSchristos 1299689912eSchristos /* 1309689912eSchristos * use precomputed logs and builtins to avoid linking with libm 1319689912eSchristos */ 1329689912eSchristos 1339689912eSchristos uint 1349689912eSchristos isc_histo_bits_to_digits(uint bits) { 1359689912eSchristos REQUIRE(bits >= ISC_HISTO_MINBITS); 1369689912eSchristos REQUIRE(bits <= ISC_HISTO_MAXBITS); 1379689912eSchristos return floor(1.0 - (1.0 - bits) * LN_2 / LN_10); 1389689912eSchristos } 1399689912eSchristos 1409689912eSchristos uint 1419689912eSchristos isc_histo_digits_to_bits(uint digits) { 1429689912eSchristos REQUIRE(digits >= ISC_HISTO_MINDIGITS); 1439689912eSchristos REQUIRE(digits <= ISC_HISTO_MAXDIGITS); 1449689912eSchristos return ceil(1.0 - (1.0 - digits) * LN_10 / LN_2); 1459689912eSchristos } 1469689912eSchristos 1479689912eSchristos /**********************************************************************/ 1489689912eSchristos 1499689912eSchristos /* 1509689912eSchristos * The way we map buckets to keys is what gives the histogram a 1519689912eSchristos * consistent relative error across the whole range of `uint64_t`. 1529689912eSchristos * The mapping is log-linear: a chunk key is the logarithm of part 1539689912eSchristos * of the value (in other words, chunks are spaced exponentially); 1549689912eSchristos * and a bucket within a chunk is a linear function of another part 1559689912eSchristos * of the value. 1569689912eSchristos * 1579689912eSchristos * This log-linear spacing is similar to the size classes used by 1589689912eSchristos * jemalloc. It is also the way floating point numbers work: the 1599689912eSchristos * exponent is the log part, and the mantissa is the linear part. 1609689912eSchristos * 1619689912eSchristos * So, a chunk number is the log (base 2) of a `uint64_t`, which is 1629689912eSchristos * between 0 and 63, which is why there are up to 64 chunks. In 1639689912eSchristos * floating point terms the chunk number is the exponent. The 1649689912eSchristos * histogram's number of significant bits is the size of the 1659689912eSchristos * mantissa, which indexes buckets within each chunk. 1669689912eSchristos * 1679689912eSchristos * A fast way to get the logarithm of a positive integer is CLZ, 1689689912eSchristos * count leading zeroes. 1699689912eSchristos * 1709689912eSchristos * Chunk zero is special. Chunk 1 covers values between `CHUNKSIZE` 1719689912eSchristos * and `CHUNKSIZE * 2 - 1`, where `CHUNKSIZE == exponent << sigbits 1729689912eSchristos * == 1 << sigbits`. Each chunk has CHUNKSIZE buckets, so chunk 1 has 1739689912eSchristos * one value per bucket. There are CHUNKSIZE values before chunk 1 1749689912eSchristos * which map to chunk 0, so it also has one value per bucket. (Hence 1759689912eSchristos * the first two chunks have one value per bucket.) The values in 1769689912eSchristos * chunk 0 correspond to denormal nubers in floating point terms. 1779689912eSchristos * They are also the values where `63 - sigbits - clz` would be less 1789689912eSchristos * than one if denormals were not handled specially. 1799689912eSchristos * 1809689912eSchristos * This branchless conversion is due to Paul Khuong: see bin_down_of() in 1819689912eSchristos * https://pvk.ca/Blog/2015/06/27/linear-log-bucketing-fast-versatile-simple/ 1829689912eSchristos * 1839689912eSchristos * This function is in the `isc_histo_inc()` fast path. 1849689912eSchristos */ 1859689912eSchristos static inline uint 1869689912eSchristos value_to_key(const isc_histo_t *hg, uint64_t value) { 1879689912eSchristos /* ensure that denormal numbers are all in chunk zero */ 1889689912eSchristos uint64_t chunked = value | CHUNKSIZE(hg); 1899689912eSchristos int clz = __builtin_clzll((unsigned long long)(chunked)); 1909689912eSchristos /* actually 1 less than the exponent except for denormals */ 1919689912eSchristos uint exponent = 63 - hg->sigbits - clz; 1929689912eSchristos /* mantissa has leading bit set except for denormals */ 1939689912eSchristos uint mantissa = value >> exponent; 1949689912eSchristos /* leading bit of mantissa adds one to exponent */ 1959689912eSchristos return (exponent << hg->sigbits) + mantissa; 1969689912eSchristos } 1979689912eSchristos 1989689912eSchristos /* 1999689912eSchristos * Inverse functions of `value_to_key()`, to get the minimum and 2009689912eSchristos * maximum values that map to a particular key. 2019689912eSchristos * 2029689912eSchristos * We must not cause undefined behaviour by hitting integer limits, 2039689912eSchristos * which is a risk when we aim to cover the entire range of `uint64_t`. 2049689912eSchristos * 2059689912eSchristos * The maximum value in the last bucket is UINT64_MAX, which 2069689912eSchristos * `key_to_maxval()` gets by deliberately subtracting `0 - 1`, 2079689912eSchristos * undeflowing a `uint64_t`. That is OK when unsigned. 2089689912eSchristos * 2099689912eSchristos * We must take care not to shift too much in `key_to_minval()`. 2109689912eSchristos * The largest key passed by `key_to_maxval()` is `BUCKETS(hg)`, so 2119689912eSchristos * `exponent == EXPONENTS(hg) - 1 == 64 - sigbits` 2129689912eSchristos * which is always less than 64, so the size of the shift is OK. 2139689912eSchristos * 2149689912eSchristos * The `mantissa` in this edge case is just `chunksize`, which when 2159689912eSchristos * shifted becomes `1 << 64` which overflows `uint64_t` Again this is 2169689912eSchristos * OK when unsigned, so the return value is zero. 2179689912eSchristos */ 2189689912eSchristos 2199689912eSchristos static inline uint64_t 2209689912eSchristos key_to_minval(const isc_histo_t *hg, uint key) { 2219689912eSchristos uint chunksize = CHUNKSIZE(hg); 2229689912eSchristos uint exponent = (key / chunksize) - 1; 2239689912eSchristos uint64_t mantissa = (key % chunksize) + chunksize; 2249689912eSchristos return key < chunksize ? key : mantissa << exponent; 2259689912eSchristos } 2269689912eSchristos 2279689912eSchristos static inline uint64_t 2289689912eSchristos key_to_maxval(const isc_histo_t *hg, uint key) { 2299689912eSchristos return key_to_minval(hg, key + 1) - 1; 2309689912eSchristos } 2319689912eSchristos 2329689912eSchristos /**********************************************************************/ 2339689912eSchristos 2349689912eSchristos static hg_bucket_t * 2359689912eSchristos key_to_new_bucket(isc_histo_t *hg, uint key) { 2369689912eSchristos /* slow path */ 2379689912eSchristos uint chunksize = CHUNKSIZE(hg); 2389689912eSchristos uint chunk = key / chunksize; 2399689912eSchristos uint bucket = key % chunksize; 2409689912eSchristos hg_bucket_t *old_cp = NULL; 2419689912eSchristos hg_bucket_t *new_cp = isc_mem_cget(hg->mctx, CHUNKSIZE(hg), 2429689912eSchristos sizeof(hg_bucket_t)); 2439689912eSchristos hg_chunk_t *cpp = &hg->chunk[chunk]; 2449689912eSchristos if (atomic_compare_exchange_strong_acq_rel(cpp, &old_cp, new_cp)) { 2459689912eSchristos return &new_cp[bucket]; 2469689912eSchristos } else { 2479689912eSchristos /* lost the race, so use the winner's chunk */ 2489689912eSchristos isc_mem_cput(hg->mctx, new_cp, CHUNKSIZE(hg), 2499689912eSchristos sizeof(hg_bucket_t)); 2509689912eSchristos return &old_cp[bucket]; 2519689912eSchristos } 2529689912eSchristos } 2539689912eSchristos 2549689912eSchristos static hg_bucket_t * 2559689912eSchristos get_chunk(const isc_histo_t *hg, uint chunk) { 2569689912eSchristos return atomic_load_acquire(&hg->chunk[chunk]); 2579689912eSchristos } 2589689912eSchristos 2599689912eSchristos static inline hg_bucket_t * 2609689912eSchristos key_to_bucket(const isc_histo_t *hg, uint key) { 2619689912eSchristos /* fast path */ 2629689912eSchristos uint chunksize = CHUNKSIZE(hg); 2639689912eSchristos uint chunk = key / chunksize; 2649689912eSchristos uint bucket = key % chunksize; 2659689912eSchristos hg_bucket_t *cp = get_chunk(hg, chunk); 2669689912eSchristos return cp == NULL ? NULL : &cp[bucket]; 2679689912eSchristos } 2689689912eSchristos 2699689912eSchristos static inline uint64_t 2709689912eSchristos bucket_count(const hg_bucket_t *bp) { 2719689912eSchristos return bp == NULL ? 0 : atomic_load_relaxed(bp); 2729689912eSchristos } 2739689912eSchristos 2749689912eSchristos static inline uint64_t 2759689912eSchristos get_key_count(const isc_histo_t *hg, uint key) { 2769689912eSchristos return bucket_count(key_to_bucket(hg, key)); 2779689912eSchristos } 2789689912eSchristos 2799689912eSchristos static inline void 2809689912eSchristos add_key_count(isc_histo_t *hg, uint key, uint64_t inc) { 2819689912eSchristos /* fast path */ 2829689912eSchristos if (inc > 0) { 2839689912eSchristos hg_bucket_t *bp = key_to_bucket(hg, key); 2849689912eSchristos bp = bp != NULL ? bp : key_to_new_bucket(hg, key); 2859689912eSchristos atomic_fetch_add_relaxed(bp, inc); 2869689912eSchristos } 2879689912eSchristos } 2889689912eSchristos 2899689912eSchristos /**********************************************************************/ 2909689912eSchristos 2919689912eSchristos void 2929689912eSchristos isc_histo_add(isc_histo_t *hg, uint64_t value, uint64_t inc) { 2939689912eSchristos REQUIRE(HISTO_VALID(hg)); 2949689912eSchristos add_key_count(hg, value_to_key(hg, value), inc); 2959689912eSchristos } 2969689912eSchristos 2979689912eSchristos void 2989689912eSchristos isc_histo_inc(isc_histo_t *hg, uint64_t value) { 2999689912eSchristos isc_histo_add(hg, value, 1); 3009689912eSchristos } 3019689912eSchristos 3029689912eSchristos void 3039689912eSchristos isc_histo_put(isc_histo_t *hg, uint64_t min, uint64_t max, uint64_t count) { 3049689912eSchristos REQUIRE(HISTO_VALID(hg)); 3059689912eSchristos 3069689912eSchristos uint kmin = value_to_key(hg, min); 3079689912eSchristos uint kmax = value_to_key(hg, max); 3089689912eSchristos 3099689912eSchristos for (uint key = kmin; key <= kmax; key++) { 3109689912eSchristos uint64_t mid = ISC_MIN(max, key_to_maxval(hg, key)); 3119689912eSchristos double in_bucket = mid - min + 1; 3129689912eSchristos double remaining = max - min + 1; 3139689912eSchristos uint64_t inc = ceil(count * in_bucket / remaining); 3149689912eSchristos add_key_count(hg, key, inc); 3159689912eSchristos count -= inc; 3169689912eSchristos min = mid + 1; 3179689912eSchristos } 3189689912eSchristos } 3199689912eSchristos 3209689912eSchristos isc_result_t 3219689912eSchristos isc_histo_get(const isc_histo_t *hg, uint key, uint64_t *minp, uint64_t *maxp, 3229689912eSchristos uint64_t *countp) { 3239689912eSchristos REQUIRE(HISTO_VALID(hg)); 3249689912eSchristos 3259689912eSchristos if (key < BUCKETS(hg)) { 3269689912eSchristos SET_IF_NOT_NULL(minp, key_to_minval(hg, key)); 3279689912eSchristos SET_IF_NOT_NULL(maxp, key_to_maxval(hg, key)); 3289689912eSchristos SET_IF_NOT_NULL(countp, get_key_count(hg, key)); 3299689912eSchristos return ISC_R_SUCCESS; 3309689912eSchristos } else { 3319689912eSchristos return ISC_R_RANGE; 3329689912eSchristos } 3339689912eSchristos } 3349689912eSchristos 3359689912eSchristos void 3369689912eSchristos isc_histo_next(const isc_histo_t *hg, uint *keyp) { 3379689912eSchristos REQUIRE(HISTO_VALID(hg)); 3389689912eSchristos REQUIRE(keyp != NULL); 3399689912eSchristos 3409689912eSchristos uint chunksize = CHUNKSIZE(hg); 3419689912eSchristos uint buckets = BUCKETS(hg); 3429689912eSchristos uint key = *keyp; 3439689912eSchristos 3449689912eSchristos key++; 3459689912eSchristos while (key < buckets && key % chunksize == 0 && 3469689912eSchristos key_to_bucket(hg, key) == NULL) 3479689912eSchristos { 3489689912eSchristos key += chunksize; 3499689912eSchristos } 3509689912eSchristos *keyp = key; 3519689912eSchristos } 3529689912eSchristos 3539689912eSchristos void 3549689912eSchristos isc_histo_merge(isc_histo_t **targetp, const isc_histo_t *source) { 3559689912eSchristos REQUIRE(HISTO_VALID(source)); 3569689912eSchristos REQUIRE(targetp != NULL); 3579689912eSchristos 3589689912eSchristos if (*targetp != NULL) { 3599689912eSchristos REQUIRE(HISTO_VALID(*targetp)); 3609689912eSchristos } else { 3619689912eSchristos isc_histo_create(source->mctx, source->sigbits, targetp); 3629689912eSchristos } 3639689912eSchristos 3649689912eSchristos uint64_t min, max, count; 3659689912eSchristos for (uint key = 0; 3669689912eSchristos isc_histo_get(source, key, &min, &max, &count) == ISC_R_SUCCESS; 3679689912eSchristos isc_histo_next(source, &key)) 3689689912eSchristos { 3699689912eSchristos isc_histo_put(*targetp, min, max, count); 3709689912eSchristos } 3719689912eSchristos } 3729689912eSchristos 3739689912eSchristos /**********************************************************************/ 3749689912eSchristos 3759689912eSchristos void 3769689912eSchristos isc_histomulti_create(isc_mem_t *mctx, uint sigbits, isc_histomulti_t **hmp) { 3779689912eSchristos REQUIRE(hmp != NULL); 3789689912eSchristos REQUIRE(*hmp == NULL); 3799689912eSchristos 3809689912eSchristos uint size = isc_tid_count(); 3819689912eSchristos INSIST(size > 0); 3829689912eSchristos 3839689912eSchristos isc_histomulti_t *hm = isc_mem_cget(mctx, 1, 3849689912eSchristos STRUCT_FLEX_SIZE(hm, hg, size)); 3859689912eSchristos *hm = (isc_histomulti_t){ 3869689912eSchristos .magic = HISTOMULTI_MAGIC, 3879689912eSchristos .size = size, 3889689912eSchristos }; 3899689912eSchristos 3909689912eSchristos for (uint i = 0; i < hm->size; i++) { 3919689912eSchristos isc_histo_create(mctx, sigbits, &hm->hg[i]); 3929689912eSchristos } 3939689912eSchristos 3949689912eSchristos *hmp = hm; 3959689912eSchristos } 3969689912eSchristos 3979689912eSchristos void 3989689912eSchristos isc_histomulti_destroy(isc_histomulti_t **hmp) { 3999689912eSchristos REQUIRE(hmp != NULL); 4009689912eSchristos REQUIRE(HISTOMULTI_VALID(*hmp)); 4019689912eSchristos 4029689912eSchristos isc_histomulti_t *hm = *hmp; 4039689912eSchristos isc_mem_t *mctx = hm->hg[0]->mctx; 4049689912eSchristos *hmp = NULL; 4059689912eSchristos 4069689912eSchristos for (uint i = 0; i < hm->size; i++) { 4079689912eSchristos isc_histo_destroy(&hm->hg[i]); 4089689912eSchristos } 4099689912eSchristos 4109689912eSchristos isc_mem_put(mctx, hm, STRUCT_FLEX_SIZE(hm, hg, hm->size)); 4119689912eSchristos } 4129689912eSchristos 4139689912eSchristos void 4149689912eSchristos isc_histomulti_merge(isc_histo_t **hgp, const isc_histomulti_t *hm) { 4159689912eSchristos REQUIRE(HISTOMULTI_VALID(hm)); 4169689912eSchristos 4179689912eSchristos for (uint i = 0; i < hm->size; i++) { 4189689912eSchristos isc_histo_merge(hgp, hm->hg[i]); 4199689912eSchristos } 4209689912eSchristos } 4219689912eSchristos 4229689912eSchristos void 4239689912eSchristos isc_histomulti_add(isc_histomulti_t *hm, uint64_t value, uint64_t inc) { 4249689912eSchristos REQUIRE(HISTOMULTI_VALID(hm)); 4259689912eSchristos isc_histo_t *hg = hm->hg[isc_tid()]; 4269689912eSchristos add_key_count(hg, value_to_key(hg, value), inc); 4279689912eSchristos } 4289689912eSchristos 4299689912eSchristos void 4309689912eSchristos isc_histomulti_inc(isc_histomulti_t *hm, uint64_t value) { 4319689912eSchristos isc_histomulti_add(hm, value, 1); 4329689912eSchristos } 4339689912eSchristos 4349689912eSchristos /**********************************************************************/ 4359689912eSchristos 4369689912eSchristos /* 4379689912eSchristos * https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf 4389689912eSchristos * equation 4 (incremental mean) and equation 44 (incremental variance) 4399689912eSchristos */ 4409689912eSchristos void 4419689912eSchristos isc_histo_moments(const isc_histo_t *hg, double *pm0, double *pm1, 4429689912eSchristos double *pm2) { 4439689912eSchristos REQUIRE(HISTO_VALID(hg)); 4449689912eSchristos 4459689912eSchristos uint64_t pop = 0; 4469689912eSchristos double mean = 0.0; 4479689912eSchristos double sigma = 0.0; 4489689912eSchristos 4499689912eSchristos uint64_t min, max, count; 4509689912eSchristos for (uint key = 0; 4519689912eSchristos isc_histo_get(hg, key, &min, &max, &count) == ISC_R_SUCCESS; 4529689912eSchristos isc_histo_next(hg, &key)) 4539689912eSchristos { 4549689912eSchristos if (count == 0) { /* avoid division by zero */ 4559689912eSchristos continue; 4569689912eSchristos } 4579689912eSchristos double value = min / 2.0 + max / 2.0; 4589689912eSchristos double delta = value - mean; 4599689912eSchristos pop += count; 4609689912eSchristos mean += count * delta / pop; 4619689912eSchristos sigma += count * delta * (value - mean); 4629689912eSchristos } 4639689912eSchristos 4649689912eSchristos SET_IF_NOT_NULL(pm0, pop); 4659689912eSchristos SET_IF_NOT_NULL(pm1, mean); 4669689912eSchristos SET_IF_NOT_NULL(pm2, (pop > 0) ? sqrt(sigma / pop) : 0.0); 4679689912eSchristos } 4689689912eSchristos 4699689912eSchristos /* 4709689912eSchristos * Clamped linear interpolation 4719689912eSchristos * 4729689912eSchristos * `outrange` should be `((1 << n) - 1)` for some `n`; when `n` is larger 4739689912eSchristos * than 53, `outrange` can get rounded up to a power of 2, so we clamp the 4749689912eSchristos * result to keep within bounds (extra important when `max == UINT64_MAX`) 4759689912eSchristos */ 4769689912eSchristos static inline uint64_t 4779689912eSchristos lerp(uint64_t min, uint64_t max, uint64_t lo, uint64_t in, uint64_t hi) { 4789689912eSchristos double inrange = (double)(hi - lo); 4799689912eSchristos double inpart = (double)(in - lo); 4809689912eSchristos double outrange = (double)(max - min); 4819689912eSchristos double outpart = round(outrange * inpart / inrange); 4829689912eSchristos return min + ISC_MIN((uint64_t)outpart, max - min); 4839689912eSchristos } 4849689912eSchristos 4859689912eSchristos /* 4869689912eSchristos * There is non-zero space for the inner value, and it is inside the bounds 4879689912eSchristos */ 4889689912eSchristos static inline bool 4899689912eSchristos inside(uint64_t lo, uint64_t in, uint64_t hi) { 4909689912eSchristos return lo < hi && lo <= in && in <= hi; 4919689912eSchristos } 4929689912eSchristos 4939689912eSchristos isc_result_t 4949689912eSchristos isc_histo_quantiles(const isc_histo_t *hg, uint size, const double *fraction, 4959689912eSchristos uint64_t *value) { 4969689912eSchristos hg_bucket_t *chunk[CHUNKS]; 4979689912eSchristos uint64_t total[CHUNKS]; 4989689912eSchristos uint64_t rank[ISC_HISTO_MAXQUANTILES]; 4999689912eSchristos 5009689912eSchristos REQUIRE(HISTO_VALID(hg)); 5019689912eSchristos REQUIRE(0 < size && size <= ISC_HISTO_MAXQUANTILES); 5029689912eSchristos REQUIRE(fraction != NULL); 5039689912eSchristos REQUIRE(value != NULL); 5049689912eSchristos 5059689912eSchristos const uint maxchunk = MAXCHUNK(hg); 5069689912eSchristos const uint chunksize = CHUNKSIZE(hg); 5079689912eSchristos 5089689912eSchristos /* 5099689912eSchristos * Find out which chunks exist and what their totals are. We take a 5109689912eSchristos * copy of the chunk pointers to reduce the need for atomic ops 5119689912eSchristos * later on. Scan from low to high so that higher buckets are more 5129689912eSchristos * likely to be in the CPU cache when we scan from high to low. 5139689912eSchristos */ 5149689912eSchristos uint64_t population = 0; 5159689912eSchristos for (uint c = 0; c < maxchunk; c++) { 5169689912eSchristos chunk[c] = get_chunk(hg, c); 5179689912eSchristos total[c] = 0; 5189689912eSchristos if (chunk[c] != NULL) { 5199689912eSchristos for (uint b = chunksize; b-- > 0;) { 5209689912eSchristos total[c] += bucket_count(&chunk[c][b]); 5219689912eSchristos } 5229689912eSchristos population += total[c]; 5239689912eSchristos } 5249689912eSchristos } 5259689912eSchristos 5269689912eSchristos /* 5279689912eSchristos * Now we know the population, we can convert fractions to ranks. 5289689912eSchristos * Also ensure they are within bounds and in decreasing order. 5299689912eSchristos */ 5309689912eSchristos for (uint i = 0; i < size; i++) { 5319689912eSchristos REQUIRE(0.0 <= fraction[i] && fraction[i] <= 1.0); 5329689912eSchristos REQUIRE(i == 0 || fraction[i - 1] > fraction[i]); 5339689912eSchristos rank[i] = round(fraction[i] * population); 5349689912eSchristos } 5359689912eSchristos 5369689912eSchristos /* 5379689912eSchristos * Scan chunks from high to low, keeping track of the bounds on 5389689912eSchristos * each chunk's ranks. Each time we match `rank[i]`, move on to the 5399689912eSchristos * next rank and continue the scan from the same place. 5409689912eSchristos */ 5419689912eSchristos uint i = 0; 5429689912eSchristos uint64_t chunk_lo = population; 5439689912eSchristos for (uint c = maxchunk; c-- > 0;) { 5449689912eSchristos uint64_t chunk_hi = chunk_lo; 5459689912eSchristos chunk_lo = chunk_hi - total[c]; 5469689912eSchristos 5479689912eSchristos /* 5489689912eSchristos * Scan buckets backwards within this chunk, in a similar 5499689912eSchristos * manner to the chunk scan. Skip all or part of the loop 5509689912eSchristos * if the current rank is not in the chunk. 5519689912eSchristos */ 5529689912eSchristos uint64_t bucket_lo = chunk_hi; 5539689912eSchristos for (uint b = chunksize; 5549689912eSchristos b-- > 0 && inside(chunk_lo, rank[i], chunk_hi);) 5559689912eSchristos { 5569689912eSchristos uint64_t bucket_hi = bucket_lo; 5579689912eSchristos bucket_lo = bucket_hi - bucket_count(&chunk[c][b]); 5589689912eSchristos 5599689912eSchristos /* 5609689912eSchristos * Convert all ranks that fall in this bucket. 5619689912eSchristos */ 5629689912eSchristos while (inside(bucket_lo, rank[i], bucket_hi)) { 5639689912eSchristos uint key = chunksize * c + b; 5649689912eSchristos value[i] = lerp(key_to_minval(hg, key), 5659689912eSchristos key_to_maxval(hg, key), 5669689912eSchristos bucket_lo, rank[i], bucket_hi); 5679689912eSchristos if (++i == size) { 5689689912eSchristos return ISC_R_SUCCESS; 5699689912eSchristos } 5709689912eSchristos } 5719689912eSchristos } 5729689912eSchristos } 5739689912eSchristos 5749689912eSchristos return ISC_R_UNSET; 5759689912eSchristos } 5769689912eSchristos 5779689912eSchristos /**********************************************************************/ 578