optional default base for ValueMetric

For pulled atoms, if use_zero_default_base is set to true, and if use_diff, the first data
piece will assume 0 value as base.
This requires a successful previous pull to set mHasGlobalBase.
mHasGlobalBase is reset when condition changes to false or we skip more
than 1 bucket.

Bug: 120476027
Bug: 120129928
Test: unit test
Change-Id: Id01a7bf8796394777f02ba2c9bfdc860f528b98f
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 44aa00b..1bd34f5 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -1468,6 +1468,219 @@
     EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value);
 }
 
+/*
+ * Tests pulled atoms with no conditions
+ */
+TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) {
+    ValueMetric metric;
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.mutable_value_field()->set_field(tagId);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_dimensions_in_what()->set_field(tagId);
+    metric.mutable_dimensions_in_what()->add_child()->set_field(1);
+    metric.set_use_zero_default_base(true);
+
+    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>();
+    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, int64_t timeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
+                event->write(1);
+                event->write(3);
+                event->init();
+                data->push_back(event);
+                return true;
+            }));
+
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+                                      logEventMatcherIndex, eventMatcherWizard, tagId,
+                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+
+    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);
+    EXPECT_EQ(false, interval1.hasValue);
+    EXPECT_EQ(true, valueProducer.mHasGlobalBase);
+    EXPECT_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);
+
+    valueProducer.onDataPulled(allData);
+    EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, interval1.hasBase);
+    EXPECT_EQ(11, interval1.base.long_value);
+    EXPECT_EQ(true, interval1.hasValue);
+    EXPECT_EQ(8, interval1.value.long_value);
+
+    auto it = valueProducer.mCurrentSlicedBucket.begin();
+    for (; it != valueProducer.mCurrentSlicedBucket.end(); it++) {
+        if (it != iter) {
+            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_EQ(true, interval2.hasValue);
+    EXPECT_EQ(4, interval2.value.long_value);
+    EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
+}
+
+/*
+ * Tests pulled atoms with no conditions
+ */
+TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) {
+    ValueMetric metric;
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.mutable_value_field()->set_field(tagId);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_dimensions_in_what()->set_field(tagId);
+    metric.mutable_dimensions_in_what()->add_child()->set_field(1);
+    metric.set_use_zero_default_base(true);
+
+    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>();
+    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, int64_t timeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
+                event->write(1);
+                event->write(3);
+                event->init();
+                data->push_back(event);
+                return true;
+            }));
+
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+                                      logEventMatcherIndex, eventMatcherWizard, tagId,
+                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+
+    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);
+    EXPECT_EQ(false, interval1.hasValue);
+    EXPECT_EQ(true, valueProducer.mHasGlobalBase);
+    EXPECT_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);
+
+    valueProducer.onDataPulled(allData);
+    EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, interval1.hasBase);
+    EXPECT_EQ(11, interval1.base.long_value);
+    EXPECT_EQ(true, interval1.hasValue);
+    EXPECT_EQ(8, interval1.value.long_value);
+
+    auto it = valueProducer.mCurrentSlicedBucket.begin();
+    for (; it != valueProducer.mCurrentSlicedBucket.end(); it++) {
+        if (it != iter) {
+            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_EQ(true, interval2.hasValue);
+    EXPECT_EQ(4, interval2.value.long_value);
+    EXPECT_EQ(0UL, 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);
+    valueProducer.onDataPulled(allData);
+
+    EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, interval2.hasBase);
+    EXPECT_EQ(5, interval2.base.long_value);
+    EXPECT_EQ(false, interval2.hasValue);
+    EXPECT_EQ(false, interval1.hasBase);
+    EXPECT_EQ(false, interval1.hasValue);
+    EXPECT_EQ(true, valueProducer.mHasGlobalBase);
+    EXPECT_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);
+    valueProducer.onDataPulled(allData);
+
+    EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size());
+    EXPECT_EQ(true, interval2.hasBase);
+    EXPECT_EQ(13, interval2.base.long_value);
+    EXPECT_EQ(true, interval2.hasValue);
+    EXPECT_EQ(8, interval2.value.long_value);
+    EXPECT_EQ(true, interval1.hasBase);
+    EXPECT_EQ(5, interval1.base.long_value);
+    EXPECT_EQ(true, interval1.hasValue);
+    EXPECT_EQ(5, interval1.value.long_value);
+    EXPECT_EQ(true, valueProducer.mHasGlobalBase);
+    EXPECT_EQ(2UL, valueProducer.mPastBuckets.size());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android