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 <limits>
7 #include <math.h>
8 #include <string.h>
9 
10 #include <cobalt-client/cpp/histogram.h>
11 
12 #include <cobalt-client/cpp/histogram-internal.h>
13 #include <cobalt-client/cpp/metric-options.h>
14 
15 namespace cobalt_client {
16 namespace internal {
17 namespace {
18 
GetLinearBucketValue(uint32_t bucket_index,uint32_t bucket_count,const HistogramOptions & options)19 double GetLinearBucketValue(uint32_t bucket_index, uint32_t bucket_count,
20                             const HistogramOptions& options) {
21     if (bucket_index == 0) {
22         return -DBL_MAX;
23     }
24     return options.scalar * (bucket_index - 1) + options.offset;
25 }
26 
GetExponentialBucketValue(uint32_t bucket_index,uint32_t bucket_count,const HistogramOptions & options)27 double GetExponentialBucketValue(uint32_t bucket_index, uint32_t bucket_count,
28                                  const HistogramOptions& options) {
29     if (bucket_index == 0) {
30         return -DBL_MAX;
31     }
32     return options.scalar * pow(options.base, bucket_index - 1) + options.offset;
33 }
34 
GetLinearBucket(double value,uint32_t bucket_count,const HistogramOptions & options,double max_value)35 uint32_t GetLinearBucket(double value, uint32_t bucket_count, const HistogramOptions& options,
36                          double max_value) {
37     if (value < options.offset) {
38         return 0;
39     } else if (value >= max_value) {
40         return bucket_count - 1;
41     }
42     double unshifted_bucket = (value - options.offset) / options.scalar;
43     ZX_DEBUG_ASSERT(unshifted_bucket >= std::numeric_limits<uint32_t>::min());
44     ZX_DEBUG_ASSERT(unshifted_bucket <= std::numeric_limits<uint32_t>::max());
45     return static_cast<uint32_t>(unshifted_bucket) + 1;
46 }
47 
GetExponentialBucket(double value,uint32_t bucket_count,const HistogramOptions & options,double max_value)48 uint32_t GetExponentialBucket(double value, uint32_t bucket_count, const HistogramOptions& options,
49                               double max_value) {
50     if (value < options.scalar + options.offset) {
51         return 0;
52     } else if (value >= max_value) {
53         return bucket_count - 1;
54     }
55 
56     double diff = value - options.offset;
57     uint32_t unshifted_bucket = 0;
58     // Only use the formula if the difference is positive.
59     if (diff >= options.scalar) {
60         unshifted_bucket =
61             static_cast<uint32_t>(floor((log2(diff) - log2(options.scalar)) / log2(options.base)));
62     }
63     ZX_DEBUG_ASSERT(unshifted_bucket <= bucket_count + 1);
64 
65     double lower_bound = GetExponentialBucketValue(unshifted_bucket + 1, bucket_count, options);
66     if (lower_bound > value) {
67         --unshifted_bucket;
68     }
69     return unshifted_bucket + 1;
70 }
71 
LoadExponential(uint32_t bucket_count,HistogramOptions * options)72 void LoadExponential(uint32_t bucket_count, HistogramOptions* options) {
73     options->max_value = options->scalar * pow(options->base, bucket_count) + options->offset;
74     options->map_fn = [](double val, uint32_t bucket_count, const HistogramOptions& options) {
75         return internal::GetExponentialBucket(val, bucket_count, options, options.max_value);
76     };
77     options->reverse_map_fn = internal::GetExponentialBucketValue;
78 }
79 
LoadLinear(uint32_t bucket_count,HistogramOptions * options)80 void LoadLinear(uint32_t bucket_count, HistogramOptions* options) {
81     options->max_value = static_cast<double>(options->scalar * bucket_count + options->offset);
82     options->map_fn = [](double val, uint32_t bucket_count, const HistogramOptions& options) {
83         return internal::GetLinearBucket(val, bucket_count, options, options.max_value);
84     };
85     options->reverse_map_fn = internal::GetLinearBucketValue;
86 }
87 
88 } // namespace
89 
InitBucketBuffer(HistogramBucket * buckets,uint32_t bucket_count)90 void InitBucketBuffer(HistogramBucket* buckets, uint32_t bucket_count) {
91     for (uint32_t i = 0; i < bucket_count; ++i) {
92         buckets[i].count = 0;
93         buckets[i].index = i;
94     }
95 }
96 
InitLazily(const MetricOptions & options,HistogramBucket * buckets,uint32_t num_buckets,RemoteMetricInfo * metric_info)97 void InitLazily(const MetricOptions& options, HistogramBucket* buckets, uint32_t num_buckets,
98                 RemoteMetricInfo* metric_info) {
99     ZX_DEBUG_ASSERT(!options.IsLazy());
100     *metric_info = RemoteMetricInfo::From(options);
101     InitBucketBuffer(buckets, num_buckets);
102 }
103 
HistogramFlush(const RemoteMetricInfo & metric_info,Logger * logger,BaseCounter<uint64_t> * buckets,HistogramBucket * bucket_buffer,uint32_t num_buckets)104 bool HistogramFlush(const RemoteMetricInfo& metric_info, Logger* logger,
105                     BaseCounter<uint64_t>* buckets, HistogramBucket* bucket_buffer,
106                     uint32_t num_buckets) {
107     // Sets every bucket back to 0, not all buckets will be at the same instant, but
108     // eventual consistency in the backend is good enough.
109     for (uint32_t bucket_index = 0; bucket_index < num_buckets; ++bucket_index) {
110         bucket_buffer[bucket_index].count = buckets[bucket_index].Exchange();
111     }
112     return logger->Log(metric_info, bucket_buffer, num_buckets);
113 }
114 
HistogramUndoFlush(BaseCounter<uint64_t> * buckets,HistogramBucket * bucket_buffer,uint32_t num_buckets)115 void HistogramUndoFlush(BaseCounter<uint64_t>* buckets, HistogramBucket* bucket_buffer,
116                         uint32_t num_buckets) {
117     for (uint32_t bucket_index = 0; bucket_index < num_buckets; ++bucket_index) {
118         buckets[bucket_index].Increment(bucket_buffer[bucket_index].count);
119     }
120 }
121 
122 } // namespace internal
123 
124 HistogramOptions::HistogramOptions(const HistogramOptions&) = default;
125 
Exponential(uint32_t bucket_count,int64_t max)126 HistogramOptions HistogramOptions::Exponential(uint32_t bucket_count, int64_t max) {
127     return Exponential(bucket_count, 0, max);
128 }
129 
Exponential(uint32_t bucket_count,int64_t min,int64_t max)130 HistogramOptions HistogramOptions::Exponential(uint32_t bucket_count, int64_t min, int64_t max) {
131     ZX_DEBUG_ASSERT_MSG(min < max, "min must be smaller than max.");
132     // 2^bucket - 1 is the lower bound of the overflow bucket.
133     uint64_t overflow_limit = (1 << bucket_count) - 1;
134     uint64_t range = max - min;
135     uint32_t scalar = 1;
136 
137     // If max is past the overflow limit we need to scale up, and the minimum is 1.
138     if (range - overflow_limit > 0) {
139         scalar = static_cast<uint32_t>(range / overflow_limit);
140         if (range % overflow_limit != 0) {
141             scalar++;
142         }
143     }
144     ZX_DEBUG_ASSERT_MSG(2 * range >= scalar * overflow_limit,
145                         "range is too small for the number of buckets.");
146 
147     return CustomizedExponential(bucket_count, 2, scalar, min);
148 }
149 
CustomizedExponential(uint32_t bucket_count,uint32_t base,uint32_t scalar,int64_t min)150 HistogramOptions HistogramOptions::CustomizedExponential(uint32_t bucket_count, uint32_t base,
151                                                          uint32_t scalar, int64_t min) {
152     HistogramOptions options;
153     options.type = Type::kExponential;
154     options.base = static_cast<double>(base);
155     options.scalar = static_cast<double>(scalar);
156     options.offset = static_cast<double>(min - scalar);
157     internal::LoadExponential(bucket_count, &options);
158     return options;
159 }
160 
Linear(uint32_t bucket_count,int64_t max)161 HistogramOptions HistogramOptions::Linear(uint32_t bucket_count, int64_t max) {
162     return Linear(bucket_count, 0, max);
163 }
164 
Linear(uint32_t bucket_count,int64_t min,int64_t max)165 HistogramOptions HistogramOptions::Linear(uint32_t bucket_count, int64_t min, int64_t max) {
166     ZX_DEBUG_ASSERT_MSG(min < max, "min must be smaller than max.");
167     uint64_t range = max - min;
168     ZX_DEBUG_ASSERT_MSG(range >= bucket_count, "range is too small for the number of buckets.");
169     uint64_t scalar = range / bucket_count;
170     if (range % bucket_count != 0) {
171         scalar++;
172     }
173     ZX_DEBUG_ASSERT_MSG(scalar <= std::numeric_limits<uint32_t>::max(), "scalar overflow\n");
174     return CustomizedLinear(bucket_count, static_cast<uint32_t>(scalar), min);
175 }
176 
CustomizedLinear(uint32_t bucket_count,uint32_t step_size,int64_t min)177 HistogramOptions HistogramOptions::CustomizedLinear(uint32_t bucket_count, uint32_t step_size,
178                                                     int64_t min) {
179     HistogramOptions options;
180     options.scalar = static_cast<double>(step_size);
181     options.offset = static_cast<double>(min);
182     options.type = Type::kLinear;
183     internal::LoadLinear(bucket_count, &options);
184     return options;
185 }
186 
IsValid() const187 bool HistogramOptions::IsValid() const {
188     switch (type) {
189     case Type::kExponential:
190         if (base == 0) {
191             return false;
192         }
193         __FALLTHROUGH;
194     case Type::kLinear:
195         if (scalar == 0) {
196             return false;
197         }
198         break;
199     }
200 
201     return true;
202 }
203 
204 } // namespace cobalt_client
205