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