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/DurationMetricProducer.h"
16
17 #include <gmock/gmock.h>
18 #include <gtest/gtest.h>
19 #include <stdio.h>
20
21 #include <set>
22 #include <unordered_map>
23 #include <vector>
24
25 #include "metrics_test_helper.h"
26 #include "src/condition/ConditionWizard.h"
27 #include "src/stats_log_util.h"
28 #include "stats_event.h"
29 #include "tests/statsd_test_util.h"
30
31 using namespace android::os::statsd;
32 using namespace testing;
33 using android::sp;
34 using std::set;
35 using std::unordered_map;
36 using std::vector;
37
38 #ifdef __ANDROID__
39
40 namespace android {
41 namespace os {
42 namespace statsd {
43
44
45 namespace {
46
47 const ConfigKey kConfigKey(0, 12345);
48 const uint64_t protoHash = 0x1234567890;
makeLogEvent(LogEvent * logEvent,int64_t timestampNs,int atomId)49 void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) {
50 AStatsEvent* statsEvent = AStatsEvent_obtain();
51 AStatsEvent_setAtomId(statsEvent, atomId);
52 AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
53
54 parseStatsEventToLogEvent(statsEvent, logEvent);
55 }
56
onDumpReport(DurationMetricProducer & producer,int64_t dumpTimeNs)57 StatsLogReport onDumpReport(DurationMetricProducer& producer, int64_t dumpTimeNs) {
58 ProtoOutputStream output;
59 set<int32_t> usedUids;
60 producer.onDumpReport(dumpTimeNs, true /*include current partial bucket*/, true /*erase data*/,
61 FAST, nullptr, usedUids, &output);
62 return outputStreamToProto(&output);
63 }
64
65 } // namespace
66
67 // Setup for parameterized tests.
68 class DurationMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
69
70 INSTANTIATE_TEST_SUITE_P(DurationMetricProducerTest_PartialBucket,
71 DurationMetricProducerTest_PartialBucket,
72 testing::Values(APP_UPGRADE, BOOT_COMPLETE));
73
TEST(DurationMetricTrackerTest,TestFirstBucket)74 TEST(DurationMetricTrackerTest, TestFirstBucket) {
75 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
76 DurationMetric metric;
77 metric.set_id(1);
78 metric.set_bucket(ONE_MINUTE);
79 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
80
81 FieldMatcher dimensions;
82 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
83
84 DurationMetricProducer durationProducer(
85 kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/,
86 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
87 wizard, protoHash, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, provider);
88
89 EXPECT_EQ(600500000000, durationProducer.mCurrentBucketStartTimeNs);
90 EXPECT_EQ(10, durationProducer.mCurrentBucketNum);
91 EXPECT_EQ(660000000005, durationProducer.getCurrentBucketEndTimeNs());
92 }
93
TEST(DurationMetricTrackerTest,TestNoCondition)94 TEST(DurationMetricTrackerTest, TestNoCondition) {
95 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
96 int64_t bucketStartTimeNs = 10000000000;
97 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
98
99 DurationMetric metric;
100 metric.set_id(1);
101 metric.set_bucket(ONE_MINUTE);
102 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
103
104 int tagId = 1;
105 LogEvent event1(/*uid=*/0, /*pid=*/0);
106 makeLogEvent(&event1, bucketStartTimeNs + 1, tagId);
107 LogEvent event2(/*uid=*/0, /*pid=*/0);
108 makeLogEvent(&event2, bucketStartTimeNs + bucketSizeNs + 2, tagId);
109
110 FieldMatcher dimensions;
111 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
112
113 DurationMetricProducer durationProducer(
114 kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/,
115 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
116 wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
117
118 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
119 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
120 durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
121 ASSERT_EQ(1UL, durationProducer.mPastBuckets.size());
122 EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
123 durationProducer.mPastBuckets.end());
124 const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
125 ASSERT_EQ(2UL, buckets.size());
126 EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
127 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
128 EXPECT_EQ(bucketSizeNs - 1LL, buckets[0].mDuration);
129 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs);
130 EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[1].mBucketEndNs);
131 EXPECT_EQ(2LL, buckets[1].mDuration);
132 }
133
TEST(DurationMetricTrackerTest,TestNonSlicedCondition)134 TEST(DurationMetricTrackerTest, TestNonSlicedCondition) {
135 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
136 int64_t bucketStartTimeNs = 10000000000;
137 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
138
139 DurationMetric metric;
140 metric.set_id(1);
141 metric.set_bucket(ONE_MINUTE);
142 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
143
144 int tagId = 1;
145 LogEvent event1(/*uid=*/0, /*pid=*/0);
146 makeLogEvent(&event1, bucketStartTimeNs + 1, tagId);
147 LogEvent event2(/*uid=*/0, /*pid=*/0);
148 makeLogEvent(&event2, bucketStartTimeNs + 2, tagId);
149 LogEvent event3(/*uid=*/0, /*pid=*/0);
150 makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId);
151 LogEvent event4(/*uid=*/0, /*pid=*/0);
152 makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId);
153
154 FieldMatcher dimensions;
155 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
156
157 DurationMetricProducer durationProducer(
158 kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
159 -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
160 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
161 bucketStartTimeNs, bucketStartTimeNs, provider);
162 durationProducer.mCondition = ConditionState::kFalse;
163
164 assertConditionTimer(durationProducer.mConditionTimer, false, 0, 0);
165 EXPECT_FALSE(durationProducer.mCondition);
166 EXPECT_FALSE(durationProducer.isConditionSliced());
167
168 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
169 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
170 durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
171 ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
172
173 int64_t conditionStartTimeNs = bucketStartTimeNs + bucketSizeNs + 2;
174 int64_t bucket2EndTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
175 durationProducer.onMatchedLogEvent(1 /* start index*/, event3);
176 durationProducer.onConditionChanged(true /* condition */, conditionStartTimeNs);
177 assertConditionTimer(durationProducer.mConditionTimer, true, 0, conditionStartTimeNs);
178 durationProducer.onMatchedLogEvent(2 /* stop index*/, event4);
179 durationProducer.flushIfNeededLocked(bucket2EndTimeNs + 1);
180 assertConditionTimer(durationProducer.mConditionTimer, true, 0, bucket2EndTimeNs,
181 /*currentBucketStartDelayNs=*/1);
182 ASSERT_EQ(1UL, durationProducer.mPastBuckets.size());
183 EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) !=
184 durationProducer.mPastBuckets.end());
185 const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
186 ASSERT_EQ(1UL, buckets2.size());
187 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs);
188 EXPECT_EQ(bucket2EndTimeNs, buckets2[0].mBucketEndNs);
189 EXPECT_EQ(1LL, buckets2[0].mDuration);
190 EXPECT_EQ(bucket2EndTimeNs - conditionStartTimeNs, buckets2[0].mConditionTrueNs);
191 }
192
TEST(DurationMetricTrackerTest,TestNonSlicedConditionUnknownState)193 TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState) {
194 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
195 int64_t bucketStartTimeNs = 10000000000;
196 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
197
198 DurationMetric metric;
199 metric.set_id(1);
200 metric.set_bucket(ONE_MINUTE);
201 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
202
203 int tagId = 1;
204 LogEvent event1(/*uid=*/0, /*pid=*/0);
205 makeLogEvent(&event1, bucketStartTimeNs + 1, tagId);
206 LogEvent event2(/*uid=*/0, /*pid=*/0);
207 makeLogEvent(&event2, bucketStartTimeNs + 2, tagId);
208 LogEvent event3(/*uid=*/0, /*pid=*/0);
209 makeLogEvent(&event3, bucketStartTimeNs + bucketSizeNs + 1, tagId);
210 LogEvent event4(/*uid=*/0, /*pid=*/0);
211 makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId);
212
213 FieldMatcher dimensions;
214 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
215
216 DurationMetricProducer durationProducer(
217 kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
218 -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
219 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
220 bucketStartTimeNs, bucketStartTimeNs, provider);
221
222 EXPECT_EQ(ConditionState::kUnknown, durationProducer.mCondition);
223 EXPECT_FALSE(durationProducer.isConditionSliced());
224
225 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
226 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
227 durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
228 ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
229
230 durationProducer.onMatchedLogEvent(1 /* start index*/, event3);
231 durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2);
232 durationProducer.onMatchedLogEvent(2 /* stop index*/, event4);
233 durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
234 ASSERT_EQ(1UL, durationProducer.mPastBuckets.size());
235 const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
236 ASSERT_EQ(1UL, buckets2.size());
237 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs);
238 EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs);
239 EXPECT_EQ(1LL, buckets2[0].mDuration);
240 }
241
TEST_P(DurationMetricProducerTest_PartialBucket,TestSumDuration)242 TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDuration) {
243 /**
244 * The duration starts from the first bucket, through the two partial buckets (10-70sec),
245 * another bucket, and ends at the beginning of the next full bucket.
246 * Expected buckets:
247 * - [10,25]: 14 secs
248 * - [25,70]: All 45 secs
249 * - [70,130]: All 60 secs
250 * - [130, 210]: Only 5 secs (event ended at 135sec)
251 */
252 int64_t bucketStartTimeNs = 10000000000;
253 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
254 int tagId = 1;
255
256 DurationMetric metric;
257 metric.set_id(1);
258 metric.set_bucket(ONE_MINUTE);
259 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
260 metric.set_split_bucket_for_app_upgrade(true);
261 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
262 FieldMatcher dimensions;
263 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
264
265 DurationMetricProducer durationProducer(
266 kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
267 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
268 wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
269
270 int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
271 LogEvent event1(/*uid=*/0, /*pid=*/0);
272 makeLogEvent(&event1, startTimeNs, tagId);
273 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
274 ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
275 EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
276
277 int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
278 switch (GetParam()) {
279 case APP_UPGRADE:
280 durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
281 break;
282 case BOOT_COMPLETE:
283 durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
284 break;
285 }
286 ASSERT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
287 std::vector<DurationBucket> buckets =
288 durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
289 EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
290 EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketEndNs);
291 EXPECT_EQ(partialBucketSplitTimeNs - startTimeNs, buckets[0].mDuration);
292 EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
293 EXPECT_EQ(0, durationProducer.getCurrentBucketNum());
294
295 // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
296 int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
297 LogEvent event2(/*uid=*/0, /*pid=*/0);
298 makeLogEvent(&event2, endTimeNs, tagId);
299 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
300 buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
301 ASSERT_EQ(3UL, buckets.size());
302 EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketStartNs);
303 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs);
304 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - partialBucketSplitTimeNs, buckets[1].mDuration);
305 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs);
306 EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
307 EXPECT_EQ(bucketSizeNs, buckets[2].mDuration);
308 }
309
TEST_P(DurationMetricProducerTest_PartialBucket,TestSumDurationWithSplitInFollowingBucket)310 TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationWithSplitInFollowingBucket) {
311 /**
312 * Expected buckets (start at 11s, upgrade at 75s, end at 135s):
313 * - [10,70]: 59 secs
314 * - [70,75]: 5 sec
315 * - [75,130]: 55 secs
316 */
317 int64_t bucketStartTimeNs = 10000000000;
318 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
319 int tagId = 1;
320
321 DurationMetric metric;
322 metric.set_id(1);
323 metric.set_bucket(ONE_MINUTE);
324 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
325 metric.set_split_bucket_for_app_upgrade(true);
326 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
327 FieldMatcher dimensions;
328 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
329
330 DurationMetricProducer durationProducer(
331 kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
332 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
333 wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
334
335 int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
336 LogEvent event1(/*uid=*/0, /*pid=*/0);
337 makeLogEvent(&event1, startTimeNs, tagId);
338 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
339 ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
340 EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
341
342 int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
343 switch (GetParam()) {
344 case APP_UPGRADE:
345 durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
346 break;
347 case BOOT_COMPLETE:
348 durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
349 break;
350 }
351 ASSERT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
352 std::vector<DurationBucket> buckets =
353 durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
354 EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
355 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
356 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration);
357 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs);
358 EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketEndNs);
359 EXPECT_EQ(partialBucketSplitTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration);
360 EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
361 EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
362
363 // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
364 int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
365 LogEvent event2(/*uid=*/0, /*pid=*/0);
366 makeLogEvent(&event2, endTimeNs, tagId);
367 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
368 buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
369 ASSERT_EQ(3UL, buckets.size());
370 EXPECT_EQ(partialBucketSplitTimeNs, buckets[2].mBucketStartNs);
371 EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
372 EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - partialBucketSplitTimeNs,
373 buckets[2].mDuration);
374 }
375
TEST_P(DurationMetricProducerTest_PartialBucket,TestSumDurationAnomaly)376 TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) {
377 sp<AlarmMonitor> alarmMonitor;
378 int64_t bucketStartTimeNs = 10000000000;
379 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
380 int tagId = 1;
381
382 // Setup metric with alert.
383 DurationMetric metric;
384 metric.set_id(1);
385 metric.set_bucket(ONE_MINUTE);
386 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
387 metric.set_split_bucket_for_app_upgrade(true);
388 Alert alert;
389 alert.set_num_buckets(3);
390 alert.set_trigger_if_sum_gt(2);
391
392 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
393 FieldMatcher dimensions;
394 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
395
396 DurationMetricProducer durationProducer(
397 kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
398 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
399 wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
400
401 sp<AnomalyTracker> anomalyTracker =
402 durationProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs);
403 EXPECT_TRUE(anomalyTracker != nullptr);
404
405 int64_t startTimeNs = bucketStartTimeNs + 1;
406 LogEvent event1(/*uid=*/0, /*pid=*/0);
407 makeLogEvent(&event1, startTimeNs, tagId);
408 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
409
410 int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
411 switch (GetParam()) {
412 case APP_UPGRADE:
413 durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
414 break;
415 case BOOT_COMPLETE:
416 durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
417 break;
418 }
419
420 // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
421 int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
422 LogEvent event2(/*uid=*/0, /*pid=*/0);
423 makeLogEvent(&event2, endTimeNs, tagId);
424 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
425
426 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs,
427 anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
428 }
429
TEST_P(DurationMetricProducerTest_PartialBucket,TestMaxDuration)430 TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDuration) {
431 int64_t bucketStartTimeNs = 10000000000;
432 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
433 int tagId = 1;
434
435 DurationMetric metric;
436 metric.set_id(1);
437 metric.set_bucket(ONE_MINUTE);
438 metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
439 metric.set_split_bucket_for_app_upgrade(true);
440
441 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
442 FieldMatcher dimensions;
443 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
444
445 DurationMetricProducer durationProducer(
446 kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
447 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
448 wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
449
450 int64_t startTimeNs = bucketStartTimeNs + 1;
451 LogEvent event1(/*uid=*/0, /*pid=*/0);
452 makeLogEvent(&event1, startTimeNs, tagId);
453 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
454 ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
455 EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
456
457 int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
458 switch (GetParam()) {
459 case APP_UPGRADE:
460 durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
461 break;
462 case BOOT_COMPLETE:
463 durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
464 break;
465 }
466 ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
467 EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
468 EXPECT_EQ(0, durationProducer.getCurrentBucketNum());
469
470 // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
471 int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
472 LogEvent event2(/*uid=*/0, /*pid=*/0);
473 makeLogEvent(&event2, endTimeNs, tagId);
474 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
475 ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
476
477 durationProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1);
478 std::vector<DurationBucket> buckets =
479 durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
480 ASSERT_EQ(1UL, buckets.size());
481 EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketStartNs);
482 EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[0].mBucketEndNs);
483 EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
484 }
485
TEST_P(DurationMetricProducerTest_PartialBucket,TestMaxDurationWithSplitInNextBucket)486 TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket) {
487 int64_t bucketStartTimeNs = 10000000000;
488 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
489 int tagId = 1;
490
491 DurationMetric metric;
492 metric.set_id(1);
493 metric.set_bucket(ONE_MINUTE);
494 metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
495 metric.set_split_bucket_for_app_upgrade(true);
496
497 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
498 FieldMatcher dimensions;
499 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
500
501 DurationMetricProducer durationProducer(
502 kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
503 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
504 wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
505
506 int64_t startTimeNs = bucketStartTimeNs + 1;
507 LogEvent event1(/*uid=*/0, /*pid=*/0);
508 makeLogEvent(&event1, startTimeNs, tagId);
509 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
510 ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
511 EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
512
513 int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
514 switch (GetParam()) {
515 case APP_UPGRADE:
516 durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
517 break;
518 case BOOT_COMPLETE:
519 durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
520 break;
521 }
522 ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
523 EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
524 EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
525
526 // Stop occurs in the same partial bucket as created for the app upgrade.
527 int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC;
528 LogEvent event2(/*uid=*/0, /*pid=*/0);
529 makeLogEvent(&event2, endTimeNs, tagId);
530 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
531 ASSERT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
532 EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
533
534 durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
535 std::vector<DurationBucket> buckets =
536 durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
537 ASSERT_EQ(1UL, buckets.size());
538 EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketStartNs);
539 EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs);
540 EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
541 }
542
TEST(DurationMetricProducerTest,TestSumDurationAppUpgradeSplitDisabled)543 TEST(DurationMetricProducerTest, TestSumDurationAppUpgradeSplitDisabled) {
544 /**
545 * The duration starts from the first bucket, through one full bucket (10-70sec).
546 * The app upgrade should not split a partial bucket.
547 * Expected buckets:
548 * - [10,70]: All 60 secs
549 * - [70, 75]: Only 5 secs (event ended at 75sec)
550 */
551 int64_t bucketStartTimeNs = 10000000000;
552 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
553 int tagId = 1;
554
555 DurationMetric metric;
556 metric.set_id(1);
557 metric.set_bucket(ONE_MINUTE);
558 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
559 metric.set_split_bucket_for_app_upgrade(false);
560 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
561 FieldMatcher dimensions;
562 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
563
564 DurationMetricProducer durationProducer(
565 kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
566 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
567 wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
568
569 int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
570 LogEvent event1(/*uid=*/0, /*pid=*/0);
571 makeLogEvent(&event1, startTimeNs, tagId);
572 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
573 ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
574 EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
575
576 int64_t appUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
577 durationProducer.notifyAppUpgrade(appUpgradeTimeNs);
578
579 ASSERT_EQ(0UL, durationProducer.mPastBuckets.size());
580 EXPECT_EQ(0, durationProducer.getCurrentBucketNum());
581
582 // We skip ahead one bucket, so we fill in one full bucket and expect 0 partial buckets.
583 int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
584 LogEvent event2(/*uid=*/0, /*pid=*/0);
585 makeLogEvent(&event2, endTimeNs, tagId);
586 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
587 ASSERT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
588 DurationBucket bucket = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0];
589 EXPECT_EQ(bucketStartTimeNs, bucket.mBucketStartNs);
590 EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucket.mBucketEndNs);
591 EXPECT_EQ(bucketSizeNs - 1 * NS_PER_SEC, bucket.mDuration);
592 EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
593 }
594
TEST(DurationMetricProducerTest,TestClearCurrentSlicedTrackerMapWhenStop)595 TEST(DurationMetricProducerTest, TestClearCurrentSlicedTrackerMapWhenStop) {
596 int64_t bucketStartTimeNs = 10000000000;
597 int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
598 int tagId = 1;
599
600 DurationMetric metric;
601 metric.set_id(1);
602 metric.set_bucket(ONE_MINUTE);
603 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
604 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
605 FieldMatcher dimensions;
606 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
607
608 LogEvent event1(/*uid=*/0, /*pid=*/0);
609 makeLogEvent(&event1, bucketStartTimeNs + 50, tagId);
610 LogEvent event2(/*uid=*/0, /*pid=*/0);
611 makeLogEvent(&event2, bucketStartTimeNs + 100, tagId);
612 LogEvent event3(/*uid=*/0, /*pid=*/0);
613 makeLogEvent(&event3, bucketStartTimeNs + 150, tagId);
614 LogEvent event4(/*uid=*/0, /*pid=*/0);
615 makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 5, tagId);
616
617 DurationMetricProducer durationProducer(
618 kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
619 -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
620 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
621 bucketStartTimeNs, bucketStartTimeNs, provider);
622
623 durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + 5);
624 durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
625 durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
626 durationProducer.onMatchedLogEvent(1 /* start index*/, event3);
627 durationProducer.onConditionChanged(false /* condition */, bucketStartTimeNs + 200);
628 durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1);
629 durationProducer.onMatchedLogEvent(2 /* stop index*/, event4);
630
631 ASSERT_TRUE(durationProducer.mCurrentSlicedDurationTrackerMap.empty());
632 EXPECT_EQ(1UL, durationProducer.mPastBuckets.size());
633 EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
634 }
635
TEST(DurationMetricProducerTest,TestCorruptedDataReason_WhatLoss)636 TEST(DurationMetricProducerTest, TestCorruptedDataReason_WhatLoss) {
637 const int64_t bucketStartTimeNs = 10000000000;
638 const int tagId = 1;
639
640 DurationMetric metric;
641 metric.set_id(1);
642 metric.set_bucket(ONE_MINUTE);
643 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
644 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
645 FieldMatcher dimensions;
646 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
647
648 DurationMetricProducer durationProducer(
649 kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
650 -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
651 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
652 bucketStartTimeNs, bucketStartTimeNs, provider);
653
654 durationProducer.onMatchedLogEventLost(tagId, DATA_CORRUPTED_SOCKET_LOSS,
655 MetricProducer::LostAtomType::kWhat);
656 {
657 // Check dump report content.
658 StatsLogReport report = onDumpReport(durationProducer, bucketStartTimeNs + 50);
659 EXPECT_THAT(report.data_corrupted_reason(), ElementsAre(DATA_CORRUPTED_SOCKET_LOSS));
660 }
661
662 durationProducer.onMatchedLogEventLost(tagId, DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW,
663 MetricProducer::LostAtomType::kWhat);
664 {
665 // Check dump report content.
666 StatsLogReport report = onDumpReport(durationProducer, bucketStartTimeNs + 150);
667 EXPECT_THAT(report.data_corrupted_reason(),
668 ElementsAre(DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW, DATA_CORRUPTED_SOCKET_LOSS));
669 }
670 }
671
TEST(DurationMetricProducerTest,TestCorruptedDataReason_ConditionLoss)672 TEST(DurationMetricProducerTest, TestCorruptedDataReason_ConditionLoss) {
673 const int64_t bucketStartTimeNs = 10000000000;
674 const int conditionId = 10;
675
676 DurationMetric metric;
677 metric.set_id(1);
678 metric.set_bucket(ONE_MINUTE);
679 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
680 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
681 FieldMatcher dimensions;
682 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
683
684 DurationMetricProducer durationProducer(
685 kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
686 -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
687 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
688 bucketStartTimeNs, bucketStartTimeNs, provider);
689
690 durationProducer.onMatchedLogEventLost(conditionId, DATA_CORRUPTED_SOCKET_LOSS,
691 MetricProducer::LostAtomType::kCondition);
692 {
693 // Check dump report content.
694 StatsLogReport report = onDumpReport(durationProducer, bucketStartTimeNs + 50);
695 EXPECT_THAT(report.data_corrupted_reason(), ElementsAre(DATA_CORRUPTED_SOCKET_LOSS));
696 }
697
698 durationProducer.onMatchedLogEventLost(conditionId, DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW,
699 MetricProducer::LostAtomType::kCondition);
700 {
701 // Check dump report content.
702 StatsLogReport report = onDumpReport(durationProducer, bucketStartTimeNs + 150);
703 EXPECT_THAT(report.data_corrupted_reason(),
704 ElementsAre(DATA_CORRUPTED_EVENT_QUEUE_OVERFLOW, DATA_CORRUPTED_SOCKET_LOSS));
705 }
706 }
707
TEST(DurationMetricProducerTest,TestCorruptedDataReason_StateLoss)708 TEST(DurationMetricProducerTest, TestCorruptedDataReason_StateLoss) {
709 const int64_t bucketStartTimeNs = 10000000000;
710 const int stateAtomId = 10;
711
712 DurationMetric metric;
713 metric.set_id(1);
714 metric.set_bucket(ONE_MINUTE);
715 metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
716 sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
717 FieldMatcher dimensions;
718 sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
719
720 DurationMetricProducer durationProducer(
721 kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
722 -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
723 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
724 bucketStartTimeNs, bucketStartTimeNs, provider);
725
726 durationProducer.onStateEventLost(stateAtomId, DATA_CORRUPTED_SOCKET_LOSS);
727 {
728 // Check dump report content.
729 StatsLogReport report = onDumpReport(durationProducer, bucketStartTimeNs + 50);
730 EXPECT_THAT(report.data_corrupted_reason(), ElementsAre(DATA_CORRUPTED_SOCKET_LOSS));
731 }
732
733 // validation that data corruption signal remains accurate after another dump
734 {
735 // Check dump report content.
736 StatsLogReport report = onDumpReport(durationProducer, bucketStartTimeNs + 150);
737 EXPECT_THAT(report.data_corrupted_reason(), ElementsAre(DATA_CORRUPTED_SOCKET_LOSS));
738 }
739 }
740
741 } // namespace statsd
742 } // namespace os
743 } // namespace android
744 #else
745 GTEST_LOG_(INFO) << "This test does nothing.\n";
746 #endif
747