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 #include "src/metrics/parsing_utils/histogram_parsing_utils.h"
18 
19 #include <gtest/gtest.h>
20 
21 #include <algorithm>
22 #include <numeric>
23 #include <variant>
24 #include <vector>
25 
26 #include "src/guardrail/StatsdStats.h"
27 #include "src/stats_util.h"
28 #include "src/statsd_config.pb.h"
29 #include "tests/metrics/parsing_utils/parsing_test_utils.h"
30 #include "tests/statsd_test_util.h"
31 
32 #ifdef __ANDROID__
33 
34 using namespace std;
35 using namespace testing;
36 
37 namespace android {
38 namespace os {
39 namespace statsd {
40 namespace {
41 
42 using HistogramParsingUtilsTest = InitConfigTest;
43 
44 constexpr auto LINEAR = HistogramBinConfig::GeneratedBins::LINEAR;
45 constexpr auto EXPONENTIAL = HistogramBinConfig::GeneratedBins::EXPONENTIAL;
46 
TEST_F(HistogramParsingUtilsTest,TestMissingHistogramBinConfigId)47 TEST_F(HistogramParsingUtilsTest, TestMissingHistogramBinConfigId) {
48     StatsdConfig config = createExplicitHistogramStatsdConfig(/* bins */ {5});
49     config.mutable_value_metric(0)->mutable_histogram_bin_configs()->Mutable(0)->clear_id();
50 
51     EXPECT_EQ(initConfig(config),
52               InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_MISSING_BIN_CONFIG_ID,
53                                   config.value_metric(0).id()));
54 }
55 
TEST_F(HistogramParsingUtilsTest,TestMissingHistogramBinConfigBinningStrategy)56 TEST_F(HistogramParsingUtilsTest, TestMissingHistogramBinConfigBinningStrategy) {
57     StatsdConfig config = createHistogramStatsdConfig();
58     config.mutable_value_metric(0)->add_histogram_bin_configs()->set_id(1);
59 
60     EXPECT_EQ(initConfig(config),
61               InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_UNKNOWN_BINNING_STRATEGY,
62                                   config.value_metric(0).id()));
63 }
64 
TEST_F(HistogramParsingUtilsTest,TestGeneratedBinsMissingMin)65 TEST_F(HistogramParsingUtilsTest, TestGeneratedBinsMissingMin) {
66     StatsdConfig config =
67             createGeneratedHistogramStatsdConfig(/* min */ 1, /* max */ 10, /* count */ 5, LINEAR);
68     config.mutable_value_metric(0)
69             ->mutable_histogram_bin_configs(0)
70             ->mutable_generated_bins()
71             ->clear_min();
72 
73     EXPECT_EQ(
74             initConfig(config),
75             InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_MISSING_GENERATED_BINS_ARGS,
76                                 config.value_metric(0).id()));
77 }
78 
TEST_F(HistogramParsingUtilsTest,TestGeneratedBinsMissingMax)79 TEST_F(HistogramParsingUtilsTest, TestGeneratedBinsMissingMax) {
80     StatsdConfig config =
81             createGeneratedHistogramStatsdConfig(/* min */ 1, /* max */ 10, /* count */ 5, LINEAR);
82     config.mutable_value_metric(0)
83             ->mutable_histogram_bin_configs(0)
84             ->mutable_generated_bins()
85             ->clear_max();
86 
87     EXPECT_EQ(
88             initConfig(config),
89             InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_MISSING_GENERATED_BINS_ARGS,
90                                 config.value_metric(0).id()));
91 }
92 
TEST_F(HistogramParsingUtilsTest,TestGeneratedBinsMissingCount)93 TEST_F(HistogramParsingUtilsTest, TestGeneratedBinsMissingCount) {
94     StatsdConfig config =
95             createGeneratedHistogramStatsdConfig(/* min */ 1, /* max */ 10, /* count */ 5, LINEAR);
96     config.mutable_value_metric(0)
97             ->mutable_histogram_bin_configs(0)
98             ->mutable_generated_bins()
99             ->clear_count();
100 
101     EXPECT_EQ(
102             initConfig(config),
103             InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_MISSING_GENERATED_BINS_ARGS,
104                                 config.value_metric(0).id()));
105 }
106 
TEST_F(HistogramParsingUtilsTest,TestGeneratedBinsMissingStrategy)107 TEST_F(HistogramParsingUtilsTest, TestGeneratedBinsMissingStrategy) {
108     StatsdConfig config = createHistogramStatsdConfig();
109     *config.mutable_value_metric(0)->add_histogram_bin_configs() =
110             createGeneratedBinConfig(/* id */ 1, /* min */ 1, /* max */ 10, /* count */ 5,
111                                      HistogramBinConfig::GeneratedBins::UNKNOWN);
112 
113     EXPECT_EQ(
114             initConfig(config),
115             InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_MISSING_GENERATED_BINS_ARGS,
116                                 config.value_metric(0).id()));
117 
118     config.mutable_value_metric(0)
119             ->mutable_histogram_bin_configs(0)
120             ->mutable_generated_bins()
121             ->clear_strategy();
122 
123     clearData();
124     EXPECT_EQ(
125             initConfig(config),
126             InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_MISSING_GENERATED_BINS_ARGS,
127                                 config.value_metric(0).id()));
128 }
129 
TEST_F(HistogramParsingUtilsTest,TestGeneratedBinsMinNotLessThanMax)130 TEST_F(HistogramParsingUtilsTest, TestGeneratedBinsMinNotLessThanMax) {
131     StatsdConfig config =
132             createGeneratedHistogramStatsdConfig(/* min */ 10, /* max */ 10, /* count */ 5, LINEAR);
133 
134     EXPECT_EQ(initConfig(config),
135               InvalidConfigReason(
136                       INVALID_CONFIG_REASON_VALUE_METRIC_HIST_GENERATED_BINS_INVALID_MIN_MAX,
137                       config.value_metric(0).id()));
138 }
139 
TEST_F(HistogramParsingUtilsTest,TestExponentialBinsMinNotLessThanMax)140 TEST_F(HistogramParsingUtilsTest, TestExponentialBinsMinNotLessThanMax) {
141     StatsdConfig config = createGeneratedHistogramStatsdConfig(/* min */ 10, /* max */ 10,
142                                                                /* count */ 5, EXPONENTIAL);
143 
144     EXPECT_EQ(initConfig(config),
145               InvalidConfigReason(
146                       INVALID_CONFIG_REASON_VALUE_METRIC_HIST_GENERATED_BINS_INVALID_MIN_MAX,
147                       config.value_metric(0).id()));
148 }
149 
TEST_F(HistogramParsingUtilsTest,TestExponentialBinsZeroMin)150 TEST_F(HistogramParsingUtilsTest, TestExponentialBinsZeroMin) {
151     StatsdConfig config = createGeneratedHistogramStatsdConfig(/* min */ 0, /* max */ 10,
152                                                                /* count */ 5, EXPONENTIAL);
153 
154     EXPECT_EQ(initConfig(config),
155               InvalidConfigReason(
156                       INVALID_CONFIG_REASON_VALUE_METRIC_HIST_GENERATED_BINS_INVALID_MIN_MAX,
157                       config.value_metric(0).id()));
158 }
159 
TEST_F(HistogramParsingUtilsTest,TestTooFewGeneratedBins)160 TEST_F(HistogramParsingUtilsTest, TestTooFewGeneratedBins) {
161     StatsdConfig config =
162             createGeneratedHistogramStatsdConfig(/* min */ 10, /* max */ 50, /* count */ 2, LINEAR);
163 
164     EXPECT_EQ(initConfig(config), nullopt);
165 
166     config.mutable_value_metric(0)
167             ->mutable_histogram_bin_configs(0)
168             ->mutable_generated_bins()
169             ->set_count(1);
170 
171     clearData();
172     EXPECT_EQ(initConfig(config),
173               InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_TOO_FEW_BINS,
174                                   config.value_metric(0).id()));
175 }
176 
TEST_F(HistogramParsingUtilsTest,TestTooManyGeneratedBins)177 TEST_F(HistogramParsingUtilsTest, TestTooManyGeneratedBins) {
178     StatsdConfig config = createGeneratedHistogramStatsdConfig(/* min */ 10, /* max */ 50,
179                                                                /* count */ 100, LINEAR);
180 
181     EXPECT_EQ(initConfig(config), nullopt);
182 
183     config.mutable_value_metric(0)
184             ->mutable_histogram_bin_configs(0)
185             ->mutable_generated_bins()
186             ->set_count(101);
187 
188     clearData();
189     EXPECT_EQ(initConfig(config),
190               InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_TOO_MANY_BINS,
191                                   config.value_metric(0).id()));
192 }
193 
TEST_F(HistogramParsingUtilsTest,TestTooFewExplicitBins)194 TEST_F(HistogramParsingUtilsTest, TestTooFewExplicitBins) {
195     StatsdConfig config = createExplicitHistogramStatsdConfig(/* bins */ {1});
196 
197     EXPECT_EQ(initConfig(config),
198               InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_TOO_FEW_BINS,
199                                   config.value_metric(0).id()));
200 
201     config.mutable_value_metric(0)
202             ->mutable_histogram_bin_configs(0)
203             ->mutable_explicit_bins()
204             ->add_bin(2);
205 
206     clearData();
207     EXPECT_EQ(initConfig(config), nullopt);
208 }
209 
TEST_F(HistogramParsingUtilsTest,TestTooManyExplicitBins)210 TEST_F(HistogramParsingUtilsTest, TestTooManyExplicitBins) {
211     BinStarts bins(100);
212     // Fill bins with values 1, 2, ..., 100.
213     std::iota(std::begin(bins), std::end(bins), 1);
214     StatsdConfig config = createExplicitHistogramStatsdConfig(bins);
215 
216     EXPECT_EQ(initConfig(config), nullopt);
217 
218     config.mutable_value_metric(0)
219             ->mutable_histogram_bin_configs(0)
220             ->mutable_explicit_bins()
221             ->add_bin(101);
222 
223     clearData();
224     EXPECT_EQ(initConfig(config),
225               InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_HIST_TOO_MANY_BINS,
226                                   config.value_metric(0).id()));
227 }
228 
TEST_F(HistogramParsingUtilsTest,TestExplicitBinsDuplicateValues)229 TEST_F(HistogramParsingUtilsTest, TestExplicitBinsDuplicateValues) {
230     BinStarts bins(50);
231     // Fill bins with values 1, 2, ..., 50.
232     std::iota(std::begin(bins), std::end(bins), 1);
233     StatsdConfig config = createExplicitHistogramStatsdConfig(bins);
234 
235     config.mutable_value_metric(0)
236             ->mutable_histogram_bin_configs(0)
237             ->mutable_explicit_bins()
238             ->add_bin(50);
239 
240     EXPECT_EQ(initConfig(config),
241               InvalidConfigReason(
242                       INVALID_CONFIG_REASON_VALUE_METRIC_HIST_EXPLICIT_BINS_NOT_STRICTLY_ORDERED,
243                       config.value_metric(0).id()));
244 }
245 
TEST_F(HistogramParsingUtilsTest,TestExplicitBinsUnsortedValues)246 TEST_F(HistogramParsingUtilsTest, TestExplicitBinsUnsortedValues) {
247     BinStarts bins(50);
248     // Fill bins with values 1, 2, ..., 50.
249     std::iota(std::begin(bins), std::end(bins), 1);
250 
251     // Swap values at indices 10 and 40.
252     std::swap(bins[10], bins[40]);
253 
254     StatsdConfig config = createExplicitHistogramStatsdConfig(bins);
255 
256     EXPECT_EQ(initConfig(config),
257               InvalidConfigReason(
258                       INVALID_CONFIG_REASON_VALUE_METRIC_HIST_EXPLICIT_BINS_NOT_STRICTLY_ORDERED,
259                       config.value_metric(0).id()));
260 }
261 
getParsedBins(const ValueMetric & metric)262 const BinStarts getParsedBins(const ValueMetric& metric) {
263     ParseHistogramBinConfigsResult result =
264             parseHistogramBinConfigs(metric, /* aggregationTypes */ {ValueMetric::HISTOGRAM});
265     return holds_alternative<vector<optional<const BinStarts>>>(result)
266                    ? *get<vector<optional<const BinStarts>>>(result).front()
267                    : BinStarts();
268 }
269 
getParsedGeneratedBins(float min,float max,int count,HistogramBinConfig::GeneratedBins::Strategy strategy)270 const BinStarts getParsedGeneratedBins(float min, float max, int count,
271                                        HistogramBinConfig::GeneratedBins::Strategy strategy) {
272     StatsdConfig config = createGeneratedHistogramStatsdConfig(min, max, count, strategy);
273 
274     return getParsedBins(config.value_metric(0));
275 }
276 
getParsedLinearBins(float min,float max,int count)277 const BinStarts getParsedLinearBins(float min, float max, int count) {
278     return getParsedGeneratedBins(min, max, count, LINEAR);
279 }
280 
TEST_F(HistogramParsingUtilsTest,TestValidLinearBins)281 TEST_F(HistogramParsingUtilsTest, TestValidLinearBins) {
282     EXPECT_THAT(getParsedLinearBins(-10, 10, 5),
283                 ElementsAre(UNDERFLOW_BIN_START, -10, -6, -2, 2, 6, 10));
284     EXPECT_THAT(getParsedLinearBins(-10, 10, 2), ElementsAre(UNDERFLOW_BIN_START, -10, 0, 10));
285     EXPECT_THAT(getParsedLinearBins(-100, -50, 3),
286                 ElementsAre(UNDERFLOW_BIN_START, -100, FloatNear(-83.33, 0.01),
287                             FloatNear(-66.67, 0.01), -50));
288     EXPECT_THAT(getParsedLinearBins(2.5, 11.3, 7),
289                 ElementsAre(UNDERFLOW_BIN_START, 2.5, FloatNear(3.76, 0.01), FloatNear(5.01, 0.01),
290                             FloatNear(6.27, 0.01), FloatNear(7.53, 0.01), FloatNear(8.79, 0.01),
291                             FloatNear(10.04, 0.01), 11.3));
292 }
293 
getParsedExponentialBins(float min,float max,int count)294 BinStarts getParsedExponentialBins(float min, float max, int count) {
295     return getParsedGeneratedBins(min, max, count, EXPONENTIAL);
296 }
297 
TEST_F(HistogramParsingUtilsTest,TestValidExponentialBins)298 TEST_F(HistogramParsingUtilsTest, TestValidExponentialBins) {
299     EXPECT_THAT(getParsedExponentialBins(5, 160, 5),
300                 ElementsAre(UNDERFLOW_BIN_START, 5, 10, 20, 40, 80, 160));
301     EXPECT_THAT(getParsedExponentialBins(3, 1875, 4),
302                 ElementsAre(UNDERFLOW_BIN_START, 3, FloatEq(15), FloatEq(75), FloatEq(375), 1875));
303     EXPECT_THAT(getParsedExponentialBins(1, 1000, 3),
304                 ElementsAre(UNDERFLOW_BIN_START, 1, 10, 100, 1000));
305 }
306 
getParsedExplicitBins(BinStarts bins)307 BinStarts getParsedExplicitBins(BinStarts bins) {
308     StatsdConfig config = createExplicitHistogramStatsdConfig(bins);
309 
310     return getParsedBins(config.value_metric(0));
311 }
312 
TEST_F(HistogramParsingUtilsTest,TestValidExplicitBins)313 TEST_F(HistogramParsingUtilsTest, TestValidExplicitBins) {
314     EXPECT_THAT(getParsedExplicitBins({0, 1, 2}), ElementsAre(UNDERFLOW_BIN_START, 0, 1, 2));
315     EXPECT_THAT(getParsedExplicitBins({-1, 5, 200}), ElementsAre(UNDERFLOW_BIN_START, -1, 5, 200));
316 }
317 
TEST_F(HistogramParsingUtilsTest,TestMultipleHistogramBinConfigs)318 TEST_F(HistogramParsingUtilsTest, TestMultipleHistogramBinConfigs) {
319     StatsdConfig config = createGeneratedHistogramStatsdConfig(/* min */ -100, /* max */ 0,
320                                                                /* count */ 5, LINEAR);
321     config.mutable_value_metric(0)->clear_aggregation_type();
322     config.mutable_value_metric(0)->add_aggregation_types(ValueMetric::HISTOGRAM);
323     config.mutable_value_metric(0)->add_aggregation_types(ValueMetric::HISTOGRAM);
324     config.mutable_value_metric(0)->mutable_value_field()->add_child()->set_field(2);
325     *config.mutable_value_metric(0)->add_histogram_bin_configs() =
326             createExplicitBinConfig(/* id */ 2, {1, 9, 30});
327 
328     ParseHistogramBinConfigsResult result = parseHistogramBinConfigs(
329             config.value_metric(0),
330             /* aggregationTypes */ {ValueMetric::HISTOGRAM, ValueMetric::HISTOGRAM});
331     ASSERT_TRUE(holds_alternative<vector<optional<const BinStarts>>>(result));
332     const vector<optional<const BinStarts>>& histograms =
333             get<vector<optional<const BinStarts>>>(result);
334     ASSERT_EQ(histograms.size(), 2);
335 
336     EXPECT_THAT(*(histograms[0]), ElementsAre(UNDERFLOW_BIN_START, -100, -80, -60, -40, -20, 0));
337     EXPECT_THAT(*(histograms[1]), ElementsAre(UNDERFLOW_BIN_START, 1, 9, 30));
338 }
339 
340 }  // anonymous namespace
341 
342 }  // namespace statsd
343 }  // namespace os
344 }  // namespace android
345 #else
346 GTEST_LOG_(INFO) << "This test does nothing.\n";
347 #endif
348