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