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/duration_helper/OringDurationTracker.h"
16 #include "src/condition/ConditionWizard.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 <set>
25 #include <unordered_map>
26 #include <vector>
27 
28 using namespace testing;
29 using android::sp;
30 using std::set;
31 using std::unordered_map;
32 using std::vector;
33 
34 #ifdef __ANDROID__
35 namespace android {
36 namespace os {
37 namespace statsd {
38 
39 const ConfigKey kConfigKey(0, 12345);
40 const int TagId = 1;
41 const int64_t metricId = 123;
42 const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event");
43 
44 const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(TagId, 1, "maps");
45 const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
46 const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
47 const int64_t bucketSizeNs = 30 * NS_PER_SEC;
48 
TEST(OringDurationTrackerTest,TestDurationOverlap)49 TEST(OringDurationTrackerTest, TestDurationOverlap) {
50     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
51 
52     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
53     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
54     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
55 
56     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
57 
58     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
59     int64_t bucketStartTimeNs = 10000000000;
60     int64_t bucketNum = 0;
61     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
62     int64_t durationTimeNs = 2 * 1000;
63 
64     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
65                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
66                                  false, false, {});
67 
68     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
69     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
70     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
71     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
72 
73     tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
74     tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, &buckets);
75     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
76 
77     ASSERT_EQ(1u, buckets[eventKey].size());
78     EXPECT_EQ(durationTimeNs, buckets[eventKey][0].mDuration);
79 }
80 
TEST(OringDurationTrackerTest,TestDurationNested)81 TEST(OringDurationTrackerTest, TestDurationNested) {
82     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
83 
84     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
85     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
86     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
87 
88     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
89 
90     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
91     int64_t bucketStartTimeNs = 10000000000;
92     int64_t bucketNum = 0;
93     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
94 
95     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
96                                  bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
97 
98     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
99     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
100 
101     tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false);
102     tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
103 
104     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
105     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
106     ASSERT_EQ(1u, buckets[eventKey].size());
107     EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration);
108 }
109 
TEST(OringDurationTrackerTest,TestStopAll)110 TEST(OringDurationTrackerTest, TestStopAll) {
111     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
112 
113     const std::vector<HashableDimensionKey> kConditionKey1 =
114         {getMockedDimensionKey(TagId, 1, "maps")};
115     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
116     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
117     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
118 
119     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
120 
121     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
122     int64_t bucketStartTimeNs = 10000000000;
123     int64_t bucketNum = 0;
124     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
125 
126     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
127                                  bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
128 
129     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
130     tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
131 
132     tracker.noteStopAll(eventStartTimeNs + 2003);
133 
134     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
135     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
136     ASSERT_EQ(1u, buckets[eventKey].size());
137     EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration);
138 }
139 
TEST(OringDurationTrackerTest,TestCrossBucketBoundary)140 TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
141     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
142 
143     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
144     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
145     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
146 
147     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
148 
149     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
150     int64_t bucketStartTimeNs = 10000000000;
151     int64_t bucketNum = 0;
152     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
153     int64_t durationTimeNs = 2 * 1000;
154 
155     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
156                                  bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
157 
158     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
159     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
160     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, &buckets);
161     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey());
162     EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
163 
164     ASSERT_EQ(2u, buckets[eventKey].size());
165     EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
166     EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
167 
168     tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 10, false);
169     tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false);
170     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, &buckets);
171     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
172     ASSERT_EQ(2u, buckets[eventKey].size());
173     EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
174     EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
175 }
176 
TEST(OringDurationTrackerTest,TestDurationConditionChange)177 TEST(OringDurationTrackerTest, TestDurationConditionChange) {
178     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
179 
180     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
181     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
182     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
183 
184     ConditionKey key1;
185     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
186 
187     EXPECT_CALL(*wizard, query(_, key1, _))  // #4
188             .WillOnce(Return(ConditionState::kFalse));
189 
190     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
191 
192     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
193     int64_t bucketStartTimeNs = 10000000000;
194     int64_t bucketNum = 0;
195     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
196     int64_t durationTimeNs = 2 * 1000;
197 
198     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
199                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
200                                  true, false, {});
201 
202     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
203 
204     tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 5);
205 
206     tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
207 
208     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
209     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
210     ASSERT_EQ(1u, buckets[eventKey].size());
211     EXPECT_EQ(5LL, buckets[eventKey][0].mDuration);
212 }
213 
TEST(OringDurationTrackerTest,TestDurationConditionChange2)214 TEST(OringDurationTrackerTest, TestDurationConditionChange2) {
215     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
216 
217     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
218     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
219     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
220 
221     ConditionKey key1;
222     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
223 
224     EXPECT_CALL(*wizard, query(_, key1, _))
225             .Times(2)
226             .WillOnce(Return(ConditionState::kFalse))
227             .WillOnce(Return(ConditionState::kTrue));
228 
229     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
230 
231     int64_t bucketStartTimeNs = 10000000000;
232     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
233     int64_t bucketNum = 0;
234     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
235     int64_t durationTimeNs = 2 * 1000;
236 
237     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
238                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
239                                  true, false, {});
240 
241     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
242     // condition to false; record duration 5n
243     tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 5);
244     // condition to true.
245     tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 1000);
246     // 2nd duration: 1000ns
247     tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
248 
249     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
250     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
251     ASSERT_EQ(1u, buckets[eventKey].size());
252     EXPECT_EQ(1005LL, buckets[eventKey][0].mDuration);
253 }
254 
TEST(OringDurationTrackerTest,TestDurationConditionChangeNested)255 TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
256     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
257 
258     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
259     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
260     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
261 
262     ConditionKey key1;
263     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
264 
265     EXPECT_CALL(*wizard, query(_, key1, _))  // #4
266             .WillOnce(Return(ConditionState::kFalse));
267 
268     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
269 
270     int64_t bucketStartTimeNs = 10000000000;
271     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
272     int64_t bucketNum = 0;
273     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
274 
275     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
276                                  bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {});
277 
278     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
279     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
280 
281     tracker.noteStop(kEventKey1, eventStartTimeNs + 3, false);
282 
283     tracker.onSlicedConditionMayChange(true, eventStartTimeNs + 15);
284 
285     tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
286 
287     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, &buckets);
288     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
289     ASSERT_EQ(1u, buckets[eventKey].size());
290     EXPECT_EQ(15LL, buckets[eventKey][0].mDuration);
291 }
292 
TEST(OringDurationTrackerTest,TestPredictAnomalyTimestamp)293 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
294     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
295 
296     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
297     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
298     Alert alert;
299     alert.set_id(101);
300     alert.set_metric_id(1);
301     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
302     alert.set_num_buckets(2);
303     alert.set_refractory_period_secs(1);
304 
305     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
306     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
307 
308     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
309     int64_t bucketNum = 0;
310     int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
311 
312     sp<AlarmMonitor> alarmMonitor;
313     sp<DurationAnomalyTracker> anomalyTracker =
314         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
315     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
316                                  bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
317                                  {anomalyTracker});
318 
319     // Nothing in the past bucket.
320     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
321     EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
322               tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
323 
324     tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 3, false);
325     ASSERT_EQ(0u, buckets[eventKey].size());
326 
327     int64_t event1StartTimeNs = eventStartTimeNs + 10;
328     tracker.noteStart(kEventKey1, true, event1StartTimeNs, ConditionKey());
329     // No past buckets. The anomaly will happen in bucket #0.
330     EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
331               tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
332 
333     int64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10;
334     tracker.flushIfNeeded(event1StopTimeNs, &buckets);
335     tracker.noteStop(kEventKey1, event1StopTimeNs, false);
336 
337     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
338     ASSERT_EQ(1u, buckets[eventKey].size());
339     EXPECT_EQ(3LL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10,
340               buckets[eventKey][0].mDuration);
341 
342     const int64_t bucket0Duration = 3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10;
343     const int64_t bucket1Duration = eventStartTimeNs + 10 - bucketStartTimeNs;
344 
345     // One past buckets. The anomaly will happen in bucket #1.
346     int64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
347     tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey());
348     EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
349                           bucket1Duration),
350               tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
351     tracker.noteStop(kEventKey1, event2StartTimeNs + 1, false);
352 
353     // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
354     // bucket #2.
355     int64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
356     tracker.noteStart(kEventKey1, true, event3StartTimeNs, ConditionKey());
357     EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
358               tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
359 }
360 
TEST(OringDurationTrackerTest,TestPredictAnomalyTimestamp2)361 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) {
362     Alert alert;
363     alert.set_id(101);
364     alert.set_metric_id(1);
365     alert.set_trigger_if_sum_gt(5 * NS_PER_SEC);
366     alert.set_num_buckets(1);
367     alert.set_refractory_period_secs(20);
368 
369     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
370     int64_t bucketNum = 0;
371 
372     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
373     sp<AlarmMonitor> alarmMonitor;
374     sp<DurationAnomalyTracker> anomalyTracker =
375         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
376     OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, 1,
377 
378                                  true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
379                                  bucketSizeNs, true, false, {anomalyTracker});
380 
381     int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
382     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
383     // Anomaly happens in the bucket #1.
384     EXPECT_EQ((long long)(bucketStartTimeNs + 14 * NS_PER_SEC),
385               tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
386 
387     tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 14 * NS_PER_SEC, false);
388 
389     EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC,
390               anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY));
391 
392     int64_t event2StartTimeNs = bucketStartTimeNs + 22 * NS_PER_SEC;
393     EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC,
394               anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY));
395     EXPECT_EQ((long long)(bucketStartTimeNs + 35 * NS_PER_SEC),
396               tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
397 }
398 
TEST(OringDurationTrackerTest,TestPredictAnomalyTimestamp3)399 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) {
400     // Test the cases where the refractory period is smaller than the bucket size, longer than
401     // the bucket size, and longer than 2x of the anomaly detection window.
402     for (int j = 0; j < 3; j++) {
403         int64_t thresholdNs = j * bucketSizeNs + 5 * NS_PER_SEC;
404         for (int i = 0; i <= 7; ++i) {
405 
406             Alert alert;
407             alert.set_id(101);
408             alert.set_metric_id(1);
409             alert.set_trigger_if_sum_gt(thresholdNs);
410             alert.set_num_buckets(3);
411             alert.set_refractory_period_secs(
412                 bucketSizeNs / NS_PER_SEC / 2 + i * bucketSizeNs / NS_PER_SEC);
413 
414             int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
415             int64_t bucketNum = 101;
416 
417             sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
418             sp<AlarmMonitor> alarmMonitor;
419             sp<DurationAnomalyTracker> anomalyTracker =
420                 new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
421             OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard,
422                                          1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
423                                          bucketSizeNs, true, false, {anomalyTracker});
424 
425             int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
426             tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
427             EXPECT_EQ((long long)(eventStartTimeNs + thresholdNs),
428                       tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
429             int64_t eventStopTimeNs = eventStartTimeNs + thresholdNs + NS_PER_SEC;
430             tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStopTimeNs, false);
431 
432             int64_t refractoryPeriodEndSec =
433                 anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY);
434             EXPECT_EQ(eventStopTimeNs / (int64_t)NS_PER_SEC + alert.refractory_period_secs(),
435                        refractoryPeriodEndSec);
436 
437             // Acquire and release a wakelock in the next bucket.
438             int64_t event2StartTimeNs = eventStopTimeNs + bucketSizeNs;
439             tracker.noteStart(DEFAULT_DIMENSION_KEY, true, event2StartTimeNs, ConditionKey());
440             int64_t event2StopTimeNs = event2StartTimeNs + 4 * NS_PER_SEC;
441             tracker.noteStop(DEFAULT_DIMENSION_KEY, event2StopTimeNs, false);
442 
443             // Test the alarm prediction works well when seeing another wakelock start event.
444             for (int k = 0; k <= 2; ++k) {
445                 int64_t event3StartTimeNs = event2StopTimeNs + NS_PER_SEC + k * bucketSizeNs;
446                 int64_t alarmTimestampNs =
447                     tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs);
448                 EXPECT_GT(alarmTimestampNs, 0u);
449                 EXPECT_GE(alarmTimestampNs, event3StartTimeNs);
450                 EXPECT_GE(alarmTimestampNs, refractoryPeriodEndSec *(int64_t) NS_PER_SEC);
451             }
452         }
453     }
454 }
455 
TEST(OringDurationTrackerTest,TestAnomalyDetectionExpiredAlarm)456 TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
457     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
458 
459     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
460     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
461     Alert alert;
462     alert.set_id(101);
463     alert.set_metric_id(1);
464     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
465     alert.set_num_buckets(2);
466     const int32_t refPeriodSec = 45;
467     alert.set_refractory_period_secs(refPeriodSec);
468 
469     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
470     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
471 
472     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
473     int64_t bucketNum = 0;
474     int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
475 
476     sp<AlarmMonitor> alarmMonitor;
477     sp<DurationAnomalyTracker> anomalyTracker =
478         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
479     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
480                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
481                                  false, false, {anomalyTracker});
482 
483     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
484     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
485     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
486     EXPECT_TRUE(tracker.mStarted.empty());
487     EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration);  // 10ns
488 
489     ASSERT_EQ(0u, tracker.mStarted.size());
490 
491     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey());
492     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
493     EXPECT_EQ((long long)(52ULL * NS_PER_SEC),  // (10s + 1s + 1ns + 20ns) - 10ns + 40s, rounded up
494               (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
495     // The alarm is set to fire at 52s, and when it does, an anomaly would be declared. However,
496     // because this is a unit test, the alarm won't actually fire at all. Since the alarm fails
497     // to fire in time, the anomaly is instead caught when noteStop is called, at around 71s.
498     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, &buckets);
499     tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
500     EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs));
501     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey),
502               std::ceil((eventStartTimeNs + 2 * bucketSizeNs + 25.0) / NS_PER_SEC + refPeriodSec));
503 }
504 
TEST(OringDurationTrackerTest,TestAnomalyDetectionFiredAlarm)505 TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
506     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
507 
508     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
509     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
510     Alert alert;
511     alert.set_id(101);
512     alert.set_metric_id(1);
513     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
514     alert.set_num_buckets(2);
515     const int32_t refPeriodSec = 45;
516     alert.set_refractory_period_secs(refPeriodSec);
517 
518     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
519     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
520     ConditionKey conkey;
521     conkey[StringToId("APP_BACKGROUND")] = kConditionKey1;
522     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
523     int64_t bucketSizeNs = 30 * NS_PER_SEC;
524 
525     sp<AlarmMonitor> alarmMonitor;
526     sp<DurationAnomalyTracker> anomalyTracker =
527         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
528     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
529                                  bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false,
530                                  false, {anomalyTracker});
531 
532     tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey);  // start key1
533     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
534     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
535     EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
536     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
537 
538     tracker.noteStop(kEventKey1, 17 * NS_PER_SEC, false); // stop key1 (2 seconds later)
539     ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
540     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
541 
542     tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey);  // start key1 again
543     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
544     alarm = anomalyTracker->mAlarms.begin()->second;
545     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
546     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
547 
548     tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey);  // start key2
549     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
550     alarm = anomalyTracker->mAlarms.begin()->second;
551     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
552     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
553 
554     tracker.noteStop(kEventKey1, 47 * NS_PER_SEC, false); // stop key1
555     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
556     alarm = anomalyTracker->mAlarms.begin()->second;
557     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
558     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
559 
560     // Now, at 60s, which is 38s after key1 started again, we have reached 40s of 'on' time.
561     std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarms({alarm});
562     anomalyTracker->informAlarmsFired(62 * NS_PER_SEC, firedAlarms);
563     ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
564     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
565 
566     tracker.noteStop(kEventKey2, 69 * NS_PER_SEC, false); // stop key2
567     ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
568     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
569 }
570 
571 }  // namespace statsd
572 }  // namespace os
573 }  // namespace android
574 #else
575 GTEST_LOG_(INFO) << "This test does nothing.\n";
576 #endif
577