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