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 optional<UploadThreshold> emptyThreshold;
43 const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
44 
45 const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(TagId, 1, "maps");
46 const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
47 const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
48 const int64_t bucketSizeNs = 30 * NS_PER_SEC;
49 
TEST(OringDurationTrackerTest,TestDurationOverlap)50 TEST(OringDurationTrackerTest, TestDurationOverlap) {
51     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
52 
53     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
54     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
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, false,
66                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
67                                  false, false, {});
68 
69     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
70                       StatsdStats::kDimensionKeySizeHardLimitMin);
71     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
72     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey(),
73                       StatsdStats::kDimensionKeySizeHardLimitMin);  // overlapping wl
74     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
75 
76     tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
77     tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets);
78     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
79 
80     ASSERT_EQ(1u, buckets[eventKey].size());
81     EXPECT_EQ(durationTimeNs, buckets[eventKey][0].mDuration);
82 }
83 
TEST(OringDurationTrackerTest,TestDurationNested)84 TEST(OringDurationTrackerTest, TestDurationNested) {
85     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
86 
87     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
88     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
89     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
90 
91     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
92 
93     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
94     int64_t bucketStartTimeNs = 10000000000;
95     int64_t bucketNum = 0;
96     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
97 
98     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
99                                  bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
100 
101     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
102                       StatsdStats::kDimensionKeySizeHardLimitMin);
103     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey(),
104                       StatsdStats::kDimensionKeySizeHardLimitMin);  // overlapping wl
105 
106     tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false);
107     tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
108 
109     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets);
110     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
111     ASSERT_EQ(1u, buckets[eventKey].size());
112     EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration);
113 }
114 
TEST(OringDurationTrackerTest,TestStopAll)115 TEST(OringDurationTrackerTest, TestStopAll) {
116     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
117 
118     const std::vector<HashableDimensionKey> kConditionKey1 =
119         {getMockedDimensionKey(TagId, 1, "maps")};
120     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
121     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
122     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
123 
124     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
125 
126     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
127     int64_t bucketStartTimeNs = 10000000000;
128     int64_t bucketNum = 0;
129     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
130 
131     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
132                                  bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
133 
134     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
135                       StatsdStats::kDimensionKeySizeHardLimitMin);
136     tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey(),
137                       StatsdStats::kDimensionKeySizeHardLimitMin);  // overlapping wl
138 
139     tracker.noteStopAll(eventStartTimeNs + 2003);
140 
141     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets);
142     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
143     ASSERT_EQ(1u, buckets[eventKey].size());
144     EXPECT_EQ(2003LL, buckets[eventKey][0].mDuration);
145 }
146 
TEST(OringDurationTrackerTest,TestCrossBucketBoundary)147 TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
148     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
149 
150     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
151     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
152     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
153 
154     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
155 
156     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
157     int64_t bucketStartTimeNs = 10000000000;
158     int64_t bucketNum = 0;
159     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
160     int64_t durationTimeNs = 2 * 1000;
161 
162     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
163                                  bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
164 
165     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
166                       StatsdStats::kDimensionKeySizeHardLimitMin);
167     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
168     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, emptyThreshold, &buckets);
169     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey(),
170                       StatsdStats::kDimensionKeySizeHardLimitMin);
171     EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
172 
173     ASSERT_EQ(2u, buckets[eventKey].size());
174     EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
175     EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
176 
177     tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 10, false);
178     tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 12, false);
179     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 12, emptyThreshold, &buckets);
180     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
181     ASSERT_EQ(2u, buckets[eventKey].size());
182     EXPECT_EQ(bucketSizeNs - 1, buckets[eventKey][0].mDuration);
183     EXPECT_EQ(bucketSizeNs, buckets[eventKey][1].mDuration);
184 }
185 
TEST(OringDurationTrackerTest,TestDurationConditionChange)186 TEST(OringDurationTrackerTest, TestDurationConditionChange) {
187     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
188 
189     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
190     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
191     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
192 
193     ConditionKey key1;
194     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
195 
196     EXPECT_CALL(*wizard, query(_, key1, _))  // #4
197             .WillOnce(Return(ConditionState::kFalse));
198 
199     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
200 
201     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
202     int64_t bucketStartTimeNs = 10000000000;
203     int64_t bucketNum = 0;
204     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
205     int64_t durationTimeNs = 2 * 1000;
206 
207     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
208                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
209                                  true, false, {});
210 
211     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1,
212                       StatsdStats::kDimensionKeySizeHardLimitMin);
213 
214     tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
215 
216     tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
217 
218     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets);
219     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
220     ASSERT_EQ(1u, buckets[eventKey].size());
221     EXPECT_EQ(5LL, buckets[eventKey][0].mDuration);
222 }
223 
TEST(OringDurationTrackerTest,TestDurationConditionChange2)224 TEST(OringDurationTrackerTest, TestDurationConditionChange2) {
225     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
226 
227     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
228     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
229     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
230 
231     ConditionKey key1;
232     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
233 
234     EXPECT_CALL(*wizard, query(_, key1, _))
235             .Times(2)
236             .WillOnce(Return(ConditionState::kFalse))
237             .WillOnce(Return(ConditionState::kTrue));
238 
239     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
240 
241     int64_t bucketStartTimeNs = 10000000000;
242     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
243     int64_t bucketNum = 0;
244     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
245     int64_t durationTimeNs = 2 * 1000;
246 
247     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
248                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
249                                  true, false, {});
250 
251     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1,
252                       StatsdStats::kDimensionKeySizeHardLimitMin);
253     // condition to false; record duration 5n
254     tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
255     // condition to true.
256     tracker.onSlicedConditionMayChange(eventStartTimeNs + 1000);
257     // 2nd duration: 1000ns
258     tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
259 
260     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets);
261     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
262     ASSERT_EQ(1u, buckets[eventKey].size());
263     EXPECT_EQ(1005LL, buckets[eventKey][0].mDuration);
264 }
265 
TEST(OringDurationTrackerTest,TestDurationConditionChangeNested)266 TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
267     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
268 
269     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
270     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
271     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
272 
273     ConditionKey key1;
274     key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
275 
276     EXPECT_CALL(*wizard, query(_, key1, _))  // #4
277             .WillOnce(Return(ConditionState::kFalse));
278 
279     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
280 
281     int64_t bucketStartTimeNs = 10000000000;
282     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
283     int64_t bucketNum = 0;
284     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
285 
286     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
287                                  bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {});
288 
289     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1,
290                       StatsdStats::kDimensionKeySizeHardLimitMin);
291     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1,
292                       StatsdStats::kDimensionKeySizeHardLimitMin);
293 
294     tracker.noteStop(kEventKey1, eventStartTimeNs + 3, false);
295 
296     tracker.onSlicedConditionMayChange(eventStartTimeNs + 15);
297 
298     tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
299 
300     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets);
301     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
302     ASSERT_EQ(1u, buckets[eventKey].size());
303     EXPECT_EQ(15LL, buckets[eventKey][0].mDuration);
304 }
305 
TEST(OringDurationTrackerTest,TestPredictAnomalyTimestamp)306 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
307     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
308 
309     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
310     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
311     Alert alert;
312     alert.set_id(101);
313     alert.set_metric_id(1);
314     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
315     alert.set_num_buckets(2);
316     alert.set_refractory_period_secs(1);
317 
318     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
319     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
320 
321     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
322     int64_t bucketNum = 0;
323     int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
324 
325     sp<AlarmMonitor> alarmMonitor;
326     sp<DurationAnomalyTracker> anomalyTracker =
327         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
328     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
329                                  bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
330                                  {anomalyTracker});
331 
332     // Nothing in the past bucket.
333     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey(),
334                       StatsdStats::kDimensionKeySizeHardLimitMin);
335     EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
336               tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
337 
338     tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStartTimeNs + 3, false);
339     ASSERT_EQ(0u, buckets[eventKey].size());
340 
341     int64_t event1StartTimeNs = eventStartTimeNs + 10;
342     tracker.noteStart(kEventKey1, true, event1StartTimeNs, ConditionKey(),
343                       StatsdStats::kDimensionKeySizeHardLimitMin);
344     // No past buckets. The anomaly will happen in bucket #0.
345     EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
346               tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
347 
348     int64_t event1StopTimeNs = eventStartTimeNs + bucketSizeNs + 10;
349     tracker.flushIfNeeded(event1StopTimeNs, emptyThreshold, &buckets);
350     tracker.noteStop(kEventKey1, event1StopTimeNs, false);
351 
352     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
353     ASSERT_EQ(1u, buckets[eventKey].size());
354     EXPECT_EQ(3LL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10,
355               buckets[eventKey][0].mDuration);
356 
357     const int64_t bucket0Duration = 3ULL + bucketStartTimeNs + bucketSizeNs - eventStartTimeNs - 10;
358     const int64_t bucket1Duration = eventStartTimeNs + 10 - bucketStartTimeNs;
359 
360     // One past buckets. The anomaly will happen in bucket #1.
361     int64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
362     tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey(),
363                       StatsdStats::kDimensionKeySizeHardLimitMin);
364     EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
365                           bucket1Duration),
366               tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
367     tracker.noteStop(kEventKey1, event2StartTimeNs + 1, false);
368 
369     // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
370     // bucket #2.
371     int64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
372     tracker.noteStart(kEventKey1, true, event3StartTimeNs, ConditionKey(),
373                       StatsdStats::kDimensionKeySizeHardLimitMin);
374     EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
375               tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
376 }
377 
TEST(OringDurationTrackerTest,TestPredictAnomalyTimestamp2)378 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp2) {
379     Alert alert;
380     alert.set_id(101);
381     alert.set_metric_id(1);
382     alert.set_trigger_if_sum_gt(5 * NS_PER_SEC);
383     alert.set_num_buckets(1);
384     alert.set_refractory_period_secs(20);
385 
386     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
387     int64_t bucketNum = 0;
388 
389     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
390     sp<AlarmMonitor> alarmMonitor;
391     sp<DurationAnomalyTracker> anomalyTracker =
392         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
393     OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard, 1,
394 
395                                  true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
396                                  bucketSizeNs, true, false, {anomalyTracker});
397 
398     int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
399     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey(),
400                       StatsdStats::kDimensionKeySizeHardLimitMin);
401     // Anomaly happens in the bucket #1.
402     EXPECT_EQ((long long)(bucketStartTimeNs + 14 * NS_PER_SEC),
403               tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
404 
405     tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 14 * NS_PER_SEC, false);
406 
407     EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC,
408               anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY));
409 
410     int64_t event2StartTimeNs = bucketStartTimeNs + 22 * NS_PER_SEC;
411     EXPECT_EQ((long long)(bucketStartTimeNs + 34 * NS_PER_SEC) / NS_PER_SEC,
412               anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY));
413     EXPECT_EQ((long long)(bucketStartTimeNs + 35 * NS_PER_SEC),
414               tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
415 }
416 
TEST(OringDurationTrackerTest,TestPredictAnomalyTimestamp3)417 TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) {
418     // Test the cases where the refractory period is smaller than the bucket size, longer than
419     // the bucket size, and longer than 2x of the anomaly detection window.
420     for (int j = 0; j < 3; j++) {
421         int64_t thresholdNs = j * bucketSizeNs + 5 * NS_PER_SEC;
422         for (int i = 0; i <= 7; ++i) {
423 
424             Alert alert;
425             alert.set_id(101);
426             alert.set_metric_id(1);
427             alert.set_trigger_if_sum_gt(thresholdNs);
428             alert.set_num_buckets(3);
429             alert.set_refractory_period_secs(
430                 bucketSizeNs / NS_PER_SEC / 2 + i * bucketSizeNs / NS_PER_SEC);
431 
432             int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
433             int64_t bucketNum = 101;
434 
435             sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
436             sp<AlarmMonitor> alarmMonitor;
437             sp<DurationAnomalyTracker> anomalyTracker =
438                 new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
439             OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard,
440                                          1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
441                                          bucketSizeNs, true, false, {anomalyTracker});
442 
443             int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
444             tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey(),
445                               StatsdStats::kDimensionKeySizeHardLimitMin);
446             EXPECT_EQ((long long)(eventStartTimeNs + thresholdNs),
447                       tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
448             int64_t eventStopTimeNs = eventStartTimeNs + thresholdNs + NS_PER_SEC;
449             tracker.noteStop(DEFAULT_DIMENSION_KEY, eventStopTimeNs, false);
450 
451             int64_t refractoryPeriodEndSec =
452                 anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY);
453             EXPECT_EQ(eventStopTimeNs / (int64_t)NS_PER_SEC + alert.refractory_period_secs(),
454                        refractoryPeriodEndSec);
455 
456             // Acquire and release a wakelock in the next bucket.
457             int64_t event2StartTimeNs = eventStopTimeNs + bucketSizeNs;
458             tracker.noteStart(DEFAULT_DIMENSION_KEY, true, event2StartTimeNs, ConditionKey(),
459                               StatsdStats::kDimensionKeySizeHardLimitMin);
460             int64_t event2StopTimeNs = event2StartTimeNs + 4 * NS_PER_SEC;
461             tracker.noteStop(DEFAULT_DIMENSION_KEY, event2StopTimeNs, false);
462 
463             // Test the alarm prediction works well when seeing another wakelock start event.
464             for (int k = 0; k <= 2; ++k) {
465                 int64_t event3StartTimeNs = event2StopTimeNs + NS_PER_SEC + k * bucketSizeNs;
466                 int64_t alarmTimestampNs =
467                     tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs);
468                 EXPECT_GT(alarmTimestampNs, 0u);
469                 EXPECT_GE(alarmTimestampNs, event3StartTimeNs);
470                 EXPECT_GE(alarmTimestampNs, refractoryPeriodEndSec *(int64_t) NS_PER_SEC);
471             }
472         }
473     }
474 }
475 
TEST(OringDurationTrackerTest,TestAnomalyDetectionExpiredAlarm)476 TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
477     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
478 
479     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
480     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
481     Alert alert;
482     alert.set_id(101);
483     alert.set_metric_id(1);
484     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
485     alert.set_num_buckets(2);
486     const int32_t refPeriodSec = 45;
487     alert.set_refractory_period_secs(refPeriodSec);
488 
489     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
490     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
491 
492     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
493     int64_t bucketNum = 0;
494     int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
495 
496     sp<AlarmMonitor> alarmMonitor;
497     sp<DurationAnomalyTracker> anomalyTracker =
498         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
499     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
500                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
501                                  false, false, {anomalyTracker});
502 
503     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
504                       StatsdStats::kDimensionKeySizeHardLimitMin);
505     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
506     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
507     EXPECT_TRUE(tracker.mStarted.empty());
508     EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration);  // 10ns
509 
510     ASSERT_EQ(0u, tracker.mStarted.size());
511 
512     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey(),
513                       StatsdStats::kDimensionKeySizeHardLimitMin);
514     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
515     EXPECT_EQ((long long)(52ULL * NS_PER_SEC),  // (10s + 1s + 1ns + 20ns) - 10ns + 40s, rounded up
516               (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
517     // The alarm is set to fire at 52s, and when it does, an anomaly would be declared. However,
518     // because this is a unit test, the alarm won't actually fire at all. Since the alarm fails
519     // to fire in time, the anomaly is instead caught when noteStop is called, at around 71s.
520     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs + 25, emptyThreshold, &buckets);
521     tracker.noteStop(kEventKey1, eventStartTimeNs + 2 * bucketSizeNs + 25, false);
522     EXPECT_EQ(anomalyTracker->getSumOverPastBuckets(eventKey), (long long)(bucketSizeNs));
523     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey),
524               std::ceil((eventStartTimeNs + 2 * bucketSizeNs + 25.0) / NS_PER_SEC + refPeriodSec));
525 }
526 
TEST(OringDurationTrackerTest,TestAnomalyDetectionFiredAlarm)527 TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
528     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
529 
530     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
531     const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
532     Alert alert;
533     alert.set_id(101);
534     alert.set_metric_id(1);
535     alert.set_trigger_if_sum_gt(40 * NS_PER_SEC);
536     alert.set_num_buckets(2);
537     const int32_t refPeriodSec = 45;
538     alert.set_refractory_period_secs(refPeriodSec);
539 
540     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
541     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
542     ConditionKey conkey;
543     conkey[StringToId("APP_BACKGROUND")] = kConditionKey1;
544     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
545     int64_t bucketSizeNs = 30 * NS_PER_SEC;
546 
547     sp<AlarmMonitor> alarmMonitor;
548     sp<DurationAnomalyTracker> anomalyTracker =
549         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
550     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
551                                  bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false,
552                                  false, {anomalyTracker});
553 
554     tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey,
555                       StatsdStats::kDimensionKeySizeHardLimitMin);  // start key1
556     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
557     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
558     EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
559     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
560 
561     tracker.noteStop(kEventKey1, 17 * NS_PER_SEC, false); // stop key1 (2 seconds later)
562     ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
563     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
564 
565     tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey,
566                       StatsdStats::kDimensionKeySizeHardLimitMin);  // start key1 again
567     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
568     alarm = anomalyTracker->mAlarms.begin()->second;
569     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
570     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
571 
572     tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey,
573                       StatsdStats::kDimensionKeySizeHardLimitMin);  // start key2
574     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
575     alarm = anomalyTracker->mAlarms.begin()->second;
576     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
577     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
578 
579     tracker.noteStop(kEventKey1, 47 * NS_PER_SEC, false); // stop key1
580     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
581     alarm = anomalyTracker->mAlarms.begin()->second;
582     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
583     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
584 
585     // Now, at 60s, which is 38s after key1 started again, we have reached 40s of 'on' time.
586     std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarms({alarm});
587     anomalyTracker->informAlarmsFired(62 * NS_PER_SEC, firedAlarms);
588     ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
589     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
590 
591     tracker.noteStop(kEventKey2, 69 * NS_PER_SEC, false); // stop key2
592     ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
593     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 62U + refPeriodSec);
594 }
595 
TEST(OringDurationTrackerTest,TestUploadThreshold)596 TEST(OringDurationTrackerTest, TestUploadThreshold) {
597     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
598 
599     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
600 
601     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
602     int64_t bucketStartTimeNs = 10000000000;
603     int64_t bucketNum = 0;
604     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
605     int64_t event2StartTimeNs = bucketStartTimeNs + bucketSizeNs + 1;
606     int64_t thresholdDurationNs = 2000;
607 
608     UploadThreshold threshold;
609     threshold.set_gt_int(thresholdDurationNs);
610 
611     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
612                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
613                                  false, false, {});
614 
615     // Duration below the gt_int threshold should not be added to past buckets.
616     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
617                       StatsdStats::kDimensionKeySizeHardLimitMin);
618     tracker.noteStop(kEventKey1, eventStartTimeNs + thresholdDurationNs, false);
619     tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, threshold, &buckets);
620     EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
621 
622     // Duration above the gt_int threshold should be added to past buckets.
623     tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey(),
624                       StatsdStats::kDimensionKeySizeHardLimitMin);
625     tracker.noteStop(kEventKey1, event2StartTimeNs + thresholdDurationNs + 1, false);
626     tracker.flushIfNeeded(event2StartTimeNs + bucketSizeNs + 1, threshold, &buckets);
627     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
628     ASSERT_EQ(1u, buckets[eventKey].size());
629     EXPECT_EQ(thresholdDurationNs + 1, buckets[eventKey][0].mDuration);
630 }
631 
TEST(OringDurationTrackerTest,TestClearStateKeyMapWhenBucketFull)632 TEST(OringDurationTrackerTest, TestClearStateKeyMapWhenBucketFull) {
633     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
634 
635     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
636 
637     Alert alert = createAlert("Alert_1", /*metric_id=*/1, /*num_buckets*/ 2,
638                               /*trigger_sum=*/40 * NS_PER_SEC);
639 
640     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
641     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
642 
643     int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
644     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
645     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
646     int64_t bucketNum = 0;
647     int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
648 
649     sp<AlarmMonitor> alarmMonitor;
650     sp<DurationAnomalyTracker> anomalyTracker =
651             new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
652     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
653                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
654                                  false, false, {anomalyTracker});
655 
656     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
657                       StatsdStats::kDimensionKeySizeHardLimitMin);
658     tracker.noteStop(kEventKey1, bucketStartTimeNs + 50, false);
659     EXPECT_TRUE(tracker.hasAccumulatedDuration());
660 
661     tracker.noteStart(kEventKey1, true, bucketStartTimeNs + 100, ConditionKey(),
662                       StatsdStats::kDimensionKeySizeHardLimitMin);
663     EXPECT_FALSE(tracker.mStarted.empty());
664     tracker.onConditionChanged(false, bucketStartTimeNs + 150);
665     EXPECT_TRUE(tracker.mStarted.empty());
666     EXPECT_FALSE(tracker.mPaused.empty());
667     EXPECT_FALSE(tracker.mStateKeyDurationMap.empty());
668     EXPECT_TRUE(tracker.hasAccumulatedDuration());
669 
670     // Since this is a full bucket, flush.
671     tracker.flushIfNeeded(bucketEndTimeNs + 1, emptyThreshold, &buckets);
672     EXPECT_TRUE(tracker.mStateKeyDurationMap.empty());
673     EXPECT_TRUE(tracker.hasAccumulatedDuration());
674 
675     tracker.noteStop(kEventKey1, bucketStartTimeNs + bucketSizeNs + 200, false);
676     EXPECT_FALSE(tracker.hasAccumulatedDuration());
677 }
678 
TEST(OringDurationTrackerTest,TestClearStateKeyMapWhenNoTrackers)679 TEST(OringDurationTrackerTest, TestClearStateKeyMapWhenNoTrackers) {
680     const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
681 
682     const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
683     Alert alert = createAlert("Alert_1", /*metric_id=*/1, /*num_buckets*/ 2,
684                               /*trigger_sum=*/40 * NS_PER_SEC);
685 
686     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
687     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
688 
689     int64_t bucketStartTimeNs = 10 * NS_PER_SEC;
690     int64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
691     int64_t bucketNum = 0;
692     int64_t eventStartTimeNs = bucketStartTimeNs + NS_PER_SEC + 1;
693 
694     sp<AlarmMonitor> alarmMonitor;
695     // Duration tracker does not have an anomalyTracker
696     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
697                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
698                                  false, false, {});
699 
700     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
701                       StatsdStats::kDimensionKeySizeHardLimitMin);
702     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
703     tracker.flushCurrentBucket(eventStartTimeNs + 20, emptyThreshold, 0, &buckets);
704 
705     EXPECT_TRUE(tracker.mStarted.empty());
706     // During flush, we will clear the map since there are no anomaly trackers.
707     EXPECT_TRUE(tracker.mStateKeyDurationMap.empty());
708     EXPECT_FALSE(tracker.hasAccumulatedDuration());
709 }
710 
711 class OringDurationTrackerTest_DimLimit : public Test {
712 protected:
~OringDurationTrackerTest_DimLimit()713     ~OringDurationTrackerTest_DimLimit() {
714         StatsdStats::getInstance().reset();
715     }
716 };
717 
TEST_F(OringDurationTrackerTest_DimLimit,TestDimLimit)718 TEST_F(OringDurationTrackerTest_DimLimit, TestDimLimit) {
719     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
720     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1 /* conditionIndex */,
721                                  false /* nesting */, 0 /* currentBucketStartNs */,
722                                  0 /* currentBucketNum */, 0 /* startTimeNs */, bucketSizeNs,
723                                  true /* conditionSliced */, false /* fullLink */,
724                                  {} /* anomalyTrackers */);
725 
726     const size_t dimensionHardLimit = 900;
727     for (int i = 1; i <= dimensionHardLimit; i++) {
728         const HashableDimensionKey key = getMockedDimensionKey(TagId, i, "maps");
729         tracker.noteStart(key, false /* condition */, i /* eventTime */, ConditionKey(),
730                           dimensionHardLimit);
731     }
732     ASSERT_FALSE(tracker.mHasHitGuardrail);
733     const HashableDimensionKey key = getMockedDimensionKey(TagId, dimensionHardLimit + 1, "maps");
734     tracker.noteStart(key, false /* condition */, dimensionHardLimit + 1 /* eventTime */,
735                       ConditionKey(), dimensionHardLimit);
736     EXPECT_TRUE(tracker.mHasHitGuardrail);
737 }
738 
739 }  // namespace statsd
740 }  // namespace os
741 }  // namespace android
742 #else
743 GTEST_LOG_(INFO) << "This test does nothing.\n";
744 #endif
745