diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 017cb6d9221ec..15d248f06c7ca 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -235,6 +235,7 @@ cc_test { "tests/condition/CombinationConditionTracker_test.cpp", "tests/condition/SimpleConditionTracker_test.cpp", "tests/condition/StateTracker_test.cpp", + "tests/condition/ConditionTimer_test.cpp", "tests/metrics/OringDurationTracker_test.cpp", "tests/metrics/MaxDurationTracker_test.cpp", "tests/metrics/CountMetricProducer_test.cpp", diff --git a/cmds/statsd/src/condition/ConditionTimer.h b/cmds/statsd/src/condition/ConditionTimer.h new file mode 100644 index 0000000000000..442bc11934fe3 --- /dev/null +++ b/cmds/statsd/src/condition/ConditionTimer.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +namespace android { +namespace os { +namespace statsd { + +/** + * A simple stopwatch to time the duration of condition being true. + * + * The owner of the stopwatch (MetricProducer) is responsible to notify the stopwatch when condition + * changes (start/pause), and when to start a new bucket (a new lap basically). All timestamps + * should be elapsedRealTime in nano seconds. + * + * Keep the timer simple and inline everything. This class is *NOT* thread safe. Caller is + * responsible for thread safety. + */ +class ConditionTimer { +public: + explicit ConditionTimer(bool initCondition, int64_t bucketStartNs) : mCondition(initCondition) { + if (initCondition) { + mLastConditionTrueTimestampNs = bucketStartNs; + } + }; + + // Tracks how long the condition has been stayed true in the *current* bucket. + // When a new bucket is created, this value will be reset to 0. + int64_t mTimerNs = 0; + + // Last elapsed real timestamp when condition turned to true + // When a new bucket is created and the condition is true, then the timestamp is set + // to be the bucket start timestamp. + int64_t mLastConditionTrueTimestampNs = 0; + + bool mCondition = false; + + int64_t newBucketStart(int64_t nextBucketStartNs) { + if (mCondition) { + mTimerNs += (nextBucketStartNs - mLastConditionTrueTimestampNs); + mLastConditionTrueTimestampNs = nextBucketStartNs; + } + + int64_t temp = mTimerNs; + mTimerNs = 0; + return temp; + } + + void onConditionChanged(bool newCondition, int64_t timestampNs) { + if (newCondition == mCondition) { + return; + } + mCondition = newCondition; + if (newCondition) { + mLastConditionTrueTimestampNs = timestampNs; + } else { + mTimerNs += (timestampNs - mLastConditionTrueTimestampNs); + } + } + + FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_False); + FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_True); +}; + +} // namespace statsd +} // namespace os +} // namespace android \ No newline at end of file diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 18bfdfc463990..90a4e8b900516 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -72,6 +72,7 @@ const int FIELD_ID_VALUES = 9; const int FIELD_ID_BUCKET_NUM = 4; const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; +const int FIELD_ID_CONDITION_TRUE_NS = 10; const Value ZERO_LONG((int64_t)0); const Value ZERO_DOUBLE((int64_t)0); @@ -107,7 +108,8 @@ ValueMetricProducer::ValueMetricProducer( mCurrentBucketIsInvalid(false), mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC : StatsdStats::kPullMaxDelayNs), - mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()) { + mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()), + mConditionTimer(mCondition == ConditionState::kTrue, timeBaseNs) { int64_t bucketSizeMills = 0; if (metric.has_bucket()) { bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()); @@ -153,6 +155,7 @@ ValueMetricProducer::ValueMetricProducer( // flushIfNeeded to adjust start and end to bucket boundaries. // Adjust start for partial bucket mCurrentBucketStartTimeNs = startTimeNs; + mConditionTimer.newBucketStart(mCurrentBucketStartTimeNs); // Kicks off the puller immediately if condition is true and diff based. if (mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) { pullAndMatchEventsLocked(startTimeNs, mCondition); @@ -293,6 +296,11 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM, (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs))); } + // only write the condition timer value if the metric has a condition. + if (mConditionTrackerIndex >= 0) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS, + (long long)bucket.mConditionTrueNs); + } for (int i = 0; i < (int)bucket.valueIndex.size(); i ++) { int index = bucket.valueIndex[i]; const Value& value = bucket.values[i]; @@ -386,19 +394,19 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, resetBase(); } mCondition = newCondition; - } else { VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, (long long)mCurrentBucketStartTimeNs); StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId); invalidateCurrentBucket(); - // Something weird happened. If we received another event if the future, the condition might + // Something weird happened. If we received another event in the future, the condition might // be wrong. mCondition = initialCondition(mConditionTrackerIndex); } // This part should alway be called. flushIfNeededLocked(eventTimeNs); + mConditionTimer.onConditionChanged(mCondition, eventTimeNs); } void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs, ConditionState condition) { @@ -799,12 +807,14 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, (int)mCurrentSlicedBucket.size()); int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs; - + // Close the current bucket. + int64_t conditionTrueDuration = mConditionTimer.newBucketStart(bucketEndTime); bool isBucketLargeEnough = bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs; if (isBucketLargeEnough && !mCurrentBucketIsInvalid) { // The current bucket is large enough to keep. for (const auto& slice : mCurrentSlicedBucket) { ValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second); + bucket.mConditionTrueNs = conditionTrueDuration; // it will auto create new vector of ValuebucketInfo if the key is not found. if (bucket.valueIndex.size() > 0) { auto& bucketList = mPastBuckets[slice.first]; @@ -817,6 +827,8 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, appendToFullBucket(eventTimeNs, fullBucketEndTimeNs); initCurrentSlicedBucket(nextBucketStartTimeNs); + // Update the condition timer again, in case we skipped buckets. + mConditionTimer.newBucketStart(nextBucketStartTimeNs); mCurrentBucketNum += numBucketsForward; } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 12cec5d6dbec7..0f5633732db9a 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -19,12 +19,13 @@ #include #include #include -#include "../anomaly/AnomalyTracker.h" -#include "../condition/ConditionTracker.h" -#include "../external/PullDataReceiver.h" -#include "../external/StatsPullerManager.h" -#include "../matchers/EventMatcherWizard.h" -#include "../stats_log_util.h" +#include "anomaly/AnomalyTracker.h" +#include "condition/ConditionTimer.h" +#include "condition/ConditionTracker.h" +#include "external/PullDataReceiver.h" +#include "external/StatsPullerManager.h" +#include "matchers/EventMatcherWizard.h" +#include "stats_log_util.h" #include "MetricProducer.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" @@ -37,6 +38,9 @@ struct ValueBucket { int64_t mBucketEndNs; std::vector valueIndex; std::vector values; + // If the metric has no condition, then this field is just wasted. + // When we tune statsd memory usage in the future, this is a candidate to optimize. + int64_t mConditionTrueNs; }; @@ -228,6 +232,8 @@ private: const bool mSplitBucketForAppUpgrade; + ConditionTimer mConditionTimer; + FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection); FRIEND_TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange); FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 1dfc433cf0e2d..54ca757f5f611 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -129,6 +129,8 @@ message ValueBucketInfo { optional int64 start_bucket_elapsed_millis = 5; optional int64 end_bucket_elapsed_millis = 6; + + optional int64 condition_true_nanos = 10; } message ValueMetricData { diff --git a/cmds/statsd/tests/condition/ConditionTimer_test.cpp b/cmds/statsd/tests/condition/ConditionTimer_test.cpp new file mode 100644 index 0000000000000..ea02cd3a5ee1f --- /dev/null +++ b/cmds/statsd/tests/condition/ConditionTimer_test.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/condition/ConditionTimer.h" + +#include +#include + +#ifdef __ANDROID__ + +namespace android { +namespace os { +namespace statsd { + +static int64_t time_base = 10; +static int64_t ct_start_time = 200; + +TEST(ConditionTimerTest, TestTimer_Inital_False) { + ConditionTimer timer(false, time_base); + EXPECT_EQ(false, timer.mCondition); + EXPECT_EQ(0, timer.mTimerNs); + + EXPECT_EQ(0, timer.newBucketStart(ct_start_time)); + EXPECT_EQ(0, timer.mTimerNs); + + timer.onConditionChanged(true, ct_start_time + 5); + EXPECT_EQ(ct_start_time + 5, timer.mLastConditionTrueTimestampNs); + EXPECT_EQ(true, timer.mCondition); + + EXPECT_EQ(95, timer.newBucketStart(ct_start_time + 100)); + EXPECT_EQ(ct_start_time + 100, timer.mLastConditionTrueTimestampNs); + EXPECT_EQ(true, timer.mCondition); +} + +TEST(ConditionTimerTest, TestTimer_Inital_True) { + ConditionTimer timer(true, time_base); + EXPECT_EQ(true, timer.mCondition); + EXPECT_EQ(0, timer.mTimerNs); + + EXPECT_EQ(ct_start_time - time_base, timer.newBucketStart(ct_start_time)); + EXPECT_EQ(true, timer.mCondition); + EXPECT_EQ(0, timer.mTimerNs); + EXPECT_EQ(ct_start_time, timer.mLastConditionTrueTimestampNs); + + timer.onConditionChanged(false, ct_start_time + 5); + EXPECT_EQ(5, timer.mTimerNs); + + EXPECT_EQ(5, timer.newBucketStart(ct_start_time + 100)); + EXPECT_EQ(0, timer.mTimerNs); +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index afa05a93c55a9..c12a59003bab6 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -55,8 +55,11 @@ double epsilon = 0.001; static void assertPastBucketValuesSingleKey( const std::unordered_map>& mPastBuckets, - const std::initializer_list& expectedValuesList) { + const std::initializer_list& expectedValuesList, + const std::initializer_list& expectedDurationNsList) { std::vector expectedValues(expectedValuesList); + std::vector expectedDurationNs(expectedDurationNsList); + ASSERT_EQ(expectedValues.size(), expectedDurationNs.size()); if (expectedValues.size() == 0) { ASSERT_EQ(0, mPastBuckets.size()); return; @@ -69,10 +72,11 @@ static void assertPastBucketValuesSingleKey( 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; } } - class ValueMetricProducerTestHelper { public: @@ -237,6 +241,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(8, curInterval.value.long_value); EXPECT_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(tagId, bucket3StartTimeNs + 1); @@ -256,7 +261,9 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_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(tagId, bucket4StartTimeNs + 1); @@ -275,8 +282,11 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_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); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[1].mConditionTrueNs); EXPECT_EQ(13, valueProducer->mPastBuckets.begin()->second[2].values[0].long_value); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[2].mConditionTrueNs); } TEST(ValueMetricProducerTest, TestPartialBucketCreated) { @@ -326,8 +336,11 @@ TEST(ValueMetricProducerTest, TestPartialBucketCreated) { 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); } /* @@ -385,6 +398,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { EXPECT_EQ(8, curInterval.value.long_value); EXPECT_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(tagId, bucket3StartTimeNs + 1); @@ -402,6 +416,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { EXPECT_EQ(8, curInterval.value.long_value); EXPECT_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(tagId, bucket4StartTimeNs + 1); @@ -420,6 +435,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_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); } /* @@ -468,6 +484,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { EXPECT_EQ(10, curInterval.value.long_value); EXPECT_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(tagId, bucket4StartTimeNs + 1); @@ -485,14 +502,16 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); EXPECT_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); + EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[1].mConditionTrueNs); } /* * Tests pulled atoms with no conditions and take zero value after reset */ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp pullerManager = new StrictMock(); EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(false)); sp valueProducer = @@ -546,6 +565,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { EXPECT_EQ(26, curInterval.value.long_value); EXPECT_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); } /* @@ -574,6 +594,15 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { event->init(); data->push_back(event); return true; + })) + .WillOnce(Invoke([](int tagId, vector>* data) { + data->clear(); + shared_ptr event = make_shared(tagId, bucket3StartTimeNs + 1); + event->write(tagId); + event->write(180); + event->init(); + data->push_back(event); + return true; })); sp valueProducer = @@ -598,7 +627,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { event->init(); allData.push_back(event); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}); // has one slice EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); @@ -609,7 +638,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { EXPECT_EQ(10, curInterval.value.long_value); valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}); // has one slice EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); @@ -617,6 +646,9 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(20, curInterval.value.long_value); EXPECT_EQ(false, curInterval.hasBase); + + valueProducer->onConditionChanged(true, bucket3StartTimeNs + 1); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1}); } TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { @@ -705,8 +737,7 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1); EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucket2StartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(20L, - valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].values[0].long_value); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}); allData.clear(); event = make_shared(tagId, bucket3StartTimeNs + 1); @@ -719,10 +750,12 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { 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}); } TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { - ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); metric.set_split_bucket_for_app_upgrade(false); UidMap uidMap; @@ -791,8 +824,10 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) { // 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(20L, valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].values[0].long_value); + EXPECT_EQ(bucketStartTimeNs, + valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, + {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)}); EXPECT_FALSE(valueProducer->mCondition); } @@ -835,7 +870,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { EXPECT_EQ(30, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs}); } TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { @@ -872,7 +907,8 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(20, curInterval.value.long_value); @@ -900,7 +936,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { EXPECT_EQ(50, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}); } TEST(ValueMetricProducerTest, TestAnomalyDetection) { @@ -1008,7 +1044,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // has one slice EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; // startUpdated:true sum:0 start:11 EXPECT_EQ(true, curInterval.hasBase); @@ -1031,7 +1068,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { EXPECT_EQ(true, curInterval.hasBase); EXPECT_EQ(23, curInterval.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}); // pull 3 come late. // The previous bucket gets closed with error. (Has start value 23, no ending) @@ -1050,7 +1087,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { EXPECT_EQ(true, curInterval.hasBase); EXPECT_EQ(36, curInterval.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}); } /* @@ -1089,7 +1126,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { // has one slice EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(true, curInterval.hasBase); EXPECT_EQ(100, curInterval.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -1098,7 +1136,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { // 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}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); EXPECT_EQ(false, curInterval.hasBase); // Now the alarm is delivered. @@ -1107,7 +1145,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 30, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(false, curInterval.hasBase); EXPECT_EQ(false, curInterval.hasValue); @@ -1160,7 +1198,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { // has one slice EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::Interval curInterval = + valueProducer->mCurrentSlicedBucket.begin()->second[0]; // startUpdated:false sum:0 start:100 EXPECT_EQ(true, curInterval.hasBase); EXPECT_EQ(100, curInterval.base.long_value); @@ -1169,7 +1208,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { // pull on bucket boundary come late, condition change happens before it valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(false, curInterval.hasBase); @@ -1177,7 +1216,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { // condition changed to true again, before the pull alarm is delivered valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(true, curInterval.hasBase); EXPECT_EQ(130, curInterval.base.long_value); @@ -1194,12 +1233,13 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { EXPECT_EQ(140, curInterval.base.long_value); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(10, curInterval.value.long_value); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); allData.clear(); allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs, 160)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20, 30}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20, 30}, + {bucketSizeNs - 8, bucketSizeNs - 24}); } TEST(ValueMetricProducerTest, TestPushedAggregateMin) { @@ -1230,7 +1270,8 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1242,7 +1283,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { EXPECT_EQ(10, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateMax) { @@ -1273,7 +1314,8 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1335,7 +1377,9 @@ TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { valueProducer.flushIfNeededLocked(bucket2StartTimeNs); EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); - EXPECT_TRUE(std::abs(valueProducer.mPastBuckets.begin()->second.back().values[0].double_value - 12.5) < epsilon); + + EXPECT_TRUE(std::abs(valueProducer.mPastBuckets.begin()->second.back().values[0].double_value - + 12.5) < epsilon); } TEST(ValueMetricProducerTest, TestPushedAggregateSum) { @@ -1366,7 +1410,8 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); @@ -1378,7 +1423,7 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { EXPECT_EQ(25, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs}); } TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { @@ -1410,7 +1455,8 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; + ValueMetricProducer::Interval curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(true, curInterval.hasBase); EXPECT_EQ(10, curInterval.base.long_value); EXPECT_EQ(false, curInterval.hasValue); @@ -1449,7 +1495,7 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { valueProducer.flushIfNeededLocked(bucket3StartTimeNs); EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); - EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs}); } TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { @@ -1546,11 +1592,13 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size()); EXPECT_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); EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].valueIndex[0]); EXPECT_EQ(2, valueProducer.mPastBuckets.begin()->second[0].values[1].long_value); EXPECT_EQ(1, valueProducer.mPastBuckets.begin()->second[0].valueIndex[1]); + EXPECT_EQ(bucketSizeNs, valueProducer.mPastBuckets.begin()->second[1].mConditionTrueNs); EXPECT_EQ(3, valueProducer.mPastBuckets.begin()->second[1].values[0].long_value); EXPECT_EQ(1, valueProducer.mPastBuckets.begin()->second[1].valueIndex[0]); } @@ -1625,8 +1673,10 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { EXPECT_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); iterator++; + EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); EXPECT_EQ(4, iterator->second[0].values[0].long_value); } @@ -1795,7 +1845,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(8, interval1.value.long_value); EXPECT_FALSE(interval1.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); auto it = valueProducer->mCurrentSlicedBucket.begin(); for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { @@ -1810,7 +1860,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { EXPECT_EQ(4, interval2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); // next pull somehow did not happen, skip to end of bucket 3 allData.clear(); @@ -1828,7 +1878,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { EXPECT_EQ(5, interval2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); allData.clear(); event1 = make_shared(tagId, bucket5StartTimeNs + 1); @@ -1846,8 +1896,10 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); auto iterator = valueProducer->mPastBuckets.begin(); EXPECT_EQ(9, iterator->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); iterator++; EXPECT_EQ(8, iterator->second[0].values[0].long_value); + EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); } TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfBucket) { @@ -1930,6 +1982,15 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { sp pullerManager = new StrictMock(); EXPECT_CALL(*pullerManager, Pull(tagId, _)) + .WillOnce(Invoke([](int tagId, vector>* data) { + data->clear(); + shared_ptr event = make_shared(tagId, bucketStartTimeNs); + event->write(tagId); + event->write(50); + event->init(); + data->push_back(event); + return false; + })) .WillOnce(Invoke([](int tagId, vector>* data) { data->clear(); shared_ptr event = make_shared(tagId, bucketStartTimeNs + 8); @@ -1943,10 +2004,11 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { sp valueProducer = ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric); - valueProducer->mCondition = ConditionState::kTrue; + // 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); - vector> allData; - valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs); EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); valueProducer->onConditionChanged(false, bucketStartTimeNs + 1); @@ -2406,7 +2468,7 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { EXPECT_EQ(true, valueProducer->mHasGlobalBase); EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - EXPECT_EQ(1, valueProducer->mPastBuckets.begin()->second[0].values[0].long_value); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1}); } TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { @@ -2539,13 +2601,15 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) { // Second onConditionChanged. .WillOnce(Invoke([](int tagId, vector>* data) { data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 10, 5)); + data->push_back( + ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 10, 5)); return true; })) // Third onConditionChanged. .WillOnce(Invoke([](int tagId, vector>* data) { data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs + 10, 7)); + data->push_back( + ValueMetricProducerTestHelper::createEvent(bucket3StartTimeNs + 10, 7)); return true; })); @@ -2572,7 +2636,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) { valueProducer->onConditionChanged(false, bucket3StartTimeNs + 10); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10}); } TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { @@ -2592,7 +2656,7 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs}); } TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { @@ -2619,7 +2683,7 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs}); } TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) { @@ -2636,7 +2700,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) { // notifyAppUpgrade. .WillOnce(Invoke([](int tagId, vector>* data) { data->clear(); - data->push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 2, 10)); + data->push_back( + ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 2, 10)); return true; })); @@ -2646,7 +2711,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) { valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs}); } TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { @@ -2678,6 +2743,12 @@ TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(2, curInterval.value.long_value); + + vector> allData; + allData.push_back(ValueMetricProducerTestHelper::createEvent(bucket2StartTimeNs + 1, 10)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); + + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}); } TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) { @@ -2724,7 +2795,7 @@ TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // There was not global base available so all buckets are invalid. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); } static StatsLogReport outputStreamToProto(ProtoOutputStream* proto) { @@ -2890,6 +2961,7 @@ TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) { EXPECT_EQ(1, report.value_metrics().data_size()); EXPECT_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()); + EXPECT_EQ(10, report.value_metrics().data(0).bucket_info(0).condition_true_nanos()); }