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