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