1// Copyright 2018 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <float.h> 6#include <math.h> 7#include <string.h> 8 9#include <cobalt-client/cpp/histogram.h> 10 11#include <cobalt-client/cpp/histogram-internal.h> 12#include <cobalt-client/cpp/histogram-options.h> 13#include <fbl/limits.h> 14#include <fuchsia/cobalt/c/fidl.h> 15 16namespace cobalt_client { 17namespace internal { 18namespace { 19 20double GetLinearBucketValue(uint32_t bucket_index, const HistogramOptions& options) { 21 if (bucket_index == 0) { 22 return -DBL_MAX; 23 } 24 return options.scalar * (bucket_index - 1) + options.offset; 25} 26 27double GetExponentialBucketValue(uint32_t bucket_index, const HistogramOptions& options) { 28 if (bucket_index == 0) { 29 return -DBL_MAX; 30 } 31 return options.scalar * pow(options.base, bucket_index - 1) + options.offset; 32} 33 34uint32_t GetLinearBucket(double value, const HistogramOptions& options, double max_value) { 35 if (value < options.offset) { 36 return 0; 37 } else if (value >= max_value) { 38 return options.bucket_count + 1; 39 } 40 double unshifted_bucket = (value - options.offset) / options.scalar; 41 ZX_DEBUG_ASSERT(unshifted_bucket >= fbl::numeric_limits<uint32_t>::min()); 42 ZX_DEBUG_ASSERT(unshifted_bucket <= fbl::numeric_limits<uint32_t>::max()); 43 return static_cast<uint32_t>(unshifted_bucket) + 1; 44} 45 46uint32_t GetExponentialBucket(double value, const HistogramOptions& options, double max_value) { 47 if (value < options.scalar + options.offset) { 48 return 0; 49 } else if (value >= max_value) { 50 return options.bucket_count + 1; 51 } 52 53 // Use bigger size double to perform the calculations to avoid precision errors near boundaries. 54 double diff = value - options.offset; 55 uint32_t unshifted_bucket = 0; 56 // Only use the formula if the difference is positive. 57 if (diff >= options.scalar) { 58 unshifted_bucket = static_cast<uint32_t>(floor((log2(diff) - log2(options.scalar)) / log2(options.base))); 59 } 60 ZX_DEBUG_ASSERT(unshifted_bucket <= options.bucket_count + 1); 61 62 double lower_bound = GetExponentialBucketValue(unshifted_bucket + 1, options); 63 if (lower_bound > value) { 64 --unshifted_bucket; 65 } 66 return unshifted_bucket + 1; 67} 68 69void LoadExponential(HistogramOptions* options) { 70 double max_value = 71 options->scalar * pow(options->base, options->bucket_count) + options->offset; 72 options->map_fn = [max_value](double val, const HistogramOptions& options) { 73 return internal::GetExponentialBucket(val, options, max_value); 74 }; 75 options->reverse_map_fn = internal::GetExponentialBucketValue; 76} 77 78void LoadLinear(HistogramOptions* options) { 79 double max_value = 80 static_cast<double>(options->scalar * options->bucket_count + options->offset); 81 options->map_fn = [max_value](double val, const HistogramOptions& options) { 82 return internal::GetLinearBucket(val, options, max_value); 83 }; 84 options->reverse_map_fn = internal::GetLinearBucketValue; 85} 86 87} // namespace 88 89BaseHistogram::BaseHistogram(uint32_t num_buckets) { 90 buckets_.reserve(num_buckets); 91 for (uint32_t i = 0; i < num_buckets; ++i) { 92 buckets_.push_back(BaseCounter()); 93 } 94} 95 96BaseHistogram::BaseHistogram(BaseHistogram&& other) = default; 97 98RemoteHistogram::RemoteHistogram(uint32_t num_buckets, uint64_t metric_id, 99 const fbl::Vector<Metadata>& metadata) 100 : BaseHistogram(num_buckets), buffer_(metadata), metric_id_(metric_id) { 101 bucket_buffer_.reserve(num_buckets); 102 for (uint32_t i = 0; i < num_buckets; ++i) { 103 HistogramBucket bucket; 104 bucket.count = 0; 105 bucket.index = i; 106 bucket_buffer_.push_back(bucket); 107 } 108 auto* buckets = buffer_.mutable_event_data(); 109 buckets->set_data(bucket_buffer_.get()); 110 buckets->set_count(bucket_buffer_.size()); 111} 112 113RemoteHistogram::RemoteHistogram(RemoteHistogram&& other) 114 : BaseHistogram(fbl::move(other)), buffer_(fbl::move(other.buffer_)), 115 bucket_buffer_(fbl::move(other.bucket_buffer_)), metric_id_(other.metric_id_) {} 116 117bool RemoteHistogram::Flush(const RemoteHistogram::FlushFn& flush_handler) { 118 if (!buffer_.TryBeginFlush()) { 119 return false; 120 } 121 122 // Sets every bucket back to 0, not all buckets will be at the same instant, but 123 // eventual consistency in the backend is good enough. 124 for (uint32_t bucket_index = 0; bucket_index < bucket_buffer_.size(); ++bucket_index) { 125 bucket_buffer_[bucket_index].count = buckets_[bucket_index].Exchange(); 126 } 127 128 flush_handler(metric_id_, buffer_, fbl::BindMember(&buffer_, &EventBuffer::CompleteFlush)); 129 return true; 130} 131} // namespace internal 132 133HistogramOptions::HistogramOptions(const HistogramOptions& other) 134 : base(other.base), scalar(other.scalar), offset(other.offset), 135 bucket_count(other.bucket_count), type(other.type) { 136 if (type == Type::kLinear) { 137 internal::LoadLinear(this); 138 } else { 139 internal::LoadExponential(this); 140 } 141} 142 143HistogramOptions HistogramOptions::Exponential(uint32_t bucket_count, uint32_t base, 144 uint32_t scalar, int64_t offset) { 145 HistogramOptions options; 146 options.bucket_count = bucket_count; 147 options.base = base; 148 options.scalar = scalar; 149 options.offset = static_cast<double>(offset - scalar); 150 options.type = Type::kExponential; 151 internal::LoadExponential(&options); 152 return options; 153} 154 155HistogramOptions HistogramOptions::Linear(uint32_t bucket_count, uint32_t scalar, int64_t offset) { 156 HistogramOptions options; 157 options.bucket_count = bucket_count; 158 options.scalar = scalar; 159 options.offset = static_cast<double>(offset); 160 options.type = Type::kLinear; 161 internal::LoadLinear(&options); 162 return options; 163} 164 165Histogram::Histogram(HistogramOptions* options, internal::RemoteHistogram* remote_histogram) 166 : options_(options), remote_histogram_(remote_histogram) {} 167 168Histogram::Histogram(const Histogram&) = default; 169Histogram::Histogram(Histogram&&) = default; 170Histogram& Histogram::operator=(const Histogram&) = default; 171Histogram& Histogram::operator=(Histogram&&) = default; 172Histogram::~Histogram() = default; 173 174template <typename ValueType> void Histogram::Add(ValueType value, Histogram::Count times) { 175 double dbl_value = static_cast<double>(value); 176 uint32_t bucket = options_->map_fn(dbl_value, *options_); 177 remote_histogram_->IncrementCount(bucket, times); 178} 179 180template <typename ValueType> Histogram::Count Histogram::GetRemoteCount(ValueType value) const { 181 double dbl_value = static_cast<double>(value); 182 uint32_t bucket = options_->map_fn(dbl_value, *options_); 183 return remote_histogram_->GetCount(bucket); 184} 185 186// Supported template instantiations. 187template void Histogram::Add<double>(double, Histogram::Count); 188template void Histogram::Add<int32_t>(int32_t, Histogram::Count); 189template void Histogram::Add<uint32_t>(uint32_t, Histogram::Count); 190template void Histogram::Add<int64_t>(int64_t, Histogram::Count); 191template void Histogram::Add<uint64_t>(uint64_t, Histogram::Count); 192 193template Histogram::Count Histogram::GetRemoteCount<double>(double) const; 194template Histogram::Count Histogram::GetRemoteCount<int32_t>(int32_t) const; 195template Histogram::Count Histogram::GetRemoteCount<uint32_t>(uint32_t) const; 196template Histogram::Count Histogram::GetRemoteCount<int64_t>(int64_t) const; 197template Histogram::Count Histogram::GetRemoteCount<uint64_t>(uint64_t) const; 198 199} // namespace cobalt_client 200