One more feature in DurationMetric -- nesting on top of Or and Max

+ Added counters to count the starts.

Test: added some unit test in statsd_test.

TODO: ADD MORE UNIT TESTS

Change-Id: I3575e921a4abab27c4ea50fffde19cc1d4564030
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index aaf3ec2..e875acb 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -63,6 +63,7 @@
 DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
                                                const int conditionIndex, const size_t startIndex,
                                                const size_t stopIndex, const size_t stopAllIndex,
+                                               const bool nesting,
                                                const sp<ConditionWizard>& wizard,
                                                const vector<KeyMatcher>& internalDimension,
                                                const uint64_t startTimeNs)
@@ -71,6 +72,7 @@
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
       mStopAllIndex(stopAllIndex),
+      mNested(nesting),
       mInternalDimension(internalDimension) {
     // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
     // them in the base class, because the proto generated CountMetric, and DurationMetric are
@@ -111,11 +113,11 @@
         vector<DurationBucket>& bucket) {
     switch (mMetric.type()) {
         case DurationMetric_AggregationType_DURATION_SUM:
-            return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex,
+            return make_unique<OringDurationTracker>(mWizard, mConditionTrackerIndex, mNested,
                                                      mCurrentBucketStartTimeNs, mBucketSizeNs,
                                                      bucket);
         case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
-            return make_unique<MaxDurationTracker>(mWizard, mConditionTrackerIndex,
+            return make_unique<MaxDurationTracker>(mWizard, mConditionTrackerIndex, mNested,
                                                    mCurrentBucketStartTimeNs, mBucketSizeNs,
                                                    bucket);
     }
@@ -268,7 +270,7 @@
     if (matcherIndex == mStartIndex) {
         it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
     } else if (matcherIndex == mStopIndex) {
-        it->second->noteStop(atomKey, event.GetTimestampNs());
+        it->second->noteStop(atomKey, event.GetTimestampNs(), false);
     }
 }
 
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index eea00454..bb5d4d9 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -39,7 +39,8 @@
 public:
     DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
                            const size_t startIndex, const size_t stopIndex,
-                           const size_t stopAllIndex, const sp<ConditionWizard>& wizard,
+                           const size_t stopAllIndex, const bool nesting,
+                           const sp<ConditionWizard>& wizard,
                            const vector<KeyMatcher>& internalDimension, const uint64_t startTimeNs);
 
     virtual ~DurationMetricProducer();
@@ -80,6 +81,9 @@
     // Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
     const size_t mStopAllIndex;
 
+    // nest counting -- for the same key, stops must match the number of starts to make real stop
+    const bool mNested;
+
     // The dimension from the atom predicate. e.g., uid, wakelock name.
     const vector<KeyMatcher> mInternalDimension;
 
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 5c76d0e..18b3349 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -34,6 +34,10 @@
 // Hold duration information for one atom level duration in current on-going bucket.
 struct DurationInfo {
     DurationState state;
+
+    // the number of starts seen.
+    int32_t startCount;
+
     // most recent start time.
     int64_t lastStartTime;
     // existing duration in current bucket.
@@ -42,7 +46,7 @@
     // cache the HashableDimensionKeys we need to query the condition for this duration event.
     ConditionKey conditionKeys;
 
-    DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
+    DurationInfo() : state(kStopped), startCount(0), lastStartTime(0), lastDuration(0){};
 };
 
 struct DurationBucket {
@@ -53,18 +57,21 @@
 
 class DurationTracker {
 public:
-    DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, uint64_t currentBucketStartNs,
-                    uint64_t bucketSizeNs, std::vector<DurationBucket>& bucket)
+    DurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
+                    uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                    std::vector<DurationBucket>& bucket)
         : mWizard(wizard),
           mConditionTrackerIndex(conditionIndex),
-          mCurrentBucketStartTimeNs(currentBucketStartNs),
           mBucketSizeNs(bucketSizeNs),
+          mNested(nesting),
+          mCurrentBucketStartTimeNs(currentBucketStartNs),
           mBucket(bucket),
           mDuration(0){};
     virtual ~DurationTracker(){};
     virtual void noteStart(const HashableDimensionKey& key, bool condition,
                            const uint64_t eventTime, const ConditionKey& conditionKey) = 0;
-    virtual void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) = 0;
+    virtual void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
+                          const bool stopAll) = 0;
     virtual void noteStopAll(const uint64_t eventTime) = 0;
     virtual void onSlicedConditionMayChange(const uint64_t timestamp) = 0;
     virtual void onConditionChanged(bool condition, const uint64_t timestamp) = 0;
@@ -75,12 +82,14 @@
 protected:
     sp<ConditionWizard> mWizard;
 
-    int mConditionTrackerIndex;
+    const int mConditionTrackerIndex;
+
+    const int64_t mBucketSizeNs;
+
+    const bool mNested;
 
     uint64_t mCurrentBucketStartTimeNs;
 
-    int64_t mBucketSizeNs;
-
     std::vector<DurationBucket>& mBucket;  // where to write output
 
     int64_t mDuration;  // current recorded duration result
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 43c21a8..8c7bfb6 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -23,10 +23,10 @@
 namespace os {
 namespace statsd {
 
-MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+MaxDurationTracker::MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                                        uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
                                        std::vector<DurationBucket>& bucket)
-    : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket) {
+    : DurationTracker(wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, bucket) {
 }
 
 void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
@@ -38,10 +38,10 @@
 
     switch (duration.state) {
         case kStarted:
-            // The same event is already started. Because we are not counting nesting, so ignore.
+            duration.startCount++;
             break;
         case kPaused:
-            // Safe to do nothing here. Paused means started but condition is false.
+            duration.startCount++;
             break;
         case kStopped:
             if (!condition) {
@@ -51,11 +51,13 @@
                 duration.state = DurationState::kStarted;
                 duration.lastStartTime = eventTime;
             }
+            duration.startCount = 1;
             break;
     }
 }
 
-void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
+void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
+                                  bool forceStop) {
     VLOG("MaxDuration: key %s stop", key.c_str());
     if (mInfos.find(key) == mInfos.end()) {
         // we didn't see a start event before. do nothing.
@@ -68,16 +70,23 @@
             // already stopped, do nothing.
             break;
         case DurationState::kStarted: {
-            duration.state = DurationState::kStopped;
-            int64_t durationTime = eventTime - duration.lastStartTime;
-            VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(), (long long)duration.lastStartTime,
-                 (long long)eventTime, (long long)durationTime);
-            duration.lastDuration = duration.lastDuration + durationTime;
-            VLOG("  record duration: %lld ", (long long)duration.lastDuration);
+            duration.startCount--;
+            if (forceStop || !mNested || duration.startCount <= 0) {
+                duration.state = DurationState::kStopped;
+                int64_t durationTime = eventTime - duration.lastStartTime;
+                VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(),
+                     (long long)duration.lastStartTime, (long long)eventTime,
+                     (long long)durationTime);
+                duration.lastDuration = duration.lastDuration + durationTime;
+                VLOG("  record duration: %lld ", (long long)duration.lastDuration);
+            }
             break;
         }
         case DurationState::kPaused: {
-            duration.state = DurationState::kStopped;
+            duration.startCount--;
+            if (forceStop || !mNested || duration.startCount <= 0) {
+                duration.state = DurationState::kStopped;
+            }
             break;
         }
     }
@@ -88,11 +97,13 @@
     }
     // Once an atom duration ends, we erase it. Next time, if we see another atom event with the
     // same name, they are still considered as different atom durations.
-    mInfos.erase(key);
+    if (duration.state == DurationState::kStopped) {
+        mInfos.erase(key);
+    }
 }
 void MaxDurationTracker::noteStopAll(const uint64_t eventTime) {
     for (auto& pair : mInfos) {
-        noteStop(pair.first, eventTime);
+        noteStop(pair.first, eventTime, true);
     }
 }
 
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index b095884..167f81e 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,12 +28,13 @@
 // they stop or bucket expires.
 class MaxDurationTracker : public DurationTracker {
 public:
-    MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+    MaxDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                        uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
                        std::vector<DurationBucket>& bucket);
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
-    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
+    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
+                  const bool stopAll) override;
     void noteStopAll(const uint64_t eventTime) override;
     bool flushIfNeeded(uint64_t timestampNs) override;
     void onSlicedConditionMayChange(const uint64_t timestamp) override;
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index e4f1d21..faf5ce5 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -20,10 +20,14 @@
 namespace android {
 namespace os {
 namespace statsd {
+
+using std::pair;
+
 OringDurationTracker::OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
-                                           uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
+                                           bool nesting, uint64_t currentBucketStartNs,
+                                           uint64_t bucketSizeNs,
                                            std::vector<DurationBucket>& bucket)
-    : DurationTracker(wizard, conditionIndex, currentBucketStartNs, bucketSizeNs, bucket),
+    : DurationTracker(wizard, conditionIndex, nesting, currentBucketStartNs, bucketSizeNs, bucket),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
@@ -36,9 +40,9 @@
             mLastStartTime = eventTime;
             VLOG("record first start....");
         }
-        mStarted.insert(key);
+        mStarted[key]++;
     } else {
-        mPaused.insert(key);
+        mPaused[key]++;
     }
 
     if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
@@ -48,11 +52,16 @@
     VLOG("Oring: %s start, condition %d", key.c_str(), condition);
 }
 
-void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp) {
+void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
+                                    const bool stopAll) {
     VLOG("Oring: %s stop", key.c_str());
     auto it = mStarted.find(key);
     if (it != mStarted.end()) {
-        mStarted.erase(it);
+        (it->second)--;
+        if (stopAll || !mNested || it->second <= 0) {
+            mStarted.erase(it);
+            mConditionKeyMap.erase(key);
+        }
         if (mStarted.empty()) {
             mDuration += (timestamp - mLastStartTime);
             VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
@@ -60,8 +69,14 @@
         }
     }
 
-    mPaused.erase(key);
-    mConditionKeyMap.erase(key);
+    auto pausedIt = mPaused.find(key);
+    if (pausedIt != mPaused.end()) {
+        (pausedIt->second)--;
+        if (stopAll || !mNested || pausedIt->second <= 0) {
+            mPaused.erase(pausedIt);
+            mConditionKeyMap.erase(key);
+        }
+        }
 }
 void OringDurationTracker::noteStopAll(const uint64_t timestamp) {
     if (!mStarted.empty()) {
@@ -118,11 +133,11 @@
 }
 
 void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) {
-    vector<HashableDimensionKey> startedToPaused;
-    vector<HashableDimensionKey> pausedToStarted;
+    vector<pair<HashableDimensionKey, int>> startedToPaused;
+    vector<pair<HashableDimensionKey, int>> pausedToStarted;
     if (!mStarted.empty()) {
         for (auto it = mStarted.begin(); it != mStarted.end();) {
-            auto key = *it;
+            const auto& key = it->first;
             if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
                 VLOG("Key %s dont have condition key", key.c_str());
                 ++it;
@@ -130,8 +145,8 @@
             }
             if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) !=
                 ConditionState::kTrue) {
+                startedToPaused.push_back(*it);
                 it = mStarted.erase(it);
-                startedToPaused.push_back(key);
                 VLOG("Key %s started -> paused", key.c_str());
             } else {
                 ++it;
@@ -147,7 +162,7 @@
 
     if (!mPaused.empty()) {
         for (auto it = mPaused.begin(); it != mPaused.end();) {
-            auto key = *it;
+            const auto& key = it->first;
             if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
                 VLOG("Key %s dont have condition key", key.c_str());
                 ++it;
@@ -155,8 +170,8 @@
             }
             if (mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key]) ==
                 ConditionState::kTrue) {
+                pausedToStarted.push_back(*it);
                 it = mPaused.erase(it);
-                pausedToStarted.push_back(key);
                 VLOG("Key %s paused -> started", key.c_str());
             } else {
                 ++it;
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index b54dafa..78760ba 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -27,12 +27,13 @@
 // Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
 class OringDurationTracker : public DurationTracker {
 public:
-    OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex,
+    OringDurationTracker(sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                          uint64_t currentBucketStartNs, uint64_t bucketSizeNs,
                          std::vector<DurationBucket>& bucket);
     void noteStart(const HashableDimensionKey& key, bool condition, const uint64_t eventTime,
                    const ConditionKey& conditionKey) override;
-    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime) override;
+    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
+                  const bool stopAll) override;
     void noteStopAll(const uint64_t eventTime) override;
     void onSlicedConditionMayChange(const uint64_t timestamp) override;
     void onConditionChanged(bool condition, const uint64_t timestamp) override;
@@ -44,8 +45,8 @@
     // 2) which keys are paused (started but condition was false)
     // 3) whenever a key stops, we remove it from the started set. And if the set becomes empty,
     //    it means everything has stopped, we then record the end time.
-    std::set<HashableDimensionKey> mStarted;
-    std::set<HashableDimensionKey> mPaused;
+    std::map<HashableDimensionKey, int> mStarted;
+    std::map<HashableDimensionKey, int> mPaused;
     int64_t mLastStartTime;
     std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
 };
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index c2044d8..ba9ea67 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -247,6 +247,8 @@
 
         const auto& simpleCondition = durationWhat.simple_condition();
 
+        bool nesting = simpleCondition.count_nesting();
+
         int trackerIndices[3] = {-1, -1, -1};
         if (!simpleCondition.has_start() ||
             !handleMetricWithLogTrackers(simpleCondition.start(), metricIndex,
@@ -284,7 +286,7 @@
 
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
                 metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2],
-                wizard, internalDimension, startTimeNs);
+                nesting, wizard, internalDimension, startTimeNs);
 
         allMetricProducers.push_back(durationMetric);
     }
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 58bf1b3..9cc184a 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -45,13 +45,13 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker(wizard, -1, false, bucketStartTimeNs, bucketSizeNs, buckets);
 
     tracker.noteStart("", true, bucketStartTimeNs, key1);
-    tracker.noteStop("", bucketStartTimeNs + 10);
+    tracker.noteStop("", bucketStartTimeNs + 10, false);
 
     tracker.noteStart("", true, bucketStartTimeNs + 20, key1);
-    tracker.noteStop("", bucketStartTimeNs + 40);
+    tracker.noteStop("", bucketStartTimeNs + 40, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
@@ -67,7 +67,7 @@
     uint64_t bucketStartTimeNs = 10000000000;
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
 
-    MaxDurationTracker tracker(wizard, -1, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker(wizard, -1, false, bucketStartTimeNs, bucketSizeNs, buckets);
 
     tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
     tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
@@ -77,6 +77,37 @@
     EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
 }
 
+TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    vector<DurationBucket> buckets;
+    ConditionKey key1;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    MaxDurationTracker tracker(wizard, -1, true, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    // 2 starts
+    tracker.noteStart("", true, bucketStartTimeNs + 1, key1);
+    tracker.noteStart("", true, bucketStartTimeNs + 10, key1);
+    // one stop
+    tracker.noteStop("", bucketStartTimeNs + 20, false /*stop all*/);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + (2 * bucketSizeNs) + 1);
+
+    EXPECT_EQ(2u, buckets.size());
+    EXPECT_EQ((long long)(bucketSizeNs - 1), buckets[0].mDuration);
+    EXPECT_EQ((long long)bucketSizeNs, buckets[1].mDuration);
+
+    // real stop now.
+    tracker.noteStop("", bucketStartTimeNs + (2 * bucketSizeNs) + 5, false);
+    tracker.flushIfNeeded(bucketStartTimeNs + (3 * bucketSizeNs) + 1);
+
+    EXPECT_EQ(3u, buckets.size());
+    EXPECT_EQ(5, buckets[2].mDuration);
+}
+
 TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -93,13 +124,13 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     int64_t durationTimeNs = 2 * 1000;
 
-    MaxDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+    MaxDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
 
     tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 74a6f11..f495d6b 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -47,18 +47,43 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+    OringDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
     tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
 
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
     EXPECT_EQ(durationTimeNs, buckets[0].mDuration);
 }
 
+TEST(OringDurationTrackerTest, TestDurationNested) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    vector<DurationBucket> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    OringDurationTracker tracker(wizard, 1, true, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart("2:maps", true, eventStartTimeNs + 10, key1);  // overlapping wl
+
+    tracker.noteStop("2:maps", eventStartTimeNs + 2000, false);
+    tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(2003, buckets[0].mDuration);
+}
+
 TEST(OringDurationTrackerTest, TestDurationConditionChange) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
@@ -75,18 +100,50 @@
     uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(wizard, 1, bucketStartTimeNs, bucketSizeNs, buckets);
+    OringDurationTracker tracker(wizard, 1, false, bucketStartTimeNs, bucketSizeNs, buckets);
 
     tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
 
     tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
 
-    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs);
+    tracker.noteStop("2:maps", eventStartTimeNs + durationTimeNs, false);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
     EXPECT_EQ(1u, buckets.size());
     EXPECT_EQ(5, buckets[0].mDuration);
 }
+
+TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    ConditionKey key1;
+    key1["APP_BACKGROUND"] = "1:maps|";
+
+    EXPECT_CALL(*wizard, query(_, key1))  // #4
+            .WillOnce(Return(ConditionState::kFalse));
+
+    vector<DurationBucket> buckets;
+
+    uint64_t bucketStartTimeNs = 10000000000;
+    uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+    uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+    OringDurationTracker tracker(wizard, 1, true, bucketStartTimeNs, bucketSizeNs, buckets);
+
+    tracker.noteStart("2:maps", true, eventStartTimeNs, key1);
+    tracker.noteStart("2:maps", true, eventStartTimeNs + 2, key1);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + 3, false);
+
+    tracker.onSlicedConditionMayChange(eventStartTimeNs + 15);
+
+    tracker.noteStop("2:maps", eventStartTimeNs + 2003, false);
+
+    tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+    EXPECT_EQ(1u, buckets.size());
+    EXPECT_EQ(15, buckets[0].mDuration);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android