// Copyright (C) 2017 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/metrics/DurationMetricProducer.h" #include "src/stats_log_util.h" #include "metrics_test_helper.h" #include "src/condition/ConditionWizard.h" #include #include #include #include #include #include using namespace android::os::statsd; using namespace testing; using android::sp; using std::set; using std::unordered_map; using std::vector; #ifdef __ANDROID__ namespace android { namespace os { namespace statsd { const ConfigKey kConfigKey(0, 12345); TEST(DurationMetricTrackerTest, TestNoCondition) { sp wizard = new NaggyMock(); uint64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; DurationMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); metric.set_aggregation_type(DurationMetric_AggregationType_SUM); int tagId = 1; LogEvent event1(tagId, bucketStartTimeNs + 1); LogEvent event2(tagId, bucketStartTimeNs + bucketSizeNs + 2); FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != durationProducer.mPastBuckets.end()); const auto& buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(2UL, buckets.size()); EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(bucketSizeNs - 1ULL, buckets[0].mDuration); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[1].mBucketEndNs); EXPECT_EQ(2ULL, buckets[1].mDuration); } TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { sp wizard = new NaggyMock(); uint64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; DurationMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); metric.set_aggregation_type(DurationMetric_AggregationType_SUM); int tagId = 1; LogEvent event1(tagId, bucketStartTimeNs + 1); LogEvent event2(tagId, bucketStartTimeNs + 2); LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 1); LogEvent event4(tagId, bucketStartTimeNs + bucketSizeNs + 3); FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); EXPECT_FALSE(durationProducer.mCondition); EXPECT_FALSE(durationProducer.isConditionSliced()); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); durationProducer.flushIfNeededLocked(bucketStartTimeNs + bucketSizeNs + 1); EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); durationProducer.onMatchedLogEvent(1 /* start index*/, event3); durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + bucketSizeNs + 2); durationProducer.onMatchedLogEvent(2 /* stop index*/, event4); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); EXPECT_EQ(1UL, durationProducer.mPastBuckets.size()); EXPECT_TRUE(durationProducer.mPastBuckets.find(DEFAULT_METRIC_DIMENSION_KEY) != durationProducer.mPastBuckets.end()); const auto& buckets2 = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets2.size()); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets2[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets2[0].mBucketEndNs); EXPECT_EQ(1ULL, buckets2[0].mDuration); } TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { /** * The duration starts from the first bucket, through the two partial buckets (10-70sec), * another bucket, and ends at the beginning of the next full bucket. * Expected buckets: * - [10,25]: 14 secs * - [25,70]: All 45 secs * - [70,130]: All 60 secs * - [130, 210]: Only 5 secs (event ended at 135sec) */ uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; uint64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; int tagId = 1; DurationMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); metric.set_aggregation_type(DurationMetric_AggregationType_SUM); sp wizard = new NaggyMock(); FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); EXPECT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); std::vector buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketEndNs); EXPECT_EQ(eventUpgradeTimeNs - startTimeNs, buckets[0].mDuration); EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(3UL, buckets.size()); EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - eventUpgradeTimeNs, buckets[1].mDuration); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); EXPECT_EQ(bucketSizeNs, buckets[2].mDuration); } TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { /** * Expected buckets (start at 11s, upgrade at 75s, end at 135s): * - [10,70]: 59 secs * - [70,75]: 5 sec * - [75,130]: 55 secs */ uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; uint64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; int tagId = 1; DurationMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); metric.set_aggregation_type(DurationMetric_AggregationType_SUM); sp wizard = new NaggyMock(); FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); EXPECT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); std::vector buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs); EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketEndNs); EXPECT_EQ(eventUpgradeTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration); EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(3UL, buckets.size()); EXPECT_EQ(eventUpgradeTimeNs, buckets[2].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - eventUpgradeTimeNs, buckets[2].mDuration); } TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) { uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; uint64_t startTimeNs = bucketStartTimeNs + 1; uint64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC; int tagId = 1; // Setup metric with alert. DurationMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); metric.set_aggregation_type(DurationMetric_AggregationType_SUM); Alert alert; alert.set_num_buckets(3); alert.set_trigger_if_sum_gt(2); sp wizard = new NaggyMock(); FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); sp anomalyTracker = durationProducer.addAnomalyTracker(alert); EXPECT_TRUE(anomalyTracker != nullptr); durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, (uint64_t)anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); } TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) { uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; uint64_t startTimeNs = bucketStartTimeNs + 1; uint64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; int tagId = 1; DurationMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); LogEvent event1(tagId, startTimeNs); event1.write("111"); // uid event1.init(); sp wizard = new NaggyMock(); FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 3 * bucketSizeNs + 1); std::vector buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets.size()); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); } TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) { uint64_t bucketStartTimeNs = 10000000000; uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; uint64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; uint64_t startTimeNs = bucketStartTimeNs + 1; uint64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC; int tagId = 1; DurationMetric metric; metric.set_id(1); metric.set_bucket(ONE_MINUTE); metric.set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE); LogEvent event1(tagId, startTimeNs); event1.write("111"); // uid event1.init(); sp wizard = new NaggyMock(); FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); durationProducer.onMatchedLogEvent(1 /* start index*/, LogEvent(tagId, startTimeNs)); EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); // Stop occurs in the same partial bucket as created for the app upgrade. durationProducer.onMatchedLogEvent(2 /* stop index*/, LogEvent(tagId, endTimeNs)); EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); std::vector buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets.size()); EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); } } // namespace statsd } // namespace os } // namespace android #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif