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