/* * * Copyright 2015 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include "test/core/util/histogram.h" #include #include #include #include #include #include #include "src/core/lib/gpr/useful.h" /* Histograms are stored with exponentially increasing bucket sizes. The first bucket is [0, m) where m = 1 + resolution Bucket n (n>=1) contains [m**n, m**(n+1)) There are sufficient buckets to reach max_bucket_start */ struct grpc_histogram { /* Sum of all values seen so far */ double sum; /* Sum of squares of all values seen so far */ double sum_of_squares; /* number of values seen so far */ double count; /* m in the description */ double multiplier; double one_on_log_multiplier; /* minimum value seen */ double min_seen; /* maximum value seen */ double max_seen; /* maximum representable value */ double max_possible; /* number of buckets */ size_t num_buckets; /* the buckets themselves */ uint32_t* buckets; }; /* determine a bucket index given a value - does no bounds checking */ static size_t bucket_for_unchecked(grpc_histogram* h, double x) { return static_cast(log(x) * h->one_on_log_multiplier); } /* bounds checked version of the above */ static size_t bucket_for(grpc_histogram* h, double x) { size_t bucket = bucket_for_unchecked(h, GPR_CLAMP(x, 1.0, h->max_possible)); GPR_ASSERT(bucket < h->num_buckets); return bucket; } /* at what value does a bucket start? */ static double bucket_start(grpc_histogram* h, double x) { return pow(h->multiplier, x); } grpc_histogram* grpc_histogram_create(double resolution, double max_bucket_start) { grpc_histogram* h = static_cast(gpr_malloc(sizeof(grpc_histogram))); GPR_ASSERT(resolution > 0.0); GPR_ASSERT(max_bucket_start > resolution); h->sum = 0.0; h->sum_of_squares = 0.0; h->multiplier = 1.0 + resolution; h->one_on_log_multiplier = 1.0 / log(1.0 + resolution); h->max_possible = max_bucket_start; h->count = 0.0; h->min_seen = max_bucket_start; h->max_seen = 0.0; h->num_buckets = bucket_for_unchecked(h, max_bucket_start) + 1; GPR_ASSERT(h->num_buckets > 1); GPR_ASSERT(h->num_buckets < 100000000); h->buckets = static_cast(gpr_zalloc(sizeof(uint32_t) * h->num_buckets)); return h; } void grpc_histogram_destroy(grpc_histogram* h) { gpr_free(h->buckets); gpr_free(h); } void grpc_histogram_add(grpc_histogram* h, double x) { h->sum += x; h->sum_of_squares += x * x; h->count++; if (x < h->min_seen) { h->min_seen = x; } if (x > h->max_seen) { h->max_seen = x; } h->buckets[bucket_for(h, x)]++; } int grpc_histogram_merge(grpc_histogram* dst, const grpc_histogram* src) { if ((dst->num_buckets != src->num_buckets) || (dst->multiplier != src->multiplier)) { /* Fail because these histograms don't match */ return 0; } grpc_histogram_merge_contents(dst, src->buckets, src->num_buckets, src->min_seen, src->max_seen, src->sum, src->sum_of_squares, src->count); return 1; } void grpc_histogram_merge_contents(grpc_histogram* dst, const uint32_t* data, size_t data_count, double min_seen, double max_seen, double sum, double sum_of_squares, double count) { size_t i; GPR_ASSERT(dst->num_buckets == data_count); dst->sum += sum; dst->sum_of_squares += sum_of_squares; dst->count += count; if (min_seen < dst->min_seen) { dst->min_seen = min_seen; } if (max_seen > dst->max_seen) { dst->max_seen = max_seen; } for (i = 0; i < dst->num_buckets; i++) { dst->buckets[i] += data[i]; } } static double threshold_for_count_below(grpc_histogram* h, double count_below) { double count_so_far; double lower_bound; double upper_bound; size_t lower_idx; size_t upper_idx; if (h->count == 0) { return 0.0; } if (count_below <= 0) { return h->min_seen; } if (count_below >= h->count) { return h->max_seen; } /* find the lowest bucket that gets us above count_below */ count_so_far = 0.0; for (lower_idx = 0; lower_idx < h->num_buckets; lower_idx++) { count_so_far += h->buckets[lower_idx]; if (count_so_far >= count_below) { break; } } if (count_so_far == count_below) { /* this bucket hits the threshold exactly... we should be midway through any run of zero values following the bucket */ for (upper_idx = lower_idx + 1; upper_idx < h->num_buckets; upper_idx++) { if (h->buckets[upper_idx]) { break; } } return (bucket_start(h, static_cast(lower_idx)) + bucket_start(h, static_cast(upper_idx))) / 2.0; } else { /* treat values as uniform throughout the bucket, and find where this value should lie */ lower_bound = bucket_start(h, static_cast(lower_idx)); upper_bound = bucket_start(h, static_cast(lower_idx + 1)); return GPR_CLAMP(upper_bound - (upper_bound - lower_bound) * (count_so_far - count_below) / h->buckets[lower_idx], h->min_seen, h->max_seen); } } double grpc_histogram_percentile(grpc_histogram* h, double percentile) { return threshold_for_count_below(h, h->count * percentile / 100.0); } double grpc_histogram_mean(grpc_histogram* h) { GPR_ASSERT(h->count != 0); return h->sum / h->count; } double grpc_histogram_stddev(grpc_histogram* h) { return sqrt(grpc_histogram_variance(h)); } double grpc_histogram_variance(grpc_histogram* h) { if (h->count == 0) return 0.0; return (h->sum_of_squares * h->count - h->sum * h->sum) / (h->count * h->count); } double grpc_histogram_maximum(grpc_histogram* h) { return h->max_seen; } double grpc_histogram_minimum(grpc_histogram* h) { return h->min_seen; } double grpc_histogram_count(grpc_histogram* h) { return h->count; } double grpc_histogram_sum(grpc_histogram* h) { return h->sum; } double grpc_histogram_sum_of_squares(grpc_histogram* h) { return h->sum_of_squares; } const uint32_t* grpc_histogram_get_contents(grpc_histogram* h, size_t* size) { *size = h->num_buckets; return h->buckets; }