diff options
Diffstat (limited to 'cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp')
-rw-r--r-- | cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp | 4507 |
1 files changed, 3218 insertions, 1289 deletions
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 2262c76e64f9..5666501d7d51 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -12,21 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "src/matchers/SimpleLogMatchingTracker.h" #include "src/metrics/ValueMetricProducer.h" -#include "src/stats_log_util.h" -#include "metrics_test_helper.h" -#include "tests/statsd_test_util.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <math.h> #include <stdio.h> + #include <vector> +#include "metrics_test_helper.h" +#include "src/matchers/SimpleLogMatchingTracker.h" +#include "src/metrics/MetricProducer.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + using namespace testing; using android::sp; -using android::util::ProtoReader; using std::make_shared; using std::set; using std::shared_ptr; @@ -39,6 +41,8 @@ namespace android { namespace os { namespace statsd { +namespace { + const ConfigKey kConfigKey(0, 12345); const int tagId = 1; const int64_t metricId = 123; @@ -56,10 +60,18 @@ double epsilon = 0.001; static void assertPastBucketValuesSingleKey( const std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>>& mPastBuckets, const std::initializer_list<int>& expectedValuesList, - const std::initializer_list<int64_t>& expectedDurationNsList) { - std::vector<int> expectedValues(expectedValuesList); - std::vector<int64_t> expectedDurationNs(expectedDurationNsList); + const std::initializer_list<int64_t>& expectedDurationNsList, + const std::initializer_list<int64_t>& expectedStartTimeNsList, + const std::initializer_list<int64_t>& expectedEndTimeNsList) { + vector<int> expectedValues(expectedValuesList); + vector<int64_t> expectedDurationNs(expectedDurationNsList); + vector<int64_t> expectedStartTimeNs(expectedStartTimeNsList); + vector<int64_t> expectedEndTimeNs(expectedEndTimeNsList); + ASSERT_EQ(expectedValues.size(), expectedDurationNs.size()); + ASSERT_EQ(expectedValues.size(), expectedStartTimeNs.size()); + ASSERT_EQ(expectedValues.size(), expectedEndTimeNs.size()); + if (expectedValues.size() == 0) { ASSERT_EQ(0, mPastBuckets.size()); return; @@ -68,29 +80,48 @@ static void assertPastBucketValuesSingleKey( ASSERT_EQ(1, mPastBuckets.size()); ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size()); - auto buckets = mPastBuckets.begin()->second; + const vector<ValueBucket>& buckets = mPastBuckets.begin()->second; for (int i = 0; i < expectedValues.size(); i++) { EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value) << "Values differ at index " << i; EXPECT_EQ(expectedDurationNs[i], buckets[i].mConditionTrueNs) << "Condition duration value differ at index " << i; + EXPECT_EQ(expectedStartTimeNs[i], buckets[i].mBucketStartNs) + << "Start time differs at index " << i; + EXPECT_EQ(expectedEndTimeNs[i], buckets[i].mBucketEndNs) + << "End time differs at index " << i; } } +} // anonymous namespace + class ValueMetricProducerTestHelper { +public: + static sp<ValueMetricProducer> createValueProducerNoConditions( + sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) { + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); - public: - static shared_ptr<LogEvent> createEvent(int64_t eventTimeNs, int64_t value) { - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, eventTimeNs); - event->write(tagId); - event->write(value); - event->write(value); - event->init(); - return event; + sp<ValueMetricProducer> valueProducer = + new ValueMetricProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + valueProducer->prepareFirstBucket(); + return valueProducer; } - static sp<ValueMetricProducer> createValueProducerNoConditions( - sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) { + static sp<ValueMetricProducer> createValueProducerWithCondition( + sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric, + ConditionState conditionAfterFirstBucketPrepared) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); @@ -98,19 +129,24 @@ class ValueMetricProducerTestHelper { new EventMatcherWizard({new SimpleLogMatchingTracker( atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); sp<ValueMetricProducer> valueProducer = new ValueMetricProducer( - kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); + kConfigKey, metric, 0 /*condition index*/, {ConditionState::kUnknown}, wizard, + logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); valueProducer->prepareFirstBucket(); + valueProducer->mCondition = conditionAfterFirstBucketPrepared; return valueProducer; } - static sp<ValueMetricProducer> createValueProducerWithCondition( - sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) { + static sp<ValueMetricProducer> createValueProducerWithState( + sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric, + vector<int32_t> slicedStateAtoms, + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap) { UidMap uidMap; SimpleAtomMatcher atomMatcher; atomMatcher.set_atom_id(tagId); @@ -118,15 +154,42 @@ class ValueMetricProducerTestHelper { new EventMatcherWizard({new SimpleLogMatchingTracker( atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); - sp<ValueMetricProducer> valueProducer = - new ValueMetricProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, - eventMatcherWizard, tagId, bucketStartTimeNs, - bucketStartTimeNs, pullerManager); + sp<ValueMetricProducer> valueProducer = new ValueMetricProducer( + kConfigKey, metric, -1 /* no condition */, {}, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager, {}, + {}, slicedStateAtoms, stateGroupMap); valueProducer->prepareFirstBucket(); - valueProducer->mCondition = ConditionState::kFalse; + return valueProducer; + } + + static sp<ValueMetricProducer> createValueProducerWithConditionAndState( + sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric, + vector<int32_t> slicedStateAtoms, + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap, + ConditionState conditionAfterFirstBucketPrepared) { + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); + + sp<ValueMetricProducer> valueProducer = new ValueMetricProducer( + kConfigKey, metric, 0 /* condition tracker index */, {ConditionState::kUnknown}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager, {}, {}, slicedStateAtoms, stateGroupMap); + valueProducer->prepareFirstBucket(); + valueProducer->mCondition = conditionAfterFirstBucketPrepared; return valueProducer; } @@ -145,8 +208,27 @@ class ValueMetricProducerTestHelper { metric.set_condition(StringToId("SCREEN_ON")); return metric; } + + static ValueMetric createMetricWithState(string state) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.add_slice_by_state(StringToId(state)); + return metric; + } + + static ValueMetric createMetricWithConditionAndState(string state) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_condition(StringToId("SCREEN_ON")); + metric.add_slice_by_state(StringToId(state)); + return metric; + } }; +// Setup for parameterized tests. +class ValueMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {}; + +INSTANTIATE_TEST_SUITE_P(ValueMetricProducerTest_PartialBucket, + ValueMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); /* * Tests that the first bucket works correctly @@ -166,9 +248,9 @@ TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime) { // statsd started long ago. // The metric starts in the middle of the bucket - ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, -1, startTimeBase, - 22, pullerManager); + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, -1, + startTimeBase, 22, pullerManager); valueProducer.prepareFirstBucket(); EXPECT_EQ(startTimeBase, valueProducer.calcPreviousBucketEndTime(60 * NS_PER_SEC + 10)); @@ -196,8 +278,8 @@ TEST(ValueMetricProducerTest, TestFirstBucket) { // statsd started long ago. // The metric starts in the middle of the bucket - ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, -1, 5, + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, -1, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, pullerManager); valueProducer.prepareFirstBucket(); @@ -210,16 +292,13 @@ TEST(ValueMetricProducerTest, TestFirstBucket) { * Tests pulled atoms with no conditions */ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); return true; })); @@ -228,63 +307,55 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(8, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(23); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(23, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(23, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(12, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event->write(tagId); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(13, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(3UL, valueProducer->mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(3UL, valueProducer->mPastBuckets.begin()->second.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); EXPECT_EQ(12, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value); @@ -293,28 +364,27 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[2].mConditionTrueNs); } -TEST(ValueMetricProducerTest, TestPartialBucketCreated) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); +TEST_P(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Initialize bucket. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(tagId); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 1)); return true; })) // Partial bucket. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([partialBucketSplitTimeNs]( + int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10); - event->write(tagId); - event->write(5); - event->init(); - data->push_back(event); + data->push_back( + CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs + 8, 5)); return true; })); @@ -324,34 +394,32 @@ TEST(ValueMetricProducerTest, TestPartialBucketCreated) { // First bucket ends. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10); - event->write(tagId); - event->write(2); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 2)); valueProducer->onDataPulled(allData, /** success */ true, bucket2StartTimeNs); // Partial buckets created in 2nd bucket. - valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer->getCurrentBucketNum()); - // One full bucket and one partial bucket. - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - vector<ValueBucket> buckets = valueProducer->mPastBuckets.begin()->second; - EXPECT_EQ(2UL, buckets.size()); - // Full bucket (2 - 1) - EXPECT_EQ(1, buckets[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, buckets[0].mConditionTrueNs); - // Full bucket (5 - 3) - EXPECT_EQ(3, buckets[1].values[0].long_value); - // partial bucket [bucket2StartTimeNs, bucket2StartTimeNs + 2] - EXPECT_EQ(2, buckets[1].mConditionTrueNs); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1, 3}, + {bucketSizeNs, partialBucketSplitTimeNs - bucket2StartTimeNs}, + {bucketStartTimeNs, bucket2StartTimeNs}, + {bucket2StartTimeNs, partialBucketSplitTimeNs}); } /* * Tests pulled atoms with filtering */ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -364,81 +432,67 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(3); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 3, 3)); return true; })); sp<ValueMetricProducer> valueProducer = new ValueMetricProducer( - kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, tagId, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); + kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer->prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(3); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 3, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(8, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(4); - event->write(23); - event->init(); - allData.push_back(event); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket3StartTimeNs + 1, 4, 23)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // No new data seen, so data has been cleared. - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(8, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event->write(3); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 3, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // the base was reset - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.begin()->second.size()); EXPECT_EQ(8, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); } @@ -451,61 +505,54 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { metric.set_use_absolute_value_on_reset(true); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true)); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(true)); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(10); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(10, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(10, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second.back().values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second.back().mConditionTrueNs); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event->write(tagId); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(26, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.begin()->second.size()); EXPECT_EQ(10, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[1].values[0].long_value); @@ -518,57 +565,50 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(false)); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(false)); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(10); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(10, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event->write(tagId); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(26, curInterval.value.long_value); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_EQ(26, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[0].mConditionTrueNs); } @@ -581,82 +621,81 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // First condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); // Second condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 130)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 1); // Third condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(180); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 180)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:false sum:0 start:100 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(1); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(110, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(110, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(10, curInterval.value.long_value); valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(20, curInterval.value.long_value); - EXPECT_EQ(false, curInterval.hasBase); + EXPECT_EQ(false, curBaseInfo.hasBase); valueProducer->onConditionChanged(true, bucket3StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1}, + {bucketStartTimeNs, bucket2StartTimeNs}, + {bucket2StartTimeNs, bucket3StartTimeNs}); } -TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; @@ -667,42 +706,58 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - - valueProducer.notifyAppUpgrade(bucketStartTimeNs + 150, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); - - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 59 * NS_PER_SEC); - event2->write(1); - event2->write(10); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); - EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 150; + switch (GetParam()) { + case APP_UPGRADE: + valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); + + // Event arrives after the bucket split. + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 20); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); + + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); // Next value should create a new bucket. - shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 65 * NS_PER_SEC); - event3->write(1); - event3->write(10); - event3->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); - EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 5 * NS_PER_SEC, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10, 20}, + {partialBucketSplitTimeNs - bucketStartTimeNs, + bucket2StartTimeNs - partialBucketSplitTimeNs}, + {bucketStartTimeNs, partialBucketSplitTimeNs}, + {partialBucketSplitTimeNs, bucket2StartTimeNs}); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); } -TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); +TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -712,53 +767,53 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150; + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) .WillOnce(Return(true)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([partialBucketSplitTimeNs]( + int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 149); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 120)); return true; })); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(100); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100)); valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucket2StartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}, {bucket2StartTimeNs}, + {partialBucketSplitTimeNs}); allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(150); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 150)); valueProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucket3StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(20L, - valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].values[0].long_value); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, - {150, bucketSizeNs - 150}); + EXPECT_EQ(2, valueProducer.getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, {150, bucketSizeNs - 150}, + {bucket2StartTimeNs, partialBucketSplitTimeNs}, + {partialBucketSplitTimeNs, bucket3StartTimeNs}); } TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { @@ -773,69 +828,72 @@ TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true)); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(true)); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(100); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 100)); valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1); - EXPECT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150); + ASSERT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); } -TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to true time. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, + bucket2StartTimeNs - 100); // Condition change to false time. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs - 100); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 120)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); - valueProducer->onConditionChanged(false, bucket2StartTimeNs-100); + valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100); EXPECT_FALSE(valueProducer->mCondition); - valueProducer->notifyAppUpgrade(bucket2StartTimeNs-50, "ANY.APP", 1, 1); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs - 50; + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } // Expect one full buckets already done and starting a partial bucket. - EXPECT_EQ(bucket2StartTimeNs-50, valueProducer->mCurrentBucketStartTimeNs); - EXPECT_EQ(1UL, valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs, - valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, - {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)}); + {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); EXPECT_FALSE(valueProducer->mCondition); } @@ -851,35 +909,36 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(20); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(30, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { @@ -894,59 +953,54 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, - eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, - pullerManager); + ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, + logEventMatcherIndex, eventMatcherWizard, -1, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); valueProducer.mCondition = ConditionState::kFalse; - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has 1 slice - EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); valueProducer.onConditionChangedLocked(true, bucketStartTimeNs + 15); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(20); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(20, curInterval.value.long_value); - shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 30); - event3->write(1); - event3->write(30); - event3->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 30); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(50, curInterval.value.long_value); valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35); - shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 40); - event4->write(1); - event4->write(40); - event4->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 40); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(50, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}, {bucketStartTimeNs}, + {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestAnomalyDetection) { @@ -969,136 +1023,137 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - logEventMatcherIndex, eventMatcherWizard, -1 /*not pulled*/, - bucketStartTimeNs, bucketStartTimeNs, pullerManager); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {}, + wizard, logEventMatcherIndex, eventMatcherWizard, + -1 /*not pulled*/, bucketStartTimeNs, bucketStartTimeNs, + pullerManager); valueProducer.prepareFirstBucket(); sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert, alarmMonitor); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 1 * NS_PER_SEC, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 2 + NS_PER_SEC, 20); + + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, + bucketStartTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC, 130); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event4, tagId, + bucketStartTimeNs + 3 * bucketSizeNs + 1 * NS_PER_SEC, 1); - shared_ptr<LogEvent> event1 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1 * NS_PER_SEC); - event1->write(161); - event1->write(10); // value of interest - event1->init(); - shared_ptr<LogEvent> event2 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 + NS_PER_SEC); - event2->write(162); - event2->write(20); // value of interest - event2->init(); - shared_ptr<LogEvent> event3 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 2 * bucketSizeNs + 1 * NS_PER_SEC); - event3->write(163); - event3->write(130); // value of interest - event3->init(); - shared_ptr<LogEvent> event4 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 1 * NS_PER_SEC); - event4->write(35); - event4->write(1); // value of interest - event4->init(); - shared_ptr<LogEvent> event5 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC); - event5->write(45); - event5->write(150); // value of interest - event5->init(); - shared_ptr<LogEvent> event6 - = make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10 * NS_PER_SEC); - event6->write(25); - event6->write(160); // value of interest - event6->init(); + LogEvent event5(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event5, tagId, + bucketStartTimeNs + 3 * bucketSizeNs + 2 * NS_PER_SEC, 150); + + LogEvent event6(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event6, tagId, + bucketStartTimeNs + 3 * bucketSizeNs + 10 * NS_PER_SEC, 160); // Two events in bucket #0. - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // Value sum == 30 <= 130. EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // One event in bucket #2. No alarm as bucket #0 is trashed out. - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); // Value sum == 130 <= 130. EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), 0U); // Three events in bucket #3. - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); // Anomaly at event 4 since Value sum == 131 > 130! EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event5); + std::ceil(1.0 * event4.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event5); // Event 5 is within 3 sec refractory period. Thus last alarm timestamp is still event4. EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event4->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + std::ceil(1.0 * event4.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event6); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event6); // Anomaly at event 6 since Value sum == 160 > 130 and after refractory period. EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_METRIC_DIMENSION_KEY), - std::ceil(1.0 * event6->GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); + std::ceil(1.0 * event6.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec)); } // Test value metric no condition, the pull on bucket boundary come in time and too late TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true)); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Return(true)); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; // pull 1 allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(11); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:true sum:0 start:11 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(11, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(11, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // pull 2 at correct time allData.clear(); - event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event->write(tagId); - event->write(23); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 23)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // tartUpdated:false sum:12 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(23, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(23, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); // pull 3 come late. // The previous bucket gets closed with error. (Has start value 23, no ending) // Another bucket gets closed with error. (No start, but ending with 36) // The new bucket is back to normal. allData.clear(); - event = make_shared<LogEvent>(tagId, bucket6StartTimeNs + 1); - event->write(tagId); - event->write(36); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket6StartTimeNs + 1, 36)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket6StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:false sum:12 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(36, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); + // The 1st bucket is dropped because of no data + // The 3rd bucket is dropped due to multiple buckets being skipped. + ASSERT_EQ(2, valueProducer->mSkippedBuckets.size()); + + EXPECT_EQ(bucketStartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); + EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); + ASSERT_EQ(1, valueProducer->mSkippedBuckets[0].dropEvents.size()); + EXPECT_EQ(NO_DATA, valueProducer->mSkippedBuckets[0].dropEvents[0].reason); + EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs); + + EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[1].bucketStartTimeNs); + EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].bucketEndTimeNs); + ASSERT_EQ(1, valueProducer->mSkippedBuckets[1].dropEvents.size()); + EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[1].dropEvents[0].reason); + EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].dropEvents[0].dropTimeNs); } /* @@ -1109,56 +1164,58 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // First condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })) // condition becomes false - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); // Second condition change. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // pull on bucket boundary come late, condition change happens before it valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + EXPECT_EQ(false, curBaseInfo.hasBase); // Now the alarm is delivered. // since the condition turned to off before this pull finish, it has no effect vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 110)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); } @@ -1170,87 +1227,90 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })) // condition becomes false - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 120)); return true; })) // condition becomes true again - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 25); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 25); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 25, 130)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // startUpdated:false sum:0 start:100 - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // pull on bucket boundary come late, condition change happens before it valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); // condition changed to true again, before the pull alarm is delivered valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(130, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(130, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); // Now the alarm is delivered, but it is considered late, the data will be used // for the new bucket since it was just pulled. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 50, 140)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 50, 140)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 50); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(140, curInterval.base.long_value); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(10, curInterval.value.long_value); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs, 160)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 160)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20, 30}, - {bucketSizeNs - 8, bucketSizeNs - 24}); + assertPastBucketValuesSingleKey( + valueProducer->mPastBuckets, {20, 30}, {bucketSizeNs - 8, bucketSizeNs - 24}, + {bucketStartTimeNs, bucket2StartTimeNs}, {bucket2StartTimeNs, bucket3StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateMin) { @@ -1266,36 +1326,35 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(20); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateMax) { @@ -1311,38 +1370,34 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(20); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(20, curInterval.value.long_value); - valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); */ - /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); */ - /* EXPECT_EQ(20, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value); */ + valueProducer.flushIfNeededLocked(bucket2StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { @@ -1358,39 +1413,36 @@ TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(15); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval; curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(1, curInterval.sampleSize); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(25, curInterval.value.long_value); EXPECT_EQ(2, curInterval.sampleSize); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); EXPECT_TRUE(std::abs(valueProducer.mPastBuckets.begin()->second.back().values[0].double_value - 12.5) < epsilon); @@ -1409,36 +1461,34 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20); - event2->write(1); - event2->write(15); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(25, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { @@ -1455,63 +1505,61 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 15); - event2->write(1); - event2->write(15); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(10, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(5, curInterval.value.long_value); // no change in data. - shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10); - event3->write(1); - event3->write(15); - event3->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(15, curInterval.base.long_value); + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(0, curInterval.value.long_value); - shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 15); - event4->write(1); - event4->write(15); - event4->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15); + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(15, curInterval.base.long_value); + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(0, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { @@ -1529,85 +1577,84 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event1->write(1); - event1->write(10); - event1->write(20); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 15); - event2->write(1); - event2->write(15); - event2->write(22); - event2->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); + LogEvent event1(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10, 20); + + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 1, 15, 22); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval0 = - valueProducer.mCurrentSlicedBucket.begin()->second[0]; - ValueMetricProducer::Interval curInterval1 = - valueProducer.mCurrentSlicedBucket.begin()->second[1]; - EXPECT_EQ(true, curInterval0.hasBase); - EXPECT_EQ(10, curInterval0.base.long_value); - EXPECT_EQ(false, curInterval0.hasValue); - EXPECT_EQ(true, curInterval1.hasBase); - EXPECT_EQ(20, curInterval1.base.long_value); - EXPECT_EQ(false, curInterval1.hasValue); - - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(10, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(20, curBaseInfo.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1]; - EXPECT_EQ(true, curInterval0.hasValue); - EXPECT_EQ(5, curInterval0.value.long_value); - EXPECT_EQ(true, curInterval1.hasValue); - EXPECT_EQ(2, curInterval1.value.long_value); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(5, curInterval.value.long_value); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(2, curInterval.value.long_value); // no change in first value field - shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10); - event3->write(1); - event3->write(15); - event3->write(25); - event3->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1]; - EXPECT_EQ(true, curInterval0.hasBase); - EXPECT_EQ(15, curInterval0.base.long_value); - EXPECT_EQ(true, curInterval0.hasValue); - EXPECT_EQ(true, curInterval1.hasBase); - EXPECT_EQ(25, curInterval1.base.long_value); - EXPECT_EQ(true, curInterval1.hasValue); - - shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 15); - event4->write(1); - event4->write(15); - event4->write(29); - event4->init(); - valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4); - EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0]; - curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1]; - EXPECT_EQ(true, curInterval0.hasBase); - EXPECT_EQ(15, curInterval0.base.long_value); - EXPECT_EQ(true, curInterval0.hasValue); - EXPECT_EQ(true, curInterval1.hasBase); - EXPECT_EQ(29, curInterval1.base.long_value); - EXPECT_EQ(true, curInterval1.hasValue); + LogEvent event3(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 1, 15, 25); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(25, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + + LogEvent event4(/*uid=*/0, /*pid=*/0); + CreateThreeValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 1, 15, 29); + + valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4); + ASSERT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(15, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); + curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[1]; + curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[1]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(29, curBaseInfo.base.long_value); + EXPECT_EQ(true, curInterval.hasValue); valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); - EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size()); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second[1].values.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size()); + ASSERT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size()); + ASSERT_EQ(1UL, valueProducer.mPastBuckets.begin()->second[1].values.size()); EXPECT_EQ(bucketSizeNs, valueProducer.mPastBuckets.begin()->second[0].mConditionTrueNs); EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second[0].values[0].long_value); @@ -1630,47 +1677,38 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { metric.set_use_zero_default_base(true); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(1); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto iter = valueProducer->mCurrentSlicedBucket.begin(); auto& interval1 = iter->second[0]; + auto iterBase = valueProducer->mCurrentBaseInfo.begin(); + auto& baseInfo1 = iterBase->second[0]; EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(3, interval1.base.long_value); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(3, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event1->write(2); - event1->write(4); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(11); - event2->init(); - allData.push_back(event1); - allData.push_back(event2); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(11, interval1.base.long_value); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(11, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(8, interval1.value.long_value); @@ -1680,15 +1718,23 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { break; } } + auto itBase = valueProducer->mCurrentBaseInfo.begin(); + for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) { + if (itBase != iterBase) { + break; + } + } EXPECT_TRUE(it != iter); + EXPECT_TRUE(itBase != iterBase); auto& interval2 = it->second[0]; + auto& baseInfo2 = itBase->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(4, interval2.base.long_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(4, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_EQ(4, interval2.value.long_value); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); auto iterator = valueProducer->mPastBuckets.begin(); EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); EXPECT_EQ(8, iterator->second[0].values[0].long_value); @@ -1707,107 +1753,100 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { metric.set_use_zero_default_base(true); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(1); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - auto iter = valueProducer->mCurrentSlicedBucket.begin(); - auto& interval1 = iter->second[0]; - EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(3, interval1.base.long_value); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + const auto& it = valueProducer->mCurrentSlicedBucket.begin(); + ValueMetricProducer::Interval& interval1 = it->second[0]; + ValueMetricProducer::BaseInfo& baseInfo1 = + valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat())->second[0]; + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(3, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event1->write(2); - event1->write(4); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(11); - event2->init(); - allData.push_back(event1); - allData.push_back(event2); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(11, interval1.base.long_value); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(11, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(8, interval1.value.long_value); - auto it = valueProducer->mCurrentSlicedBucket.begin(); - for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { - if (it != iter) { + auto it2 = valueProducer->mCurrentSlicedBucket.begin(); + for (; it2 != valueProducer->mCurrentSlicedBucket.end(); it2++) { + if (it2 != it) { break; } } - EXPECT_TRUE(it != iter); - auto& interval2 = it->second[0]; - EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(4, interval2.base.long_value); + EXPECT_TRUE(it2 != it); + ValueMetricProducer::Interval& interval2 = it2->second[0]; + ValueMetricProducer::BaseInfo& baseInfo2 = + valueProducer->mCurrentBaseInfo.find(it2->first.getDimensionKeyInWhat())->second[0]; + EXPECT_EQ(2, it2->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(4, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_EQ(4, interval2.value.long_value); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); // next pull somehow did not happen, skip to end of bucket 3 allData.clear(); - event1 = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event1->write(2); - event1->write(5); - event1->init(); - allData.push_back(event1); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(5, interval2.base.long_value); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(5, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); allData.clear(); - event1 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1); - event1->write(2); - event1->write(13); - event1->init(); - allData.push_back(event1); - event2 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1); - event2->write(1); - event2->write(5); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 13)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 1, 5)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - auto it1 = std::next(valueProducer->mCurrentSlicedBucket.begin())->second[0]; - EXPECT_EQ(true, it1.hasBase); - EXPECT_EQ(13, it1.base.long_value); - EXPECT_EQ(false, it1.hasValue); - EXPECT_EQ(8, it1.value.long_value); - auto it2 = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, it2.hasBase); - EXPECT_EQ(5, it2.base.long_value); - EXPECT_EQ(false, it2.hasValue); - EXPECT_EQ(5, it2.value.long_value); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Get new references now that entries have been deleted from the map + const auto& it3 = valueProducer->mCurrentSlicedBucket.begin(); + const auto& it4 = std::next(valueProducer->mCurrentSlicedBucket.begin()); + ASSERT_EQ(it3->second.size(), 1); + ASSERT_EQ(it4->second.size(), 1); + ValueMetricProducer::Interval& interval3 = it3->second[0]; + ValueMetricProducer::Interval& interval4 = it4->second[0]; + ValueMetricProducer::BaseInfo& baseInfo3 = + valueProducer->mCurrentBaseInfo.find(it3->first.getDimensionKeyInWhat())->second[0]; + ValueMetricProducer::BaseInfo& baseInfo4 = + valueProducer->mCurrentBaseInfo.find(it4->first.getDimensionKeyInWhat())->second[0]; + + EXPECT_EQ(true, baseInfo3.hasBase); + EXPECT_EQ(5, baseInfo3.base.long_value); + EXPECT_EQ(false, interval3.hasValue); + EXPECT_EQ(5, interval3.value.long_value); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + + EXPECT_EQ(true, baseInfo4.hasBase); + EXPECT_EQ(13, baseInfo4.base.long_value); + EXPECT_EQ(false, interval4.hasValue); + EXPECT_EQ(8, interval4.value.long_value); + + ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); } /* @@ -1819,50 +1858,42 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { metric.mutable_dimensions_in_what()->add_child()->set_field(1); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(1); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1, 3)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto iter = valueProducer->mCurrentSlicedBucket.begin(); auto& interval1 = iter->second[0]; + auto iterBase = valueProducer->mCurrentBaseInfo.begin(); + auto& baseInfo1 = iterBase->second[0]; EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(3, interval1.base.long_value); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(3, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); - vector<shared_ptr<LogEvent>> allData; + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event1->write(2); - event1->write(4); - event1->init(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(11); - event2->init(); - allData.push_back(event1); - allData.push_back(event2); - + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(true, interval1.hasBase); - EXPECT_EQ(11, interval1.base.long_value); + + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, baseInfo1.hasBase); + EXPECT_EQ(11, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(8, interval1.value.long_value); EXPECT_FALSE(interval1.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); auto it = valueProducer->mCurrentSlicedBucket.begin(); for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { @@ -1870,51 +1901,57 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { break; } } + auto itBase = valueProducer->mCurrentBaseInfo.begin(); + for (; itBase != valueProducer->mCurrentBaseInfo.end(); it++) { + if (itBase != iterBase) { + break; + } + } EXPECT_TRUE(it != iter); - auto& interval2 = it->second[0]; + EXPECT_TRUE(itBase != iterBase); + auto interval2 = it->second[0]; + auto baseInfo2 = itBase->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(4, interval2.base.long_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(4, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); // next pull somehow did not happen, skip to end of bucket 3 allData.clear(); - event1 = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); - event1->write(2); - event1->write(5); - event1->init(); - allData.push_back(event1); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket4StartTimeNs + 1, 2, 5)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket4StartTimeNs); - // Only one interval left. One was trimmed. - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Only one interval left. One was trimmed. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(5, interval2.base.long_value); + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(5, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); allData.clear(); - event1 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1); - event1->write(2); - event1->write(14); - event1->init(); - allData.push_back(event1); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket5StartTimeNs); interval2 = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, interval2.hasBase); - EXPECT_EQ(14, interval2.base.long_value); + baseInfo2 = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, baseInfo2.hasBase); + EXPECT_EQ(14, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); auto iterator = valueProducer->mPastBuckets.begin(); + EXPECT_EQ(bucket4StartTimeNs, iterator->second[0].mBucketStartNs); + EXPECT_EQ(bucket5StartTimeNs, iterator->second[0].mBucketEndNs); EXPECT_EQ(9, iterator->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); iterator++; + EXPECT_EQ(bucketStartTimeNs, iterator->second[0].mBucketStartNs); + EXPECT_EQ(bucket2StartTimeNs, iterator->second[0].mBucketEndNs); EXPECT_EQ(8, iterator->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); } @@ -1924,33 +1961,32 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfB sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); // Used by onConditionChanged. - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); vector<shared_ptr<LogEvent>> allData; valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(false, curInterval.hasBase); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); } @@ -1959,38 +1995,38 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); // Condition change to true. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })) .WillOnce(Return(false)); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + ValueMetricProducer::BaseInfo& curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); valueProducer->onConditionChanged(false, bucketStartTimeNs + 20); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); EXPECT_EQ(false, curInterval.hasValue); - EXPECT_EQ(false, curInterval.hasBase); + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, valueProducer->mHasGlobalBase); } @@ -1998,41 +2034,39 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(50); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 50)); return false; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 1); // Condition change to false. data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 100)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); // Don't directly set mCondition; the real code never does that. Go through regular code path // to avoid unexpected behaviors. // valueProducer->mCondition = ConditionState::kTrue; valueProducer->onConditionChanged(true, bucketStartTimeNs); - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); valueProducer->onConditionChanged(false, bucketStartTimeNs + 1); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); } @@ -2043,25 +2077,21 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded) { metric.set_max_pull_delay_sec(0); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 120)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); // Max delay is set to 0 so pull will exceed max delay. valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); } TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { @@ -2075,84 +2105,77 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, - eventMatcherWizard, tagId, bucket2StartTimeNs, - bucket2StartTimeNs, pullerManager); + ValueMetricProducer valueProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucket2StartTimeNs, bucket2StartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); valueProducer.mCondition = ConditionState::kFalse; // Event should be skipped since it is from previous bucket. // Pull should not be called. valueProducer.onConditionChanged(true, bucketStartTimeNs); - EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); } TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 1, _, _)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(tagId); - event->write(100); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 100)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->mHasGlobalBase = false; valueProducer->onConditionChanged(true, bucketStartTimeNs + 1); valueProducer->mHasGlobalBase = true; - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(100, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(100, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); } -TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed) { +/* + * Tests that a bucket is marked invalid when a condition change pull fails. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged .WillOnce(Return(false)) // Second onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kTrue; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kTrue); // Bucket start. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(1); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); // This will fail and should invalidate the whole bucket since we do not have all the data @@ -2162,94 +2185,137 @@ TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed) { // Bucket end. allData.clear(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(140); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // Contains base from last pull which was successful. - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(140, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10, false /* include partial bucket */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); } -TEST(ValueMetricProducerTest, TestInvalidBucketWhenGuardRailHit) { +/* + * Tests that a bucket is marked invalid when the guardrail is hit. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); metric.mutable_dimensions_in_what()->set_field(tagId); metric.mutable_dimensions_in_what()->add_child()->set_field(1); metric.set_condition(StringToId("SCREEN_ON")); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 2, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { for (int i = 0; i < 2000; i++) { - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(i); - event->write(i); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i)); } return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 2); - EXPECT_EQ(true, valueProducer->mCurrentBucketIsInvalid); - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(true, valueProducer->mCurrentBucketIsSkipped); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mSkippedBuckets.size()); + + // Bucket 2 start. + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // First bucket added to mSkippedBuckets after flush. + ASSERT_EQ(1UL, valueProducer->mSkippedBuckets.size()); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, + true, FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::DIMENSION_GUARDRAIL_REACHED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); } -TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed) { +/* + * Tests that a bucket is marked invalid when the bucket's initial pull fails. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120)); return true; })) // Second onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kTrue; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kTrue); // Bucket start. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(1); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs); valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); @@ -2257,105 +2323,131 @@ TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed) { // Bucket end. allData.clear(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(140); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); // Contains base from last pull which was successful. - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(140, curInterval.base.long_value); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, + true, FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis()); } -TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed) { +/* + * Tests that a bucket is marked invalid when the bucket's final pull fails + * (i.e. failed pull on bucket boundary). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 2); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(120); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 120)); return true; })) // Second onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 3); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); - event->write(tagId); - event->write(130); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 8, 130)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - - valueProducer->mCondition = ConditionState::kTrue; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kTrue); // Bucket start. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); - event->write(1); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs); - // This will fail and should invalidate the whole bucket since we do not have all the data - // needed to compute the metric value when the screen was on. valueProducer->onConditionChanged(false, bucketStartTimeNs + 2); valueProducer->onConditionChanged(true, bucketStartTimeNs + 3); // Bucket end. allData.clear(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event2->write(1); - event2->write(140); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140)); valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1); - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); - // Last pull failed so based has been reset. - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(0UL, valueProducer->mPastBuckets.size()); + // Last pull failed so base has been reset. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */, + true, FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); } TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) // Start bucket. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); return true; })); @@ -2365,62 +2457,59 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled) { // Bucket 2 start. vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(110); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); // Bucket 3 empty. allData.clear(); - shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); - event2->init(); - allData.push_back(event2); + allData.push_back(CreateNoValuesLogEvent(tagId, bucket3StartTimeNs + 1)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); // Data has been trimmed. - EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); } TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onConditionChanged) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(3); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); // Empty pull. valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(false, valueProducer->mHasGlobalBase); } @@ -2429,46 +2518,42 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 11); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(2); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 2)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 12); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(5); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 5)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); valueProducer->onConditionChanged(false, bucketStartTimeNs + 11); valueProducer->onConditionChanged(true, bucketStartTimeNs + 12); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval& curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); @@ -2476,16 +2561,18 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { vector<shared_ptr<LogEvent>> allData; allData.clear(); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; // Data is empty, base should be reset. - EXPECT_EQ(false, curInterval.hasBase); - EXPECT_EQ(5, curInterval.base.long_value); + EXPECT_EQ(false, curBaseInfo.hasBase); + EXPECT_EQ(5, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1}); + ASSERT_EQ(1UL, valueProducer->mPastBuckets.size()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { @@ -2495,165 +2582,149 @@ TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { metric.set_condition(StringToId("SCREEN_ON")); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _, _)) // First onConditionChanged - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); // End of bucket vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(2); - event->write(2); - event->init(); - allData.push_back(event); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 2)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Key 1 should be reset since in not present in the most pull. - EXPECT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); auto iterator = valueProducer->mCurrentSlicedBucket.begin(); - EXPECT_EQ(true, iterator->second[0].hasBase); - EXPECT_EQ(2, iterator->second[0].base.long_value); + auto baseInfoIter = valueProducer->mCurrentBaseInfo.begin(); + EXPECT_EQ(true, baseInfoIter->second[0].hasBase); + EXPECT_EQ(2, baseInfoIter->second[0].base.long_value); EXPECT_EQ(false, iterator->second[0].hasValue); iterator++; - EXPECT_EQ(false, iterator->second[0].hasBase); - EXPECT_EQ(1, iterator->second[0].base.long_value); + baseInfoIter++; + EXPECT_EQ(false, baseInfoIter->second[0].hasBase); + EXPECT_EQ(1, baseInfoIter->second[0].base.long_value); EXPECT_EQ(false, iterator->second[0].hasValue); EXPECT_EQ(true, valueProducer->mHasGlobalBase); } -TEST(ValueMetricProducerTest, TestBucketIncludingUnknownConditionIsInvalid) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); - metric.mutable_dimensions_in_what()->set_field(tagId); - metric.mutable_dimensions_in_what()->add_child()->set_field(1); - metric.set_condition(StringToId("SCREEN_ON")); - - sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) - // Second onConditionChanged. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { - data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(2); - event->write(2); - event->init(); - data->push_back(event); - return true; - })); - - sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kUnknown; - - valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); - valueProducer->onConditionChanged(true, bucketStartTimeNs + 20); - - // End of bucket - vector<shared_ptr<LogEvent>> allData; - allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(4); - event->write(4); - event->init(); - allData.push_back(event); - valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - - // Bucket is incomplete so it is mark as invalid, however the base is fine since the last pull - // succeeded. - EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); -} - -TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + bucketSizeNs / 2; + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Initialization. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) // notifyAppUpgrade. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([partialBucketSplitTimeNs]( + int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + bucketSizeNs / 2, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); - valueProducer->notifyAppUpgrade(bucketStartTimeNs + bucketSizeNs / 2, "com.foo", 10000, 1); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); ASSERT_EQ(1UL, valueProducer->mCurrentFullBucket.size()); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs + 1, 4)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 4)); + // Pull fails and arrives late. valueProducer->onDataPulled(allData, /** fails */ false, bucket3StartTimeNs + 1); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + ASSERT_EQ(1, valueProducer->mSkippedBuckets.size()); + ASSERT_EQ(2, valueProducer->mSkippedBuckets[0].dropEvents.size()); + EXPECT_EQ(PULL_FAILED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason); + EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[1].reason); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); + EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); } TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Second onConditionChanged. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); data->clear(); - data->push_back( - ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 10, 5)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 5)); return true; })) // Third onConditionChanged. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket3StartTimeNs + 10); data->clear(); - data->push_back( - ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs + 10, 7)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 10, 7)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kUnknown; + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); valueProducer->onConditionChanged(false, bucketStartTimeNs); ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); // End of first bucket vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 1, 4)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 4)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(true, curInterval.hasBase); - EXPECT_EQ(5, curInterval.base.long_value); + auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(true, curBaseInfo.hasBase); + EXPECT_EQ(5, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); valueProducer->onConditionChanged(false, bucket3StartTimeNs + 10); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); } TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { @@ -2665,26 +2736,28 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs + 30, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 20)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) // Initialization. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })); @@ -2692,148 +2765,160 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs + 30, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs + 30); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 20)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } -TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Initialization. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) // notifyAppUpgrade. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([partialBucketSplitTimeNs]( + int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, partialBucketSplitTimeNs); data->clear(); - data->push_back( - ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 2, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First on condition changed. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) // Second on condition changed. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 3)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 12); - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + auto curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(2, curInterval.value.long_value); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 1, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}, {bucketStartTimeNs}, + {bucket2StartTimeNs}); } +// TODO: b/145705635 fix or delete this test TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // First condition change. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 1)); return true; })) // 2nd condition change. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 8); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1)); return true; })) // 3rd condition change. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 1)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 1)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucketStartTimeNs + 3, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 3, 10)); valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs + 3); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs, 20)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 20)); valueProducer->onDataPulled(allData, /** succeed */ false, bucket2StartTimeNs); valueProducer->onConditionChanged(false, bucket2StartTimeNs + 8); valueProducer->onConditionChanged(true, bucket2StartTimeNs + 10); allData.clear(); - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs, 30)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 30)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // There was not global base available so all buckets are invalid. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); -} - -static StatsLogReport outputStreamToProto(ProtoOutputStream* proto) { - vector<uint8_t> bytes; - bytes.resize(proto->size()); - size_t pos = 0; - sp<ProtoReader> reader = proto->data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } - - StatsLogReport report; - report.ParseFromArray(bytes.data(), bytes.size()); - return report; + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); } TEST(ValueMetricProducerTest, TestPullNeededFastDump) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -2843,40 +2928,35 @@ TEST(ValueMetricProducerTest, TestPullNeededFastDump) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) // Initial pull. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); return true; })); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); ProtoOutputStream output; std::set<string> strSet; - valueProducer.onDumpReport(bucketStartTimeNs + 10, - true /* include recent buckets */, true, + valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true, FAST, &strSet, &output); StatsLogReport report = outputStreamToProto(&output); // Bucket is invalid since we did not pull when dump report was called. - EXPECT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(0, report.value_metrics().data_size()); } TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -2886,51 +2966,41 @@ TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _, _)) // Initial pull. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); return true; })); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); vector<shared_ptr<LogEvent>> allData; allData.clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); - event->write(tagId); - event->write(2); - event->write(2); - event->init(); - allData.push_back(event); + allData.push_back(CreateThreeValueLogEvent(tagId, bucket2StartTimeNs + 1, tagId, 2, 2)); valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); ProtoOutputStream output; std::set<string> strSet; - valueProducer.onDumpReport(bucket4StartTimeNs, - false /* include recent buckets */, true, - FAST, &strSet, &output); + valueProducer.onDumpReport(bucket4StartTimeNs, false /* include recent buckets */, true, FAST, + &strSet, &output); StatsLogReport report = outputStreamToProto(&output); // Previous bucket is part of the report. - EXPECT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().data_size()); EXPECT_EQ(0, report.value_metrics().data(0).bucket_info(0).bucket_num()); } TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; SimpleAtomMatcher atomMatcher; @@ -2940,46 +3010,40 @@ TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); - EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillRepeatedly(Return()); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // Initial pull. - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); - event->write(tagId); - event->write(1); - event->write(1); - event->init(); - data->push_back(event); + data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs, tagId, 1, 1)); return true; })) - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); data->clear(); - shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); - event->write(tagId); - event->write(3); - event->write(3); - event->init(); - data->push_back(event); + data->push_back( + CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10, tagId, 3, 3)); return true; })); - ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, + ValueMetricProducer valueProducer(kConfigKey, metric, -1, {}, wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); valueProducer.prepareFirstBucket(); ProtoOutputStream output; std::set<string> strSet; - valueProducer.onDumpReport(bucketStartTimeNs + 10, - true /* include recent buckets */, true, + valueProducer.onDumpReport(bucketStartTimeNs + 10, true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, &strSet, &output); StatsLogReport report = outputStreamToProto(&output); - EXPECT_EQ(1, report.value_metrics().data_size()); - EXPECT_EQ(1, report.value_metrics().data(0).bucket_info_size()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size()); EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); } @@ -2992,11 +3056,12 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withoutCondition) { ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 10)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 30); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges) { @@ -3004,44 +3069,48 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges metric.set_use_diff(false); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + 30, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); return true; })) // condition becomes false - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + 50, 20)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 20)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); valueProducer->onConditionChanged(false, bucketStartTimeNs + 50); // has one slice - EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(20, curInterval.value.long_value); - // Now the alarm is delivered. Condition is off though. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 110)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); } @@ -3050,29 +3119,31 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue) { metric.set_use_diff(false); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 8, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + 30, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); return true; })); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); // Now the alarm is delivered. Condition is off though. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 30)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; - EXPECT_EQ(false, curInterval.hasBase); + ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; + EXPECT_EQ(false, curBaseInfo.hasBase); EXPECT_EQ(false, curInterval.hasValue); } @@ -3082,16 +3153,16 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); // Now the alarm is delivered. Condition is off though. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 30)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Condition was always false. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); } TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) { @@ -3099,29 +3170,1887 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) { metric.set_use_diff(false); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); - EXPECT_CALL(*pullerManager, Pull(tagId, _)) + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) // condition becomes true - .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 8); data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent( - bucketStartTimeNs + 30, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30, 10)); return true; })) .WillOnce(Return(false)); sp<ValueMetricProducer> valueProducer = - ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kFalse; + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); valueProducer->onConditionChanged(false, bucketStartTimeNs + 50); // Now the alarm is delivered. Condition is off though. vector<shared_ptr<LogEvent>> allData; - allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 30)); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // No buckets, we had a failure. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); +} + +/* + * Test that DUMP_REPORT_REQUESTED dump reason is logged. + * + * For the bucket to be marked invalid during a dump report requested, + * three things must be true: + * - we want to include the current partial bucket + * - we need a pull (metric is pulled and condition is true) + * - the dump latency must be FAST + */ + +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenDumpReportRequested) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 20, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20, 10)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 20); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 40, true /* include recent buckets */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), dropEvent.drop_time_millis()); +} + +/* + * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late condition + * change event (i.e. the condition change occurs in the wrong bucket). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionEventWrongBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 50, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Bucket boundary pull. + vector<shared_ptr<LogEvent>> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + // Late condition change event. + valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), dropEvent.drop_time_millis()); +} + +/* + * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late accumulate + * event (i.e. the accumulate events call occurs in the wrong bucket). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenAccumulateEventWrongBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 100); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 100, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Bucket boundary pull. + vector<shared_ptr<LogEvent>> allData; + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 15)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs - 100, 20)); + + // Late accumulateEvents event. + valueProducer->accumulateEvents(allData, bucket2StartTimeNs - 100, bucket2StartTimeNs - 100); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); +} + +/* + * Test that CONDITION_UNKNOWN dump reason is logged due to an unknown condition + * when a metric is initialized. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionUnknown) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10000); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 100, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that PULL_FAILED dump reason is logged due to a pull failure in + * #pullAndMatchEventsLocked. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenPullFailed) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 10)); + return true; + })) + // Dump report requested, pull fails. + .WillOnce(Return(false)); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that MULTIPLE_BUCKETS_SKIPPED dump reason is logged when a log event + * skips over more than one bucket. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenMultipleBucketsSkipped) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket4StartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucket4StartTimeNs + 1000, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // Condition change event that skips forward by three buckets. + valueProducer->onConditionChanged(false, bucket4StartTimeNs + 10); + + int64_t dumpTimeNs = bucket4StartTimeNs + 1000; + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(dumpTimeNs, true /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(2, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket4StartTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::MULTIPLE_BUCKETS_SKIPPED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket4StartTimeNs + 10), dropEvent.drop_time_millis()); + + // This bucket is skipped because a dumpReport with include current buckets is called. + // This creates a new bucket from bucket4StartTimeNs to dumpTimeNs in which we have no data + // since the condition is false for the entire bucket interval. + EXPECT_EQ(NanoToMillis(bucket4StartTimeNs), + report.value_metrics().skipped(1).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpTimeNs), + report.value_metrics().skipped(1).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); + + dropEvent = report.value_metrics().skipped(1).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size + * is smaller than the "min_bucket_size_nanos" specified in the metric config. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + metric.set_min_bucket_size_nanos(10000000000); // 10 seconds + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 9000000); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 9000000, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 9000000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that NO_DATA dump reason is logged when a flushed bucket contains no data. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kFalse); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that all buckets are dropped due to condition unknown until the first onConditionChanged. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestConditionUnknownMultipleBuckets) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 15 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 15 * NS_PER_SEC, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Bucket should be dropped because of condition unknown. + int64_t appUpgradeTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC; + valueProducer->notifyAppUpgrade(appUpgradeTimeNs); + + // Bucket also dropped due to condition unknown + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 3)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // This bucket is also dropped due to condition unknown. + int64_t conditionChangeTimeNs = bucket2StartTimeNs + 10 * NS_PER_SEC; + valueProducer->onConditionChanged(true, conditionChangeTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; // 15 seconds + valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(3, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(1).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(1).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); + + dropEvent = report.value_metrics().skipped(1).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(2).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(2).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(2).drop_event_size()); + + dropEvent = report.value_metrics().skipped(2).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(conditionChangeTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that a skipped bucket is logged when a forced bucket split occurs when the previous bucket + * was not flushed in time. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenForceBucketSplitBeforeBucketFlush) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })) + // App Update. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 1000); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1000, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric, + ConditionState::kFalse); + + // Condition changed event + int64_t conditionChangeTimeNs = bucketStartTimeNs + 10; + valueProducer->onConditionChanged(true, conditionChangeTimeNs); + + // App update event. + int64_t appUpdateTimeNs = bucket2StartTimeNs + 1000; + valueProducer->notifyAppUpgrade(appUpdateTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucket2StartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(1, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + ASSERT_EQ(1, report.value_metrics().data(0).bucket_info_size()); + auto data = report.value_metrics().data(0); + ASSERT_EQ(0, data.bucket_info(0).bucket_num()); + EXPECT_EQ(5, data.bucket_info(0).values(0).value_long()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test multiple bucket drop events in the same bucket. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestMultipleBucketDropEvents) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs + 10, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t, + vector<std::shared_ptr<LogEvent>>* data, bool) { + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 10)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Condition change event. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 1000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include recent buckets */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); +} + +/* + * Test that the number of logged bucket drop events is capped at the maximum. + * The maximum is currently 10 and is set in MetricProducer::maxDropEventsReached(). + */ +TEST(ValueMetricProducerTest_BucketDrop, TestMaxBucketDropEvents) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // First condition change event. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + for (int i = 0; i < 2000; i++) { + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, i)); + } + return true; + })) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 220); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 220, 10)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // First condition change event causes guardrail to be reached. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 10); + + // 2-10 condition change events result in failed pulls. + valueProducer->onConditionChanged(false, bucketStartTimeNs + 30); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 50); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 70); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 90); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 100); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 150); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 170); + valueProducer->onConditionChanged(true, bucketStartTimeNs + 190); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 200); + + // Condition change event 11 + valueProducer->onConditionChanged(true, bucketStartTimeNs + 220); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 1000; + // Because we already have 10 dump events in the current bucket, + // this case should not be added to the list of dump events. + valueProducer->onDumpReport(bucketStartTimeNs + 1000, true /* include recent buckets */, true, + FAST /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(10, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 30), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(2); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 50), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(3); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 70), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(4); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 90), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(5); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 100), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(6); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 150), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(7); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 170), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(8); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 190), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(9); + EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 200), dropEvent.drop_time_millis()); +} + +/* + * Test metric with a simple sliced state + * - Increasing values + * - Using diff + * - Second field is value field + */ +TEST(ValueMetricProducerTest, TestSlicedState) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Screen state change to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5)); + return true; + })) + // Screen state change to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 9)); + return true; + })) + // Screen state change to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::SCREEN_STATE_CHANGED}, {}); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(3, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after screen state change kStateUnknown->ON. + auto screenEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change ON->OFF. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(9, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(4, it->second[0].value.long_value); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change OFF->ON. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(21, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, OFF} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(12, it->second[0].value.long_value); + // Value for dimension, state key {{}, ON} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(4, it->second[0].value.long_value); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(3, report.value_metrics().data_size()); + + auto data = report.value_metrics().data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value()); + + data = report.value_metrics().data(1); + ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); + EXPECT_EQ(13, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value()); + + data = report.value_metrics().data(2); + ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); + EXPECT_EQ(12, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value()); +} + +/* + * Test metric with sliced state with map + * - Increasing values + * - Using diff + * - Second field is value field + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("SCREEN_STATE_ONOFF"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3)); + return true; + })) + // Screen state change to ON. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5)); + return true; + })) + // Screen state change to VR has no pull because it is in the same + // state group as ON. + + // Screen state change to ON has no pull because it is in the same + // state group as VR. + + // Screen state change to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30)); + return true; + })); + + const StateMap& stateMap = + CreateScreenStateOnOffMap(/*screen on id=*/321, /*screen off id=*/123); + const StateMap_StateGroup screenOnGroup = stateMap.group(0); + const StateMap_StateGroup screenOffGroup = stateMap.group(1); + + unordered_map<int, unordered_map<int, int64_t>> stateGroupMap; + for (auto group : stateMap.group()) { + for (auto value : group.value()) { + stateGroupMap[SCREEN_STATE_ATOM_ID][value] = group.group_id(); + } + } + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {util::SCREEN_STATE_CHANGED}, stateGroupMap); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); + + // Bucket status after metric initialized. + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(3, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, {kStateUnknown}} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after screen state change kStateUnknown->ON. + auto screenEvent = CreateScreenStateChangedEvent( + bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + itBase->second[0].currentState.getValues()[0].mValue.long_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change ON->VR. + // Both ON and VR are in the same state group, so the base should not change. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10, + android::view::DisplayStateEnum::DISPLAY_STATE_VR); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change VR->ON. + // Both ON and VR are in the same state group, so the base should not change. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12, + android::view::DisplayStateEnum::DISPLAY_STATE_ON); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, kStateUnknown} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Bucket status after screen state change VR->OFF. + screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15, + android::view::DisplayStateEnum::DISPLAY_STATE_OFF); + StateManager::getInstance().onLogEvent(*screenEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(21, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(screenOffGroup.group_id(), + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{}, ON GROUP} + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(screenOnGroup.group_id(), + it->first.getStateValuesKey().getValues()[0].mValue.long_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(16, it->second[0].value.long_value); + // Value for dimension, state key {{}, kStateUnknown} + it++; + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(3, report.value_metrics().data_size()); + + auto data = report.value_metrics().data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + + data = report.value_metrics().data(1); + ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); + EXPECT_EQ(16, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOnGroup.group_id(), data.slice_by_state(0).group_id()); + + data = report.value_metrics().data(2); + ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size()); + EXPECT_EQ(9, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); + EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_group_id()); + EXPECT_EQ(screenOffGroup.group_id(), data.slice_by_state(0).group_id()); +} + +/* + * Test metric that slices by state with a primary field and has dimensions + * - Increasing values + * - Using diff + * - Second field is value field + */ +TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithState("UID_PROCESS_STATE"); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + + MetricStateLink* stateLink = metric.add_state_link(); + stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID); + auto fieldsInWhat = stateLink->mutable_fields_in_what(); + *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */}); + auto fieldsInState = stateLink->mutable_fields_in_state(); + *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */}); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // ValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs); + data->clear(); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 2 /*uid*/, 7)); + data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs, 1 /*uid*/, 3)); + return true; + })) + // Uid 1 process state change from kStateUnknown -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 1 /*uid*/, 6)); + + // This event should be skipped. + data->push_back( + CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 2 /*uid*/, 8)); + return true; + })) + // Uid 2 process state change from kStateUnknown -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 2 /*uid*/, 9)); + + // This event should be skipped. + data->push_back( + CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 1 /*uid*/, 12)); + return true; + })) + // Uid 1 process state change from Foreground -> Background + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 1 /*uid*/, 13)); + + // This event should be skipped. + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 2 /*uid*/, 11)); + return true; + })) + // Uid 1 process state change from Background -> Foreground + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 1 /*uid*/, 17)); + + // This event should be skipped. + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 2 /*uid */, 15)); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50); + data->clear(); + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 2 /*uid*/, 20)); + data->push_back( + CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 1 /*uid*/, 21)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithState( + pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); + + // Bucket status after metric initialized. + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 1}. + auto it = valueProducer->mCurrentSlicedBucket.begin(); + auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(3, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{uid 1}, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + // Base for dimension key {uid 2} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(7, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for dimension, state key {{uid 2}, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after uid 1 process state change kStateUnknown -> Foreground. + auto uidProcessEvent = CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 1}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(6, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(3, it->second[0].value.long_value); + + // Base for dimension key {uid 2} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(7, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after uid 2 process state change kStateUnknown -> Background. + uidProcessEvent = CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 40, 2 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {uid 1}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(6, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(3, it->second[0].value.long_value); + + // Base for dimension key {uid 2} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(9, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Pull at end of first bucket. + vector<shared_ptr<LogEvent>> allData; + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 10)); + allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 15)); + valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1); + + // Buckets flushed after end of first bucket. + // None of the buckets should have a value. + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension key {uid 2}. + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(15, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Base for dimension key {uid 1} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(10, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* kStateTracker::kUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Value for key {uid 1, FOREGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* kStateTracker::kUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after uid 1 process state change from Foreground -> Background. + uidProcessEvent = CreateUidProcessStateChangedEvent( + bucket2StartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + + ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(4UL, valueProducer->mPastBuckets.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension key {uid 2}. + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(15, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND}. + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + // Base for dimension key {uid 1} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(13, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + // Value for key {uid 1, FOREGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(3, it->second[0].value.long_value); + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after uid 1 process state change Background->Foreground. + uidProcessEvent = CreateUidProcessStateChangedEvent( + bucket2StartTimeNs + 40, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND); + StateManager::getInstance().onLogEvent(*uidProcessEvent); + + ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size()); + ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size()); + // Base for dimension key {uid 2} + it = valueProducer->mCurrentSlicedBucket.begin(); + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(15, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 2, BACKGROUND} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Base for dimension key {uid 1} + it++; + itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat()); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(17, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {uid 1, kStateUnknown} + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Value for key {uid 1, BACKGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(4, it->second[0].value.long_value); + + // Value for key {uid 1, FOREGROUND} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(3, it->second[0].value.long_value); + + // Value for key {uid 2, kStateUnknown} + it++; + ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size()); + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50, true /* include recent buckets */, true, + NO_TIME_CONSTRAINTS, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(5, report.value_metrics().data_size()); + + auto data = report.value_metrics().data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(4, report.value_metrics().data(0).bucket_info(0).values(0).value_long()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + + data = report.value_metrics().data(1); + ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size()); + EXPECT_EQ(2, report.value_metrics().data(1).bucket_info(0).values(0).value_long()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + + data = report.value_metrics().data(2); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, report.value_metrics().data(2).bucket_info_size()); + EXPECT_EQ(4, report.value_metrics().data(2).bucket_info(0).values(0).value_long()); + EXPECT_EQ(7, report.value_metrics().data(2).bucket_info(1).values(0).value_long()); + + data = report.value_metrics().data(3); + ASSERT_EQ(1, report.value_metrics().data(3).bucket_info_size()); + EXPECT_EQ(3, report.value_metrics().data(3).bucket_info(0).values(0).value_long()); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value()); + + data = report.value_metrics().data(4); + EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND, + data.slice_by_state(0).value()); + ASSERT_EQ(2, report.value_metrics().data(4).bucket_info_size()); + EXPECT_EQ(6, report.value_metrics().data(4).bucket_info(0).values(0).value_long()); + EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long()); +} + +TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( + "BATTERY_SAVER_MODE_STATE"); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition changed to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 3)); + return true; + })) + // Battery saver mode state changed to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, + ConditionState::kFalse); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after battery saver mode ON event. + // Condition is false so we do nothing. + unique_ptr<LogEvent> batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + + // Bucket status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + std::unordered_map<HashableDimensionKey, std::vector<ValueMetricProducer::BaseInfo>>::iterator + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(3, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {{}, -1} + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + std::unordered_map<MetricDimensionKey, std::vector<ValueMetricProducer::Interval>>::iterator + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after battery saver mode OFF event. + unique_ptr<LogEvent> batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Pull at end of first bucket. + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 11)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(11, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + + // Bucket 2 status after condition change to false. + valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_FALSE(itBase->second[0].hasBase); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(4, it->second[0].value.long_value); + + // Start dump report and check output. + ProtoOutputStream output; + std::set<string> strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(2, report.value_metrics().data_size()); + + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).values(0).value_long()); + + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(6, data.bucket_info(0).values(0).value_long()); + EXPECT_EQ(4, data.bucket_info(1).values(0).value_long()); +} + +/* + * Test bucket splits when condition is unknown. + */ +TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, + ConditionState::kUnknown); + + // App update event. + int64_t appUpdateTimeNs = bucketStartTimeNs + 1000; + valueProducer->notifyAppUpgrade(appUpdateTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); } } // namespace statsd |