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