1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define STATSD_DEBUG false // STOPSHIP if true
18 #include "Log.h"
19
20 #include "histogram_parsing_utils.h"
21
22 #include <google/protobuf/repeated_field.h>
23
24 #include <algorithm>
25 #include <cmath>
26 #include <optional>
27 #include <variant>
28 #include <vector>
29
30 #include "guardrail/StatsdStats.h"
31 #include "src/statsd_config.pb.h"
32 #include "stats_util.h"
33
34 using google::protobuf::RepeatedPtrField;
35 using std::nullopt;
36 using std::optional;
37 using std::pow;
38 using std::variant;
39 using std::vector;
40
41 namespace android {
42 namespace os {
43 namespace statsd {
44 namespace {
45 constexpr int MIN_HISTOGRAM_BIN_COUNT = 2;
46 constexpr int MAX_HISTOGRAM_BIN_COUNT = 100;
47
generateLinearBins(float min,float max,int count)48 BinStarts generateLinearBins(float min, float max, int count) {
49 const float binWidth = (max - min) / count;
50
51 // 2 extra bins for underflow and overflow.
52 BinStarts bins(count + 2);
53 bins[0] = UNDERFLOW_BIN_START;
54 bins[1] = min;
55 bins.back() = max;
56 float curBin = min;
57
58 // Generate values starting from 3rd element to (n-1)th element.
59 std::generate(bins.begin() + 2, bins.end() - 1,
60 [&curBin, binWidth]() { return curBin += binWidth; });
61 return bins;
62 }
63
generateExponentialBins(float min,float max,int count)64 BinStarts generateExponentialBins(float min, float max, int count) {
65 BinStarts bins(count + 2);
66 bins[0] = UNDERFLOW_BIN_START;
67 bins[1] = min;
68 bins.back() = max;
69
70 // Determine the scale factor f, such that max = min * f^count.
71 // So, f = (max / min)^(1 / count) ie. f is the count'th-root of max / min.
72 const float factor = pow(max / min, 1.0 / count);
73
74 // Generate values starting from 3rd element to (n-1)th element.
75 float curBin = bins[1];
76 std::generate(bins.begin() + 2, bins.end() - 1,
77 [&curBin, factor]() { return curBin *= factor; });
78
79 return bins;
80 }
81
createExplicitBins(const BinStarts & configBins)82 BinStarts createExplicitBins(const BinStarts& configBins) {
83 BinStarts bins(configBins.size() + 1);
84 bins[0] = UNDERFLOW_BIN_START;
85 std::copy(configBins.begin(), configBins.end(), bins.begin() + 1);
86 return bins;
87 }
88 } // anonymous namespace
89
parseHistogramBinConfigs(const ValueMetric & metric,const vector<ValueMetric::AggregationType> & aggregationTypes)90 ParseHistogramBinConfigsResult parseHistogramBinConfigs(
91 const ValueMetric& metric, const vector<ValueMetric::AggregationType>& aggregationTypes) {
92 if (metric.histogram_bin_configs_size() == 0) {
93 return {};
94 }
95 vector<optional<const BinStarts>> binStartsList;
96 binStartsList.reserve(aggregationTypes.size());
97 RepeatedPtrField<HistogramBinConfig>::const_iterator binConfigIt =
98 metric.histogram_bin_configs().cbegin();
99 for (const ValueMetric::AggregationType aggType : aggregationTypes) {
100 if (aggType != ValueMetric::HISTOGRAM) {
101 binStartsList.push_back(nullopt);
102 continue;
103 }
104 const HistogramBinConfig& binConfig = *binConfigIt;
105 if (!binConfig.has_id()) {
106 ALOGE("cannot find id in HistogramBinConfig");
107 return InvalidConfigReason(
108 INVALID_CONFIG_REASON_VALUE_METRIC_HIST_MISSING_BIN_CONFIG_ID, metric.id());
109 }
110 switch (binConfig.binning_strategy_case()) {
111 case HistogramBinConfig::kGeneratedBins: {
112 const HistogramBinConfig::GeneratedBins& genBins = binConfig.generated_bins();
113 if (!genBins.has_min() || !genBins.has_max() || !genBins.has_count() ||
114 !genBins.has_strategy()) {
115 ALOGE("Missing generated bin arguments");
116 return InvalidConfigReason(
117 INVALID_CONFIG_REASON_VALUE_METRIC_HIST_MISSING_GENERATED_BINS_ARGS,
118 metric.id());
119 }
120 if (genBins.count() < MIN_HISTOGRAM_BIN_COUNT) {
121 ALOGE("Too few generated bins");
122 return InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_TOO_FEW_BINS,
123 metric.id());
124 }
125 if (genBins.count() > MAX_HISTOGRAM_BIN_COUNT) {
126 ALOGE("Too many generated bins");
127 return InvalidConfigReason(
128 INVALID_CONFIG_REASON_VALUE_METRIC_HIST_TOO_MANY_BINS, metric.id());
129 }
130 if (genBins.min() >= genBins.max()) {
131 ALOGE("Min should be lower than max for generated bins");
132 return InvalidConfigReason(
133 INVALID_CONFIG_REASON_VALUE_METRIC_HIST_GENERATED_BINS_INVALID_MIN_MAX,
134 metric.id());
135 }
136
137 switch (genBins.strategy()) {
138 case HistogramBinConfig::GeneratedBins::LINEAR: {
139 binStartsList.push_back(
140 generateLinearBins(genBins.min(), genBins.max(), genBins.count()));
141 break;
142 }
143 case HistogramBinConfig::GeneratedBins::EXPONENTIAL: {
144 // The starting point of exponential bins has to be greater than 0.
145 if (genBins.min() <= 0) {
146 ALOGE("Min should be greater than 0 for exponential bins");
147 return InvalidConfigReason(
148 INVALID_CONFIG_REASON_VALUE_METRIC_HIST_GENERATED_BINS_INVALID_MIN_MAX,
149 metric.id());
150 }
151 binStartsList.push_back(generateExponentialBins(
152 genBins.min(), genBins.max(), genBins.count()));
153 break;
154 }
155 default: {
156 ALOGE("Unknown GeneratedBins strategy");
157 return InvalidConfigReason(
158 INVALID_CONFIG_REASON_VALUE_METRIC_HIST_MISSING_GENERATED_BINS_ARGS,
159 metric.id());
160 }
161 }
162
163 break;
164 }
165 case HistogramBinConfig::kExplicitBins: {
166 const HistogramBinConfig::ExplicitBins& explicitBins = binConfig.explicit_bins();
167 if (explicitBins.bin_size() < MIN_HISTOGRAM_BIN_COUNT) {
168 ALOGE("Too few explicit bins");
169 return InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_TOO_FEW_BINS,
170 metric.id());
171 }
172 if (explicitBins.bin_size() > MAX_HISTOGRAM_BIN_COUNT) {
173 ALOGE("Too many explicit bins");
174 return InvalidConfigReason(
175 INVALID_CONFIG_REASON_VALUE_METRIC_HIST_TOO_MANY_BINS, metric.id());
176 }
177
178 // Ensure explicit bins are strictly ordered in ascending order.
179 // Use adjacent_find to find any 2 adjacent bin boundaries, b1 and b2, such that b1
180 // >= b2. If any such adjacent bins are found, the bins are not strictly ascending
181 // and the bin definition is invalid.
182 if (std::adjacent_find(explicitBins.bin().begin(), explicitBins.bin().end(),
183 std::greater_equal<float>()) != explicitBins.bin().end()) {
184 ALOGE("Explicit bins are not strictly ordered in ascending order");
185 return InvalidConfigReason(
186 INVALID_CONFIG_REASON_VALUE_METRIC_HIST_EXPLICIT_BINS_NOT_STRICTLY_ORDERED,
187 metric.id());
188 }
189
190 binStartsList.push_back(
191 createExplicitBins({explicitBins.bin().begin(), explicitBins.bin().end()}));
192
193 break;
194 }
195 case HistogramBinConfig::kClientAggregatedBins: {
196 binStartsList.push_back(nullopt);
197 break;
198 }
199 default: {
200 ALOGE("Either generated or explicit binning strategy must be set");
201 return InvalidConfigReason(
202 INVALID_CONFIG_REASON_VALUE_METRIC_HIST_UNKNOWN_BINNING_STRATEGY,
203 metric.id());
204 break;
205 }
206 }
207 binConfigIt++;
208 }
209 return binStartsList;
210 }
211
212 } // namespace statsd
213 } // namespace os
214 } // namespace android
215