1 // Copyright (C) 2017 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "src/metrics/CountMetricProducer.h"
16 #include "src/stats_log_util.h"
17 #include "metrics_test_helper.h"
18 #include "tests/statsd_test_util.h"
19
20 #include <gmock/gmock.h>
21 #include <gtest/gtest.h>
22 #include <math.h>
23 #include <stdio.h>
24 #include <vector>
25
26 using namespace testing;
27 using android::sp;
28 using std::set;
29 using std::unordered_map;
30 using std::vector;
31
32 #ifdef __ANDROID__
33
34 namespace android {
35 namespace os {
36 namespace statsd {
37
38 const ConfigKey kConfigKey(0, 12345);
39
TEST(CountMetricProducerTest,TestNonDimensionalEvents)40 TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
41 int64_t bucketStartTimeNs = 10000000000;
42 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
43 int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
44 int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
45 int tagId = 1;
46
47 CountMetric metric;
48 metric.set_id(1);
49 metric.set_bucket(ONE_MINUTE);
50
51 LogEvent event1(tagId, bucketStartTimeNs + 1);
52 event1.init();
53 LogEvent event2(tagId, bucketStartTimeNs + 2);
54 event2.init();
55
56 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
57
58 CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
59 bucketStartTimeNs);
60 countProducer.setBucketSize(60 * NS_PER_SEC);
61
62 // 2 events in bucket 1.
63 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
64 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
65
66 // Flushes at event #2.
67 countProducer.flushIfNeededLocked(bucketStartTimeNs + 2);
68 EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
69
70 // Flushes.
71 countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
72 EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
73 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
74 countProducer.mPastBuckets.end());
75 const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
76 EXPECT_EQ(1UL, buckets.size());
77 EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
78 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
79 EXPECT_EQ(2LL, buckets[0].mCount);
80
81 // 1 matched event happens in bucket 2.
82 LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2);
83 event3.init();
84
85 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
86 countProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
87 EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
88 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
89 countProducer.mPastBuckets.end());
90 EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
91 const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1];
92 EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs);
93 EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs);
94 EXPECT_EQ(1LL, bucketInfo2.mCount);
95
96 // nothing happens in bucket 3. we should not record anything for bucket 3.
97 countProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1);
98 EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
99 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
100 countProducer.mPastBuckets.end());
101 const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
102 EXPECT_EQ(2UL, buckets3.size());
103 }
104
TEST(CountMetricProducerTest,TestEventsWithNonSlicedCondition)105 TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) {
106 int64_t bucketStartTimeNs = 10000000000;
107 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
108
109 CountMetric metric;
110 metric.set_id(1);
111 metric.set_bucket(ONE_MINUTE);
112 metric.set_condition(StringToId("SCREEN_ON"));
113
114 LogEvent event1(1, bucketStartTimeNs + 1);
115 event1.init();
116
117 LogEvent event2(1, bucketStartTimeNs + 10);
118 event2.init();
119
120 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
121
122 CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs);
123 countProducer.setBucketSize(60 * NS_PER_SEC);
124
125 countProducer.onConditionChanged(true, bucketStartTimeNs);
126 countProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
127 EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
128
129 countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2);
130 // Upon this match event, the matched event1 is flushed.
131 countProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
132 EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
133
134 countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
135 EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
136 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
137 countProducer.mPastBuckets.end());
138 {
139 const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
140 EXPECT_EQ(1UL, buckets.size());
141 const auto& bucketInfo = buckets[0];
142 EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
143 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
144 EXPECT_EQ(1LL, bucketInfo.mCount);
145 }
146 }
147
TEST(CountMetricProducerTest,TestEventsWithSlicedCondition)148 TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
149 int64_t bucketStartTimeNs = 10000000000;
150 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
151
152 int tagId = 1;
153 int conditionTagId = 2;
154
155 CountMetric metric;
156 metric.set_id(1);
157 metric.set_bucket(ONE_MINUTE);
158 metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON"));
159 MetricConditionLink* link = metric.add_links();
160 link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID"));
161 buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what());
162 buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition());
163
164 LogEvent event1(tagId, bucketStartTimeNs + 1);
165 event1.write("111"); // uid
166 event1.init();
167 ConditionKey key1;
168 key1[StringToId("APP_IN_BACKGROUND_PER_UID")] =
169 {getMockedDimensionKey(conditionTagId, 2, "111")};
170
171 LogEvent event2(tagId, bucketStartTimeNs + 10);
172 event2.write("222"); // uid
173 event2.init();
174 ConditionKey key2;
175 key2[StringToId("APP_IN_BACKGROUND_PER_UID")] =
176 {getMockedDimensionKey(conditionTagId, 2, "222")};
177
178 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
179 EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse));
180
181 EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue));
182
183 CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard,
184 bucketStartTimeNs);
185 countProducer.setBucketSize(60 * NS_PER_SEC);
186
187 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
188 countProducer.flushIfNeededLocked(bucketStartTimeNs + 1);
189 EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
190
191 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
192 countProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
193 EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
194 EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
195 countProducer.mPastBuckets.end());
196 const auto& buckets = countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
197 EXPECT_EQ(1UL, buckets.size());
198 const auto& bucketInfo = buckets[0];
199 EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
200 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
201 EXPECT_EQ(1LL, bucketInfo.mCount);
202 }
203
TEST(CountMetricProducerTest,TestEventWithAppUpgrade)204 TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
205 sp<AlarmMonitor> alarmMonitor;
206 int64_t bucketStartTimeNs = 10000000000;
207 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
208 int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
209
210 int tagId = 1;
211 int conditionTagId = 2;
212
213 CountMetric metric;
214 metric.set_id(1);
215 metric.set_bucket(ONE_MINUTE);
216 Alert alert;
217 alert.set_num_buckets(3);
218 alert.set_trigger_if_sum_gt(2);
219 LogEvent event1(tagId, bucketStartTimeNs + 1);
220 event1.write("111"); // uid
221 event1.init();
222 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
223 CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard,
224 bucketStartTimeNs);
225 countProducer.setBucketSize(60 * NS_PER_SEC);
226
227 sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor);
228 EXPECT_TRUE(anomalyTracker != nullptr);
229
230 // Bucket is flushed yet.
231 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
232 EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
233 EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
234
235 // App upgrade forces bucket flush.
236 // Check that there's a past bucket and the bucket end is not adjusted.
237 countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
238 EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
239 EXPECT_EQ((long long)bucketStartTimeNs,
240 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
241 EXPECT_EQ((long long)eventUpgradeTimeNs,
242 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
243 EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
244 // Anomaly tracker only contains full buckets.
245 EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
246
247 int64_t lastEndTimeNs = countProducer.getCurrentBucketEndTimeNs();
248 // Next event occurs in same bucket as partial bucket created.
249 LogEvent event2(tagId, bucketStartTimeNs + 59 * NS_PER_SEC + 10);
250 event2.write("222"); // uid
251 event2.init();
252 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
253 EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
254 EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
255 EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
256
257 // Third event in following bucket.
258 LogEvent event3(tagId, bucketStartTimeNs + 62 * NS_PER_SEC + 10);
259 event3.write("333"); // uid
260 event3.init();
261 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
262 EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
263 EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs);
264 EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
265 }
266
TEST(CountMetricProducerTest,TestEventWithAppUpgradeInNextBucket)267 TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) {
268 int64_t bucketStartTimeNs = 10000000000;
269 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
270 int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
271
272 int tagId = 1;
273 int conditionTagId = 2;
274
275 CountMetric metric;
276 metric.set_id(1);
277 metric.set_bucket(ONE_MINUTE);
278 LogEvent event1(tagId, bucketStartTimeNs + 1);
279 event1.write("111"); // uid
280 event1.init();
281 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
282 CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard,
283 bucketStartTimeNs);
284 countProducer.setBucketSize(60 * NS_PER_SEC);
285
286 // Bucket is flushed yet.
287 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
288 EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
289
290 // App upgrade forces bucket flush.
291 // Check that there's a past bucket and the bucket end is not adjusted.
292 countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
293 EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
294 EXPECT_EQ((int64_t)bucketStartTimeNs,
295 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
296 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
297 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
298 EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
299
300 // Next event occurs in same bucket as partial bucket created.
301 LogEvent event2(tagId, bucketStartTimeNs + 70 * NS_PER_SEC + 10);
302 event2.write("222"); // uid
303 event2.init();
304 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
305 EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
306
307 // Third event in following bucket.
308 LogEvent event3(tagId, bucketStartTimeNs + 121 * NS_PER_SEC + 10);
309 event3.write("333"); // uid
310 event3.init();
311 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
312 EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
313 EXPECT_EQ((int64_t)eventUpgradeTimeNs,
314 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs);
315 EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
316 countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs);
317 }
318
TEST(CountMetricProducerTest,TestAnomalyDetectionUnSliced)319 TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) {
320 sp<AlarmMonitor> alarmMonitor;
321 Alert alert;
322 alert.set_id(11);
323 alert.set_metric_id(1);
324 alert.set_trigger_if_sum_gt(2);
325 alert.set_num_buckets(2);
326 const int32_t refPeriodSec = 1;
327 alert.set_refractory_period_secs(refPeriodSec);
328
329 int64_t bucketStartTimeNs = 10000000000;
330 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
331 int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
332 int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
333
334 CountMetric metric;
335 metric.set_id(1);
336 metric.set_bucket(ONE_MINUTE);
337
338 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
339 CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
340 bucketStartTimeNs);
341 countProducer.setBucketSize(60 * NS_PER_SEC);
342
343 sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor);
344
345 int tagId = 1;
346 LogEvent event1(tagId, bucketStartTimeNs + 1);
347 event1.init();
348 LogEvent event2(tagId, bucketStartTimeNs + 2);
349 event2.init();
350 LogEvent event3(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1);
351 event3.init();
352 LogEvent event4(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1);
353 event4.init();
354 LogEvent event5(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2);
355 event5.init();
356 LogEvent event6(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 3);
357 event6.init();
358 LogEvent event7(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC);
359 event7.init();
360
361 // Two events in bucket #0.
362 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
363 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
364
365 EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
366 EXPECT_EQ(2L, countProducer.mCurrentSlicedCounter->begin()->second);
367 EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
368
369 // One event in bucket #2. No alarm as bucket #0 is trashed out.
370 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
371 EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
372 EXPECT_EQ(1L, countProducer.mCurrentSlicedCounter->begin()->second);
373 EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U);
374
375 // Two events in bucket #3.
376 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
377 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event5);
378 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event6);
379 EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
380 EXPECT_EQ(3L, countProducer.mCurrentSlicedCounter->begin()->second);
381 // Anomaly at event 6 is within refractory period. The alarm is at event 5 timestamp not event 6
382 EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
383 std::ceil(1.0 * event5.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
384
385 countProducer.onMatchedLogEvent(1 /*log matcher index*/, event7);
386 EXPECT_EQ(1UL, countProducer.mCurrentSlicedCounter->size());
387 EXPECT_EQ(4L, countProducer.mCurrentSlicedCounter->begin()->second);
388 EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY),
389 std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
390 }
391
392 } // namespace statsd
393 } // namespace os
394 } // namespace android
395 #else
396 GTEST_LOG_(INFO) << "This test does nothing.\n";
397 #endif
398