Slice by state in DurationMetricProducer

- Added #onStateChanged logic to DurationMetricProducer
- Only SUM aggregation type supports state slicing.
OringDurationTrackers now track durations keyed by different state
values.
- Added unit tests for DurationMetricProducer state changes

Test: bit statsd_test:*
Bug: 145838038
Bug: 136566566
Change-Id: I87dcc21bb904ef0fa76ea196d60dadae00f310fa
Merged-In: I735c7deee96639c32cad4b907e59b354dfd98d77
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 23d8f59..29249f4 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -230,6 +230,47 @@
     }
 }
 
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+                               const HashableDimensionKey& primaryKey,
+                               const vector<Metric2State>& stateLinks, const int32_t stateAtomId) {
+    if (whatKey.getValues().size() < primaryKey.getValues().size()) {
+        ALOGE("Contains linked values false: whatKey is too small");
+        return false;
+    }
+
+    for (const auto& primaryValue : primaryKey.getValues()) {
+        bool found = false;
+        for (const auto& whatValue : whatKey.getValues()) {
+            if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) &&
+                primaryValue.mValue == whatValue.mValue) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+            const Field& stateField, const Field& metricField) {
+    for (auto stateLink : stateLinks) {
+        if (stateLink.stateAtomId != stateAtomId) {
+            continue;
+        }
+
+        for (size_t i = 0; i < stateLink.stateFields.size(); i++) {
+            if (stateLink.stateFields[i].mMatcher == stateField &&
+                stateLink.metricFields[i].mMatcher == metricField) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) {
     if (s1.size() != s2.size()) {
         return s1.size() < s2.size();
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index a766bba..33a5024 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -110,6 +110,10 @@
         return mStateValuesKey;
     }
 
+    inline HashableDimensionKey* getMutableStateValuesKey() {
+        return &mStateValuesKey;
+    }
+
     inline void setStateValuesKey(const HashableDimensionKey& key) {
         mStateValuesKey = key;
     }
@@ -169,6 +173,32 @@
 void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link,
                           HashableDimensionKey* statePrimaryKey);
 
+/**
+ * Returns true if the primaryKey values are a subset of the whatKey values.
+ * The values from the primaryKey come from the state atom, so we need to
+ * check that a link exists between the state atom field and what atom field.
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 27, {uid: 1005}]
+ * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}]
+ * Returns false
+ */
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+                               const HashableDimensionKey& primaryKey,
+                               const std::vector<Metric2State>& stateLinks,
+                               const int32_t stateAtomId);
+
+/**
+ * Returns true if there is a Metric2State link that links the stateField and
+ * the metricField (they are equal fields from different atoms).
+ */
+bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+            const Field& stateField, const Field& metricField);
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 14585c3..97512ed 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -334,6 +334,11 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 57d4d78..5cd00c3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -123,7 +123,8 @@
         BatteryLevelChanged battery_level_changed =
                 30 [(module) = "framework", (module) = "statsdtest"];
         ChargingStateChanged charging_state_changed = 31 [(module) = "framework"];
-        PluggedStateChanged plugged_state_changed = 32 [(module) = "framework"];
+        PluggedStateChanged plugged_state_changed = 32
+                [(module) = "framework", (module) = "statsdtest"];
         InteractiveStateChanged interactive_state_changed = 33 [(module) = "framework"];
         TouchEventReported touch_event_reported = 34;
         WakeupAlarmOccurred wakeup_alarm_occurred = 35 [(module) = "framework"];
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index e85b975..0de92f3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -55,6 +55,7 @@
 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
 const int FIELD_ID_BUCKET_INFO = 3;
 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
+const int FIELD_ID_SLICE_BY_STATE = 6;
 // for DurationBucketInfo
 const int FIELD_ID_DURATION = 3;
 const int FIELD_ID_BUCKET_NUM = 4;
@@ -115,6 +116,14 @@
     }
     mUnSlicedPartCondition = ConditionState::kUnknown;
 
+    for (const auto& stateLink : metric.state_link()) {
+        Metric2State ms;
+        ms.stateAtomId = stateLink.state_atom_id();
+        translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
+        translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
+        mMetric2StateLinks.push_back(ms);
+    }
+
     mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions);
     if (mWizard != nullptr && mConditionTrackerIndex >= 0 &&
             mMetric2ConditionLinks.size() == 1) {
@@ -150,21 +159,49 @@
     return anomalyTracker;
 }
 
+void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                                            const HashableDimensionKey& primaryKey,
+                                            const int32_t oldState, const int32_t newState) {
+    // Create a FieldValue object to hold the new state.
+    FieldValue value;
+    value.mValue.setInt(newState);
+    // Check if this metric has a StateMap. If so, map the new state value to
+    // the correct state group id.
+    mapStateValue(atomId, &value);
+
+    flushIfNeededLocked(eventTimeNs);
+
+    // Each duration tracker is mapped to a different whatKey (a set of values from the
+    // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the
+    // state change event are a subset of the tracker's whatKey field values.
+    //
+    // Ex. For a duration metric dimensioned on uid and tag:
+    // DurationTracker1 whatKey = uid: 1001, tag: 1
+    // DurationTracker2 whatKey = uid: 1002, tag 1
+    //
+    // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state
+    // change.
+    for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+        if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) {
+            continue;
+        }
+        whatIt.second->onStateChanged(eventTimeNs, atomId, value);
+    }
+}
+
 unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
         const MetricDimensionKey& eventKey) const {
     switch (mAggregationType) {
         case DurationMetric_AggregationType_SUM:
             return make_unique<OringDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
-                    mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mTimeBaseNs, mBucketSizeNs, mConditionSliced,
-                    mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+                    mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
         case DurationMetric_AggregationType_MAX_SPARSE:
             return make_unique<MaxDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
-                    mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mTimeBaseNs, mBucketSizeNs, mConditionSliced,
-                    mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+                    mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
     }
 }
 
@@ -364,6 +401,13 @@
             writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
                                            FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
         }
+        // Then fill slice_by_state.
+        for (auto state : dimensionKey.getStateValuesKey().getValues()) {
+            uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                     FIELD_ID_SLICE_BY_STATE);
+            writeStateToProto(state, protoOutput);
+            protoOutput->end(stateToken);
+        }
         // Then fill bucket_info (DurationBucketInfo).
         for (const auto& bucket : pair.second) {
             uint64_t bucketInfoToken = protoOutput->start(
@@ -460,7 +504,6 @@
                                               const ConditionKey& conditionKeys,
                                               bool condition, const LogEvent& event) {
     const auto& whatKey = eventKey.getDimensionKeyInWhat();
-
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         if (hitGuardRailLocked(eventKey)) {
@@ -471,19 +514,18 @@
 
     auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (mUseWhatDimensionAsInternalDimension) {
-        it->second->noteStart(whatKey, condition,
-                              event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
         return;
     }
 
     if (mInternalDimensions.empty()) {
-        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
-                              event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(),
+                              conditionKeys);
     } else {
         HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
         filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
-        it->second->noteStart(
-            dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(),
+                              conditionKeys);
     }
 
 }
@@ -519,6 +561,41 @@
         filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
     }
 
+    // Stores atom id to primary key pairs for each state atom that the metric is
+    // sliced by.
+    std::map<int, HashableDimensionKey> statePrimaryKeys;
+
+    // For states with primary fields, use MetricStateLinks to get the primary
+    // field values from the log event. These values will form a primary key
+    // that will be used to query StateTracker for the correct state value.
+    for (const auto& stateLink : mMetric2StateLinks) {
+        getDimensionForState(event.getValues(), stateLink,
+                             &statePrimaryKeys[stateLink.stateAtomId]);
+    }
+
+    // For each sliced state, query StateTracker for the state value using
+    // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
+    //
+    // Expected functionality: for any case where the MetricStateLinks are
+    // initialized incorrectly (ex. # of state links != # of primary fields, no
+    // links are provided for a state with primary fields, links are provided
+    // in the wrong order, etc.), StateTracker will simply return kStateUnknown
+    // when queried using an incorrect key.
+    HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY;
+    for (auto atomId : mSlicedStateAtoms) {
+        FieldValue value;
+        if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
+            // found a primary key for this state, query using the key
+            queryStateValue(atomId, statePrimaryKeys[atomId], &value);
+        } else {
+            // if no MetricStateLinks exist for this state atom,
+            // query using the default dimension key (empty HashableDimensionKey)
+            queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+        }
+        mapStateValue(atomId, &value);
+        stateValuesKey.addValue(value);
+    }
+
     // Handles Stop events.
     if (matcherIndex == mStopIndex) {
         if (mUseWhatDimensionAsInternalDimension) {
@@ -559,8 +636,8 @@
 
     condition = condition && mIsActive;
 
-    handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey,
-                     condition, event);
+    handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition,
+                     event);
 }
 
 size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 06da0f6..cc48f99 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -54,6 +54,10 @@
     sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
                                          const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
 
+    void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                        const HashableDimensionKey& primaryKey, const int32_t oldState,
+                        const int32_t newState) override;
+
 protected:
     void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
 
@@ -137,7 +141,7 @@
 
     // Helper function to create a duration tracker given the metric aggregation type.
     std::unique_ptr<DurationTracker> createDurationTracker(
-        const MetricDimensionKey& eventKey) const;
+            const MetricDimensionKey& eventKey) const;
 
     // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
     std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index be754e2..2518d85 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -120,12 +120,13 @@
         FieldValue value;
         if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
             // found a primary key for this state, query using the key
-            getMappedStateValue(atomId, statePrimaryKeys[atomId], &value);
+            queryStateValue(atomId, statePrimaryKeys[atomId], &value);
         } else {
             // if no MetricStateLinks exist for this state atom,
             // query using the default dimension key (empty HashableDimensionKey)
-            getMappedStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+            queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
         }
+        mapStateValue(atomId, &value);
         stateValuesKey.addValue(value);
     }
 
@@ -264,15 +265,17 @@
     }
 }
 
-void MetricProducer::getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
-                                         FieldValue* value) {
+void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+                                     FieldValue* value) {
     if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) {
         value->mValue = Value(StateTracker::kStateUnknown);
         value->mField.setTag(atomId);
         ALOGW("StateTracker not found for state atom %d", atomId);
         return;
     }
+}
 
+void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) {
     // check if there is a state map for this atom
     auto atomIt = mStateGroupMap.find(atomId);
     if (atomIt == mStateGroupMap.end()) {
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 4c4cd89..4550e65 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -187,7 +187,8 @@
     };
 
     void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
-                        const HashableDimensionKey& primaryKey, int oldState, int newState){};
+                        const HashableDimensionKey& primaryKey, const int32_t oldState,
+                        const int32_t newState){};
 
     // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
     // This method clears all the past buckets.
@@ -379,11 +380,15 @@
         return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
     }
 
-    // Query StateManager for original state value.
-    // If no state map exists for this atom, return the original value.
-    // Otherwise, return the group_id mapped to the atom and original value.
-    void getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
-                             FieldValue* value);
+    // Query StateManager for original state value using the queryKey.
+    // The field and value are output.
+    void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+                         FieldValue* value);
+
+    // If a state map exists for the given atom, replace the original state
+    // value with the group id mapped to the value.
+    // If no state map exists, keep the original state value.
+    void mapStateValue(const int32_t atomId, FieldValue* value);
 
     DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason);
 
@@ -467,6 +472,11 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 3fb9166..1fd6572 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -329,6 +329,11 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index afe93d4..8d59d13 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -56,11 +56,19 @@
     int64_t mDuration;
 };
 
+struct DurationValues {
+    // Recorded duration for current partial bucket.
+    int64_t mDuration;
+
+    // Sum of past partial bucket durations in current full bucket.
+    // Used for anomaly detection.
+    int64_t mDurationFullBucket;
+};
+
 class DurationTracker {
 public:
     DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-                    sp<ConditionWizard> wizard, int conditionIndex,
-                    bool nesting,
+                    sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                     int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
                     int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
                     const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
@@ -73,7 +81,6 @@
           mNested(nesting),
           mCurrentBucketStartTimeNs(currentBucketStartNs),
           mDuration(0),
-          mDurationFullBucket(0),
           mCurrentBucketNum(currentBucketNum),
           mStartTimeNs(startTimeNs),
           mConditionSliced(conditionSliced),
@@ -82,8 +89,8 @@
 
     virtual ~DurationTracker(){};
 
-    virtual void noteStart(const HashableDimensionKey& key, bool condition,
-                           const int64_t eventTime, const ConditionKey& conditionKey) = 0;
+    virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
+                           const ConditionKey& conditionKey) = 0;
     virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
                           const bool stopAll) = 0;
     virtual void noteStopAll(const int64_t eventTime) = 0;
@@ -91,6 +98,9 @@
     virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0;
     virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0;
 
+    virtual void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                const FieldValue& newState) = 0;
+
     // Flush stale buckets if needed, and return true if the tracker has no on-going duration
     // events, so that the owner can safely remove the tracker.
     virtual bool flushIfNeeded(
@@ -109,9 +119,12 @@
     // Dump internal states for debugging
     virtual void dumpStates(FILE* out, bool verbose) const = 0;
 
-    void setEventKey(const MetricDimensionKey& eventKey) {
-         mEventKey = eventKey;
-    }
+    virtual int64_t getCurrentStateKeyDuration() const = 0;
+
+    virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0;
+
+    // Replace old value with new value for the given state atom.
+    virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0;
 
 protected:
     int64_t getCurrentBucketEndTimeNs() const {
@@ -140,10 +153,11 @@
         }
     }
 
-    void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
+    void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey,
+                                        const int64_t& bucketValue, const int64_t& bucketNum) {
         for (auto& anomalyTracker : mAnomalyTrackers) {
             if (anomalyTracker != nullptr) {
-                anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
+                anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum);
             }
         }
     }
@@ -164,6 +178,10 @@
         return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
     }
 
+    void setEventKey(const MetricDimensionKey& eventKey) {
+        mEventKey = eventKey;
+    }
+
     // A reference to the DurationMetricProducer's config key.
     const ConfigKey& mConfigKey;
 
@@ -183,7 +201,8 @@
 
     int64_t mDuration;  // current recorded duration result (for partial bucket)
 
-    int64_t mDurationFullBucket;  // Sum of past partial buckets in current full bucket.
+    // Recorded duration results for each state key in the current partial bucket.
+    std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap;
 
     int64_t mCurrentBucketNum;
 
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 2be5855..ee4e167 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -26,15 +26,14 @@
 
 MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
                                        const MetricDimensionKey& eventKey,
-                                       sp<ConditionWizard> wizard, int conditionIndex,
-                                       bool nesting,
+                                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                                        int64_t currentBucketStartNs, int64_t currentBucketNum,
                                        int64_t startTimeNs, int64_t bucketSizeNs,
                                        bool conditionSliced, bool fullLink,
                                        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
-                      currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, fullLink, anomalyTrackers) {
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+                      anomalyTrackers) {
 }
 
 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -91,7 +90,6 @@
     }
 }
 
-
 void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime,
                                   bool forceStop) {
     VLOG("MaxDuration: key %s stop", key.toString().c_str());
@@ -240,6 +238,11 @@
     }
 }
 
+void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                        const FieldValue& newState) {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
 void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) {
     for (auto& pair : mInfos) {
         noteConditionChanged(pair.first, condition, timestamp);
@@ -309,6 +312,20 @@
     fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
 }
 
+int64_t MaxDurationTracker::getCurrentStateKeyDuration() const {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+    return -1;
+}
+
+int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+    return -1;
+}
+
+void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index efb8dc7..2891c6e 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -54,10 +54,19 @@
     void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
     void onConditionChanged(bool condition, const int64_t timestamp) override;
 
+    void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                        const FieldValue& newState) override;
+
     int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
+    int64_t getCurrentStateKeyDuration() const override;
+
+    int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
 private:
     // Returns true if at least one of the mInfos is started.
     bool anyStarted();
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 57f3965..19b2fe8 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -26,13 +26,12 @@
 
 OringDurationTracker::OringDurationTracker(
         const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-        sp<ConditionWizard> wizard, int conditionIndex,
-        bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
-        int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
-        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
-                      currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, fullLink, anomalyTrackers),
+        sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+        int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
+        bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+                      anomalyTrackers),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
@@ -90,10 +89,14 @@
             mConditionKeyMap.erase(key);
         }
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
-            VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
-                 (long long)mDuration);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
+            VLOG("record duration %lld, total duration %lld for state key %s",
+                 (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+                 mEventKey.getStateValuesKey().toString().c_str());
         }
     }
 
@@ -112,10 +115,14 @@
 
 void OringDurationTracker::noteStopAll(const int64_t timestamp) {
     if (!mStarted.empty()) {
-        mDuration += (timestamp - mLastStartTime);
-        VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
-             (long long)mDuration);
-        detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+        mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                (timestamp - mLastStartTime);
+        VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s",
+             (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+             mEventKey.getStateValuesKey().toString().c_str());
+        detectAndDeclareAnomaly(
+                timestamp, mCurrentBucketNum,
+                getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
     }
 
     stopAnomalyAlarm(timestamp);
@@ -146,21 +153,36 @@
 
     // Process the current bucket.
     if (mStarted.size() > 0) {
-        mDuration += (currentBucketEndTimeNs - mLastStartTime);
+        // Calculate the duration for the current state key.
+        mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                (currentBucketEndTimeNs - mLastStartTime);
     }
-    if (mDuration > 0) {
-        DurationBucket current_info;
-        current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
-        current_info.mBucketEndNs = currentBucketEndTimeNs;
-        current_info.mDuration = mDuration;
-        (*output)[mEventKey].push_back(current_info);
-        mDurationFullBucket += mDuration;
-        VLOG("  duration: %lld", (long long)current_info.mDuration);
-    }
-    if (eventTimeNs > fullBucketEnd) {
-        // End of full bucket, can send to anomaly tracker now.
-        addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum);
-        mDurationFullBucket = 0;
+    // Store DurationBucket info for each whatKey, stateKey pair.
+    // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the
+    // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to
+    // store durations for each stateKey, so we need to flush the bucket by creating a
+    // DurationBucket for each stateKey.
+    for (auto& durationIt : mStateKeyDurationMap) {
+        if (durationIt.second.mDuration > 0) {
+            DurationBucket current_info;
+            current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+            current_info.mBucketEndNs = currentBucketEndTimeNs;
+            current_info.mDuration = durationIt.second.mDuration;
+            (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)]
+                    .push_back(current_info);
+
+            durationIt.second.mDurationFullBucket += durationIt.second.mDuration;
+            VLOG("  duration: %lld", (long long)current_info.mDuration);
+        }
+
+        if (eventTimeNs > fullBucketEnd) {
+            // End of full bucket, can send to anomaly tracker now.
+            addPastBucketToAnomalyTrackers(
+                    MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first),
+                    getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum);
+            durationIt.second.mDurationFullBucket = 0;
+        }
+        durationIt.second.mDuration = 0;
     }
 
     if (mStarted.size() > 0) {
@@ -169,20 +191,19 @@
             info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1);
             info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
             info.mDuration = mBucketSizeNs;
+            // Full duration buckets are attributed to the current stateKey.
             (*output)[mEventKey].push_back(info);
             // Safe to send these buckets to anomaly tracker since they must be full buckets.
             // If it's a partial bucket, numBucketsForward would be 0.
-            addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i);
+            addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i);
             VLOG("  add filling bucket with duration %lld", (long long)info.mDuration);
         }
     } else {
         if (numBucketsForward >= 2) {
-            addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1);
+            addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1);
         }
     }
 
-    mDuration = 0;
-
     if (numBucketsForward > 0) {
         mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
         mCurrentBucketNum += numBucketsForward;
@@ -229,10 +250,14 @@
         }
 
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
-            VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
-                 (long long)mDuration);
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
+            VLOG("record duration %lld, total duration %lld for state key %s",
+                 (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(),
+                 mEventKey.getStateValuesKey().toString().c_str());
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
         }
     }
 
@@ -288,10 +313,13 @@
     } else {
         if (!mStarted.empty()) {
             VLOG("Condition false, all paused");
-            mDuration += (timestamp - mLastStartTime);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
             mPaused.insert(mStarted.begin(), mStarted.end());
             mStarted.clear();
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
         }
     }
     if (mStarted.empty()) {
@@ -299,6 +327,20 @@
     }
 }
 
+void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                          const FieldValue& newState) {
+    // If no keys are being tracked, update the current state key and return.
+    if (mStarted.empty()) {
+        updateCurrentStateKey(atomId, newState);
+        return;
+    }
+    // Add the current duration length to the previous state key and then update
+    // the last start time and current state key.
+    mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime);
+    mLastStartTime = timestamp;
+    updateCurrentStateKey(atomId, newState);
+}
+
 int64_t OringDurationTracker::predictAnomalyTimestampNs(
         const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const {
 
@@ -308,12 +350,13 @@
     // The timestamp of the current bucket end.
     const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs();
 
-    // The past duration ns for the current bucket.
-    int64_t currentBucketPastNs = mDuration + mDurationFullBucket;
+    // The past duration ns for the current bucket of the current stateKey.
+    int64_t currentStateBucketPastNs =
+            getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration();
 
     // As we move into the future, old buckets get overwritten (so their old data is erased).
     // Sum of past durations. Will change as we overwrite old buckets.
-    int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
+    int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
 
     // The refractory period end timestamp for dimension mEventKey.
     const int64_t refractoryPeriodEndNs =
@@ -372,7 +415,7 @@
                     mEventKey,
                     mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx);
         } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) {
-            pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs));
+            pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs));
         }
     }
 
@@ -382,7 +425,34 @@
 void OringDurationTracker::dumpStates(FILE* out, bool verbose) const {
     fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size());
     fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size());
-    fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
+    fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration());
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyDuration() const {
+    auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+    if (it == mStateKeyDurationMap.end()) {
+        return 0;
+    } else {
+        return it->second.mDuration;
+    }
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+    auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+    if (it == mStateKeyDurationMap.end()) {
+        return 0;
+    } else {
+        return it->second.mDurationFullBucket;
+    }
+}
+
+void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+    HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey();
+    for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) {
+        if (stateValuesKey->getValues()[i].mField.getTag() == atomId) {
+            stateValuesKey->mutableValue(i)->mValue = newState.mValue;
+        }
+    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index f44e327..bd8017a 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -28,10 +28,9 @@
 public:
     OringDurationTracker(const ConfigKey& key, const int64_t& id,
                          const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
-                         int conditionIndex,
-                         bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
-                         int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
-                         bool fullLink,
+                         int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+                         int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs,
+                         bool conditionSliced, bool fullLink,
                          const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
 
     OringDurationTracker(const OringDurationTracker& tracker) = default;
@@ -45,6 +44,9 @@
     void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
     void onConditionChanged(bool condition, const int64_t timestamp) override;
 
+    void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                        const FieldValue& newState) override;
+
     bool flushCurrentBucket(
             const int64_t& eventTimeNs,
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
@@ -56,6 +58,12 @@
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
+    int64_t getCurrentStateKeyDuration() const override;
+
+    int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
 private:
     // We don't need to keep track of individual durations. The information that's needed is:
     // 1) which keys are started. We record the first start time.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 3810c48..0d0788e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -564,6 +564,33 @@
             }
         }
 
+        std::vector<int> slicedStateAtoms;
+        unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+        if (metric.slice_by_state_size() > 0) {
+            if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) {
+                ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state");
+                return false;
+            }
+            if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+                                        allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+                return false;
+            }
+        } else {
+            if (metric.state_link_size() > 0) {
+                ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state");
+                return false;
+            }
+        }
+
+        // Check that all metric state links are a subset of dimensions_in_what fields.
+        std::vector<Matcher> dimensionsInWhat;
+        translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+        for (const auto& stateLink : metric.state_link()) {
+            if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+                return false;
+            }
+        }
+
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(config, metric.id(), metricIndex,
@@ -575,7 +602,8 @@
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
                 key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
                 trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs,
-                currentTimeNs, eventActivationMap, eventDeactivationMap);
+                currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
+                stateGroupMap);
 
         allMetricProducers.push_back(durationMetric);
     }
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index c45274e..ed98f50 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -103,12 +103,14 @@
 message DurationMetricData {
   optional DimensionsValue dimensions_in_what = 1;
 
-  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+  repeated StateValue slice_by_state = 6;
 
   repeated DurationBucketInfo bucket_info = 3;
 
   repeated DimensionsValue dimension_leaf_values_in_what = 4;
 
+  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
   repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
 }
 
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 83d9484..c7407bd 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -227,8 +227,12 @@
 
   optional int64 condition = 3;
 
+  repeated int64 slice_by_state = 9;
+
   repeated MetricConditionLink links = 4;
 
+  repeated MetricStateLink state_link = 10;
+
   enum AggregationType {
     SUM = 1;
 
@@ -238,9 +242,9 @@
 
   optional FieldMatcher dimensions_in_what = 6;
 
-  optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
-
   optional TimeUnit bucket = 7;
+
+  optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
 }
 
 message GaugeMetric {