Support slicing by chain.

BUG: b/73975181

Test: statsd test
Change-Id: I913ae0f68ff21ed0703bb5da9c60d3eaa3bf5981
diff --git a/cmds/statsd/benchmark/filter_value_benchmark.cpp b/cmds/statsd/benchmark/filter_value_benchmark.cpp
index 66c4def..cfe477d 100644
--- a/cmds/statsd/benchmark/filter_value_benchmark.cpp
+++ b/cmds/statsd/benchmark/filter_value_benchmark.cpp
@@ -54,28 +54,12 @@
     translateFieldMatcher(field_matcher, &matchers);
 
     while (state.KeepRunning()) {
-        vector<HashableDimensionKey> output;
-        filterValues(matchers, event.getValues(), &output);
-    }
-}
-
-BENCHMARK(BM_FilterValue);
-
-static void BM_FilterValue2(benchmark::State& state) {
-    LogEvent event(1, 100000);
-    FieldMatcher field_matcher;
-    createLogEventAndMatcher(&event, &field_matcher);
-
-    std::vector<Matcher> matchers;
-    translateFieldMatcher(field_matcher, &matchers);
-
-    while (state.KeepRunning()) {
         HashableDimensionKey output;
         filterValues(matchers, event.getValues(), &output);
     }
 }
 
-BENCHMARK(BM_FilterValue2);
+BENCHMARK(BM_FilterValue);
 
 }  //  namespace statsd
 }  //  namespace os
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 0c9b701..dfd8705 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -48,6 +48,11 @@
         return true;
     }
 
+    if (matcher.hasAllPositionMatcher() &&
+        (mField & (matcher.mMask & kClearAllPositionMatcherMask)) == matcher.mMatcher.getField()) {
+        return true;
+    }
+
     return false;
 }
 
@@ -67,6 +72,10 @@
             return;
         }
         switch (matcher.position()) {
+            case Position::ALL:
+                pos[depth] = 0x00;
+                mask[depth] = 0x7f;
+                break;
             case Position::ANY:
                 pos[depth] = 0;
                 mask[depth] = 0;
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 0e3ae06..f7ce23b 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -30,6 +30,7 @@
 const int32_t kMaxLogDepth = 2;
 const int32_t kLastBitMask = 0x80;
 const int32_t kClearLastBitDeco = 0x7f;
+const int32_t kClearAllPositionMatcherMask = 0xffff00ff;
 
 enum Type { UNKNOWN, INT, LONG, FLOAT, STRING };
 
@@ -205,6 +206,7 @@
  * First: [Matcher Field] 0x02010101  [Mask]0xff7f7f7f
  * Last:  [Matcher Field] 0x02018001  [Mask]0xff7f807f
  * Any:   [Matcher Field] 0x02010001  [Mask]0xff7f007f
+ * All:   [Matcher Field] 0x02010001  [Mask]0xff7f7f7f
  *
  * [To match a log Field with a Matcher] we apply the bit mask to the log Field and check if
  * the result is equal to the Matcher Field. That's a bit wise AND operation + check if 2 ints are
@@ -226,9 +228,21 @@
         return mMask;
     }
 
+    inline int32_t getRawMaskAtDepth(int32_t depth) const {
+        int32_t field = (mMask & 0x00ffffff);
+        int32_t shift = 8 * (kMaxLogDepth - depth);
+        int32_t mask = 0xff << shift;
+
+        return (field & mask) >> shift;
+    }
+
+    bool hasAllPositionMatcher() const {
+        return mMatcher.getDepth() == 2 && getRawMaskAtDepth(1) == 0x7f;
+    }
+
     bool hasAnyPositionMatcher(int* prefix) const {
-        if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(2) == 0) {
-            (*prefix) = mMatcher.getPrefix(2);
+        if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(1) == 0) {
+            (*prefix) = mMatcher.getPrefix(1);
             return true;
         }
         return false;
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index d0c8311..7103034 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -61,125 +61,22 @@
 
 bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
                   HashableDimensionKey* output) {
-    for (size_t i = 0; i < matcherFields.size(); ++i) {
-        const auto& matcher = matcherFields[i];
-        bool found = false;
-        for (const auto& value : values) {
+    size_t num_matches = 0;
+    for (const auto& value : values) {
+        for (size_t i = 0; i < matcherFields.size(); ++i) {
+            const auto& matcher = matcherFields[i];
             // TODO: potential optimization here to break early because all fields are naturally
             // sorted.
             if (value.mField.matches(matcher)) {
                 output->addValue(value);
-                output->mutableValue(i)->mField.setTag(value.mField.getTag());
-                output->mutableValue(i)->mField.setField(value.mField.getField() & matcher.mMask);
-                found = true;
-                break;
-            }
-        }
-
-        if (!found) {
-            VLOG("We can't find a dimension value for matcher (%d)%#x.", matcher.mMatcher.getTag(),
-                   matcher.mMatcher.getField());
-            return false;
-        }
-    }
-
-    return true;
-}
-
-// Filter fields using the matchers and output the results as a HashableDimensionKey.
-// Note: HashableDimensionKey is just a wrapper for vector<FieldValue>
-bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
-                  vector<HashableDimensionKey>* output) {
-    output->push_back(HashableDimensionKey());
-    // Top level is only tag id. Now take the real child matchers
-    int prevAnyMatcherPrefix = 0;
-    size_t prevPrevFanout = 0;
-    size_t prevFanout = 0;
-
-    // For each matcher get matched results.
-    vector<FieldValue> matchedResults(2);
-    for (const auto& matcher : matcherFields) {
-        size_t num_matches = 0;
-        for (const auto& value : values) {
-            // TODO: potential optimization here to break early because all fields are naturally
-            // sorted.
-            if (value.mField.matches(matcher)) {
-                if (num_matches >= matchedResults.size()) {
-                    matchedResults.resize(num_matches * 2);
-                }
-                matchedResults[num_matches].mField.setTag(value.mField.getTag());
-                matchedResults[num_matches].mField.setField(value.mField.getField() & matcher.mMask);
-                matchedResults[num_matches].mValue = value.mValue;
+                output->mutableValue(num_matches)->mField.setTag(value.mField.getTag());
+                output->mutableValue(num_matches)->mField.setField(
+                    value.mField.getField() & matcher.mMask);
                 num_matches++;
             }
         }
-
-        if (num_matches == 0) {
-            VLOG("We can't find a dimension value for matcher (%d)%#x.", matcher.mMatcher.getTag(),
-                   matcher.mMatcher.getField());
-            continue;
-        }
-
-        if (num_matches == 1) {
-            for (auto& dimension : *output) {
-                dimension.addValue(matchedResults[0]);
-            }
-            prevAnyMatcherPrefix = 0;
-            prevFanout = 0;
-            continue;
-        }
-
-        // All the complexity below is because we support ANY in dimension.
-        bool createFanout = true;
-        // createFanout is true when the matcher doesn't need to follow the prev matcher's
-        // order.
-        // e.g., get (uid, tag) from any position in attribution. because we have translated
-        // it as 2 matchers, they need to follow the same ordering, we can't create a cross
-        // product of all uid and tags.
-        // However, if the 2 matchers have different prefix, they will create a cross product
-        // e.g., [any uid] [any some other repeated field], we will create a cross product for them
-        if (prevAnyMatcherPrefix != 0) {
-            int anyMatcherPrefix = 0;
-            bool isAnyMatcher = matcher.hasAnyPositionMatcher(&anyMatcherPrefix);
-            if (isAnyMatcher && anyMatcherPrefix == prevAnyMatcherPrefix) {
-                createFanout = false;
-            } else {
-                prevAnyMatcherPrefix = anyMatcherPrefix;
-            }
-        }
-
-        // Each matcher should match exact one field, unless position is ANY
-        // When x number of fields matches a matcher, the returned dimension
-        // size is multiplied by x.
-        int oldSize;
-        if (createFanout) {
-            // First create fanout (fanout size is matchedResults.Size which could be one,
-            // which means we do nothing here)
-            oldSize = output->size();
-            for (size_t i = 1; i < num_matches; i++) {
-                output->insert(output->end(), output->begin(), output->begin() + oldSize);
-            }
-            prevPrevFanout = oldSize;
-            prevFanout = num_matches;
-        } else {
-            // If we should not create fanout, e.g., uid tag from same position should be remain
-            // together.
-            oldSize = prevPrevFanout;
-            if (prevFanout != num_matches) {
-                // sanity check.
-                ALOGE("2 Any matcher result in different output");
-                return false;
-            }
-        }
-        // now add the matched field value to output
-        for (size_t i = 0; i < num_matches; i++) {
-            for (int j = 0; j < oldSize; j++) {
-                (*output)[i * oldSize + j].addValue(matchedResults[i]);
-            }
-        }
     }
-
-    return output->size() > 0 && (*output)[0].getValues().size() > 0;
+    return num_matches > 0;
 }
 
 void filterGaugeValues(const std::vector<Matcher>& matcherFields,
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 4cfed88..6f4941f 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -122,17 +122,14 @@
 /**
  * Creating HashableDimensionKeys from FieldValues using matcher.
  *
- * This function may make modifications to the Field if the matcher has Position=LAST or ANY in
- * it. This is because: for example, when we create dimension from last uid in attribution chain,
+ * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL
+ * in it. This is because: for example, when we create dimension from last uid in attribution chain,
  * In one event, uid 1000 is at position 5 and it's the last
  * In another event, uid 1000 is at position 6, and it's the last
  * these 2 events should be mapped to the same dimension.  So we will remove the original position
  * from the dimension key for the uid field (by applying 0x80 bit mask).
  */
 bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values,
-                  std::vector<HashableDimensionKey>* output);
-// This function is used when there is at most one output dimension key. (no ANY matcher)
-bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values,
                   HashableDimensionKey* output);
 
 /**
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 8db8200..8b42146 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -121,7 +121,8 @@
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration3);
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks1);
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2);
-    FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
+    FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
+    FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
     FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
     FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
     FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);
@@ -138,6 +139,7 @@
     FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition);
     FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition);
 
+
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index 4913aef..73efb39 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -327,25 +327,7 @@
         // have both sliced and unsliced version of a predicate.
         handleConditionEvent(outputValue, matchedState == 1, &overallState, &overallChanged);
     } else {
-        std::vector<HashableDimensionKey> outputValues;
-        filterValues(mOutputDimensions, event.getValues(), &outputValues);
-
-        // If this event has multiple nodes in the attribution chain,  this log event probably will
-        // generate multiple dimensions. If so, we will find if the condition changes for any
-        // dimension and ask the corresponding metric producer to verify whether the actual sliced
-        // condition has changed or not.
-        // A high level assumption is that a predicate is either sliced or unsliced. We will never
-        // have both sliced and unsliced version of a predicate.
-        for (const HashableDimensionKey& outputValue : outputValues) {
-            ConditionState tempState;
-            bool tempChanged = false;
-            handleConditionEvent(outputValue, matchedState == 1, &tempState, &tempChanged);
-            if (tempChanged) {
-                overallChanged = true;
-            }
-            // ConditionState's | operator is overridden
-            overallState = overallState | tempState;
-        }
+        ALOGE("The condition tracker should not be sliced by ANY position matcher.");
     }
     conditionCache[mIndex] = overallState;
     conditionChangedCache[mIndex] = overallChanged;
diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateTracker.cpp
index c68875c..fe1740b 100644
--- a/cmds/statsd/src/condition/StateTracker.cpp
+++ b/cmds/statsd/src/condition/StateTracker.cpp
@@ -137,21 +137,18 @@
 
     VLOG("StateTracker evaluate event %s", event.ToString().c_str());
 
-    vector<HashableDimensionKey> keys;
-    vector<HashableDimensionKey> outputs;
-    filterValues(mPrimaryKeys, event.getValues(), &keys);
-    filterValues(mOutputDimensions, event.getValues(), &outputs);
-    if (keys.size() != 1 || outputs.size() != 1) {
-        ALOGE("More than 1 states in the event?? panic now!");
+    // Primary key can exclusive fields must be simple fields. so there won't be more than
+    // one keys matched.
+    HashableDimensionKey primaryKey;
+    HashableDimensionKey state;
+    if (!filterValues(mPrimaryKeys, event.getValues(), &primaryKey) ||
+        !filterValues(mOutputDimensions, event.getValues(), &state)) {
+        ALOGE("Failed to filter fields in the event?? panic now!");
         conditionCache[mIndex] =
                 mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
         conditionChangedCache[mIndex] = false;
         return;
     }
-    // Primary key can exclusive fields must be simple fields. so there won't be more than
-    // one keys matched.
-    const auto& primaryKey = keys[0];
-    const auto& state = outputs[0];
     hitGuardRail(primaryKey);
 
     VLOG("StateTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str());
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 364d4e9..b7105b3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -185,6 +185,9 @@
                 ranges.push_back(std::make_pair(newStart, end));
                 break;
             }
+            case Position::ALL:
+                ALOGE("Not supported: field matcher with ALL position.");
+                break;
             case Position::POSITION_UNKNOWN:
                 break;
         }
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index bc09683..c6b9405 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -88,6 +88,12 @@
         translateFieldMatcher(internalDimensions, &mInternalDimensions);
         mContainANYPositionInInternalDimensions = HasPositionANY(internalDimensions);
     }
+    if (mContainANYPositionInInternalDimensions) {
+        ALOGE("Position ANY in internal dimension not supported.");
+    }
+    if (mContainANYPositionInDimensionsInWhat) {
+        ALOGE("Position ANY in dimension_in_what not supported.");
+    }
 
     if (metric.has_dimensions_in_condition()) {
         translateFieldMatcher(metric.dimensions_in_condition(), &mDimensionsInCondition);
@@ -589,18 +595,10 @@
         it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
                               event.GetElapsedTimestampNs(), conditionKeys);
     } else {
-        if (mContainANYPositionInInternalDimensions) {
-            std::vector<HashableDimensionKey> dimensionKeys;
-            filterValues(mInternalDimensions, event.getValues(), &dimensionKeys);
-            for (const auto& key : dimensionKeys) {
-                it->second->noteStart(key, condition, event.GetElapsedTimestampNs(), conditionKeys);
-            }
-        } else {
-            HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
-            filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
-            it->second->noteStart(
-                dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
-        }
+        HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
+        filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
+        it->second->noteStart(
+            dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
     }
 
 }
@@ -612,8 +610,8 @@
     ALOGW("Not used in duration tracker.");
 }
 
-void DurationMetricProducer::onMatchedLogEventLocked_simple(const size_t matcherIndex,
-                                                            const LogEvent& event) {
+void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
+                                                     const LogEvent& event) {
     uint64_t eventTimeNs = event.GetElapsedTimestampNs();
     if (eventTimeNs < mStartTimeNs) {
         return;
@@ -712,117 +710,6 @@
     }
 }
 
-void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
-                                                     const LogEvent& event) {
-    if (!mContainANYPositionInDimensionsInWhat) {
-        onMatchedLogEventLocked_simple(matcherIndex, event);
-        return;
-    }
-
-    uint64_t eventTimeNs = event.GetElapsedTimestampNs();
-    if (eventTimeNs < mStartTimeNs) {
-        return;
-    }
-
-    flushIfNeededLocked(event.GetElapsedTimestampNs());
-
-    // Handles Stopall events.
-    if (matcherIndex == mStopAllIndex) {
-        for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->noteStopAll(event.GetElapsedTimestampNs());
-            }
-        }
-        return;
-    }
-
-    vector<HashableDimensionKey> dimensionInWhatValues;
-    if (!mDimensionsInWhat.empty()) {
-        filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
-    } else {
-        dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
-    }
-
-    // Handles Stop events.
-    if (matcherIndex == mStopIndex) {
-        if (mUseWhatDimensionAsInternalDimension) {
-            for (const HashableDimensionKey& whatKey : dimensionInWhatValues) {
-                auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
-                if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-                    for (const auto& condIt : whatIt->second) {
-                        condIt.second->noteStop(whatKey, event.GetElapsedTimestampNs(), false);
-                    }
-                }
-            }
-            return;
-        }
-
-        HashableDimensionKey internalDimensionKey = DEFAULT_DIMENSION_KEY;
-        if (!mInternalDimensions.empty()) {
-            filterValues(mInternalDimensions, event.getValues(), &internalDimensionKey);
-        }
-
-        for (const HashableDimensionKey& whatDimension : dimensionInWhatValues) {
-            auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
-            if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-                for (const auto& condIt : whatIt->second) {
-                    condIt.second->noteStop(
-                        internalDimensionKey, event.GetElapsedTimestampNs(), false);
-                }
-            }
-        }
-        return;
-    }
-
-    bool condition;
-    ConditionKey conditionKey;
-    std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
-    if (mConditionSliced) {
-        for (const auto& link : mMetric2ConditionLinks) {
-            getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
-        }
-
-        auto conditionState =
-            mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
-                           !mSameConditionDimensionsInTracker,
-                           !mHasLinksToAllConditionDimensionsInTracker,
-                           &dimensionKeysInCondition);
-        condition = (conditionState == ConditionState::kTrue);
-        if (mDimensionsInCondition.empty() && condition) {
-            dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
-        }
-    } else {
-        condition = mCondition;
-        if (condition) {
-            dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
-        }
-    }
-
-    for (const auto& whatDimension : dimensionInWhatValues) {
-        if (dimensionKeysInCondition.empty()) {
-            handleStartEvent(MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
-                             conditionKey, condition, event);
-        } else {
-            auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
-            // If the what dimension is already there, we should update all the trackers even
-            // the condition is false.
-            if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-                for (const auto& condIt : whatIt->second) {
-                    const bool cond = dimensionKeysInCondition.find(condIt.first) !=
-                            dimensionKeysInCondition.end();
-                    handleStartEvent(MetricDimensionKey(whatDimension, condIt.first),
-                                     conditionKey, cond, event);
-                    dimensionKeysInCondition.erase(condIt.first);
-                }
-            }
-            for (const auto& conditionDimension : dimensionKeysInCondition) {
-                handleStartEvent(MetricDimensionKey(whatDimension, conditionDimension),
-                                 conditionKey, condition, event);
-            }
-        }
-    }
-}
-
 size_t DurationMetricProducer::byteSizeLocked() const {
     size_t totalSize = 0;
     for (const auto& pair : mPastBuckets) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 6746e11..985749d 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -52,8 +52,6 @@
 protected:
     void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
 
-    void onMatchedLogEventLocked_simple(const size_t matcherIndex, const LogEvent& event);
-
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
             const ConditionKey& conditionKeys, bool condition,
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 6c90b03..f4495a1 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -53,39 +53,17 @@
         dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
     }
 
-    if (mContainANYPositionInDimensionsInWhat) {
-        vector<HashableDimensionKey> dimensionInWhatValues;
-        if (!mDimensionsInWhat.empty()) {
-            filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
-        } else {
-            dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
-        }
-
-        for (const auto& whatDimension : dimensionInWhatValues) {
-            for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
-                onMatchedLogEventInternalLocked(
-                        matcherIndex, MetricDimensionKey(whatDimension, conditionDimensionKey),
-                        conditionKey, condition, event);
-            }
-            if (dimensionKeysInCondition.empty()) {
-                onMatchedLogEventInternalLocked(
-                        matcherIndex, MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
-                         conditionKey, condition, event);
-            }
-        }
-    } else {
-        HashableDimensionKey dimensionInWhat;
-        filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
-        MetricDimensionKey metricKey(dimensionInWhat, DEFAULT_DIMENSION_KEY);
-        for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
-            metricKey.setDimensionKeyInCondition(conditionDimensionKey);
-            onMatchedLogEventInternalLocked(
-                    matcherIndex, metricKey, conditionKey, condition, event);
-        }
-        if (dimensionKeysInCondition.empty()) {
-            onMatchedLogEventInternalLocked(
-                    matcherIndex, metricKey, conditionKey, condition, event);
-        }
+    HashableDimensionKey dimensionInWhat;
+    filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
+    MetricDimensionKey metricKey(dimensionInWhat, DEFAULT_DIMENSION_KEY);
+    for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
+        metricKey.setDimensionKeyInCondition(conditionDimensionKey);
+        onMatchedLogEventInternalLocked(
+                matcherIndex, metricKey, conditionKey, condition, event);
+    }
+    if (dimensionKeysInCondition.empty()) {
+        onMatchedLogEventInternalLocked(
+                matcherIndex, metricKey, conditionKey, condition, event);
     }
 
  }
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index dbab814..8726c3c 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -158,7 +158,8 @@
 
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
-    FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
+    FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
+    FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
     FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
     FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
     FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 1c99e2a..2c70191 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -31,6 +31,8 @@
   LAST = 2;
 
   ANY = 3;
+
+  ALL = 4;
 }
 
 enum TimeUnit {
diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp
index 5846761..73e7c44 100644
--- a/cmds/statsd/tests/FieldValue_test.cpp
+++ b/cmds/statsd/tests/FieldValue_test.cpp
@@ -45,20 +45,41 @@
 
     const auto& matcher12 = output[0];
     EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag());
-    EXPECT_EQ((int32_t)0x2010001, matcher12.mMatcher.getField());
+    EXPECT_EQ((int32_t)0x02010001, matcher12.mMatcher.getField());
     EXPECT_EQ((int32_t)0xff7f007f, matcher12.mMask);
 }
 
-TEST(AtomMatcherTest, TestFilter) {
+TEST(AtomMatcherTest, TestFieldTranslation_ALL) {
     FieldMatcher matcher1;
     matcher1.set_field(10);
     FieldMatcher* child = matcher1.add_child();
     child->set_field(1);
-    child->set_position(Position::ANY);
+    child->set_position(Position::ALL);
 
     child = child->add_child();
     child->set_field(1);
 
+    vector<Matcher> output;
+    translateFieldMatcher(matcher1, &output);
+
+    EXPECT_EQ((size_t)1, output.size());
+
+    const auto& matcher12 = output[0];
+    EXPECT_EQ((int32_t)10, matcher12.mMatcher.getTag());
+    EXPECT_EQ((int32_t)0x02010001, matcher12.mMatcher.getField());
+    EXPECT_EQ((int32_t)0xff7f7f7f, matcher12.mMask);
+}
+
+TEST(AtomMatcherTest, TestFilter_ALL) {
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::ALL);
+
+    child->add_child()->set_field(1);
+    child->add_child()->set_field(2);
+
     child = matcher1.add_child();
     child->set_field(2);
 
@@ -85,32 +106,28 @@
     event.write("some value");
     // Convert to a LogEvent
     event.init();
-    vector<HashableDimensionKey> output;
+    HashableDimensionKey output;
 
     filterValues(matchers, event.getValues(), &output);
 
-    EXPECT_EQ((size_t)(3), output.size());
+    EXPECT_EQ((size_t)7, output.getValues().size());
+    EXPECT_EQ((int32_t)0x02010101, output.getValues()[0].mField.getField());
+    EXPECT_EQ((int32_t)1111, output.getValues()[0].mValue.int_value);
+    EXPECT_EQ((int32_t)0x02010102, output.getValues()[1].mField.getField());
+    EXPECT_EQ("location1", output.getValues()[1].mValue.str_value);
 
-    const auto& key1 = output[0];
-    EXPECT_EQ((size_t)2, key1.getValues().size());
-    EXPECT_EQ((int32_t)0x02010001, key1.getValues()[0].mField.getField());
-    EXPECT_EQ((int32_t)1111, key1.getValues()[0].mValue.int_value);
-    EXPECT_EQ((int32_t)0x00020000, key1.getValues()[1].mField.getField());
-    EXPECT_EQ("some value", key1.getValues()[1].mValue.str_value);
+    EXPECT_EQ((int32_t)0x02010201, output.getValues()[2].mField.getField());
+    EXPECT_EQ((int32_t)2222, output.getValues()[2].mValue.int_value);
+    EXPECT_EQ((int32_t)0x02010202, output.getValues()[3].mField.getField());
+    EXPECT_EQ("location2", output.getValues()[3].mValue.str_value);
 
-    const auto& key2 = output[1];
-    EXPECT_EQ((size_t)2, key2.getValues().size());
-    EXPECT_EQ((int32_t)0x02010001, key2.getValues()[0].mField.getField());
-    EXPECT_EQ((int32_t)2222, key2.getValues()[0].mValue.int_value);
-    EXPECT_EQ((int32_t)0x00020000, key2.getValues()[1].mField.getField());
-    EXPECT_EQ("some value", key2.getValues()[1].mValue.str_value);
+    EXPECT_EQ((int32_t)0x02010301, output.getValues()[4].mField.getField());
+    EXPECT_EQ((int32_t)3333, output.getValues()[4].mValue.int_value);
+    EXPECT_EQ((int32_t)0x02010302, output.getValues()[5].mField.getField());
+    EXPECT_EQ("location3", output.getValues()[5].mValue.str_value);
 
-    const auto& key3 = output[2];
-    EXPECT_EQ((size_t)2, key3.getValues().size());
-    EXPECT_EQ((int32_t)0x02010001, key3.getValues()[0].mField.getField());
-    EXPECT_EQ((int32_t)3333, key3.getValues()[0].mValue.int_value);
-    EXPECT_EQ((int32_t)0x00020000, key3.getValues()[1].mField.getField());
-    EXPECT_EQ("some value", key3.getValues()[1].mValue.str_value);
+    EXPECT_EQ((int32_t)0x00020000, output.getValues()[6].mField.getField());
+    EXPECT_EQ("some value", output.getValues()[6].mValue.str_value);
 }
 
 TEST(AtomMatcherTest, TestSubDimension) {
diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
index 7a7e000..2574ba7 100644
--- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
@@ -28,7 +28,7 @@
 
 namespace {
 
-StatsdConfig CreateStatsdConfig() {
+StatsdConfig CreateStatsdConfig(const Position position) {
     StatsdConfig config;
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     auto attributionNodeMatcher =
@@ -46,15 +46,15 @@
     countMetric->set_what(wakelockAcquireMatcher.id());
     *countMetric->mutable_dimensions_in_what() =
         CreateAttributionUidAndTagDimensions(
-            android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+            android::util::WAKELOCK_STATE_CHANGED, {position});
     countMetric->set_bucket(FIVE_MINUTES);
     return config;
 }
 
 }  // namespace
 
-TEST(AttributionE2eTest, TestAttributionMatchAndSlice) {
-    auto config = CreateStatsdConfig();
+TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid) {
+    auto config = CreateStatsdConfig(Position::FIRST);
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs =
         TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
@@ -199,6 +199,215 @@
     EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + 3 * bucketSizeNs);
 }
 
+TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain) {
+    auto config = CreateStatsdConfig(Position::ALL);
+    int64_t bucketStartTimeNs = 10000000000;
+    int64_t bucketSizeNs =
+        TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+    // Here it assumes that GMS core has two uids.
+    processor->getUidMap()->updateApp(
+        android::String16("com.android.gmscore"), 222 /* uid */, 1 /* version code*/);
+    processor->getUidMap()->updateApp(
+        android::String16("com.android.gmscore"), 444 /* uid */, 1 /* version code*/);
+    processor->getUidMap()->updateApp(
+        android::String16("app1"), 111 /* uid */, 2 /* version code*/);
+    processor->getUidMap()->updateApp(
+        android::String16("APP3"), 333 /* uid */, 2 /* version code*/);
+
+    // GMS core node is in the middle.
+    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
+                                                          CreateAttribution(222, "GMSCoreModule1"),
+                                                          CreateAttribution(333, "App3")};
+
+    // GMS core node is the last one.
+    std::vector<AttributionNodeInternal> attributions2 = {CreateAttribution(111, "App1"),
+                                                          CreateAttribution(333, "App3"),
+                                                          CreateAttribution(222, "GMSCoreModule1")};
+
+    // GMS core node is the first one.
+    std::vector<AttributionNodeInternal> attributions3 = {CreateAttribution(222, "GMSCoreModule1"),
+                                                          CreateAttribution(333, "App3")};
+
+    // Single GMS core node.
+    std::vector<AttributionNodeInternal> attributions4 = {CreateAttribution(222, "GMSCoreModule1")};
+
+    // GMS core has another uid.
+    std::vector<AttributionNodeInternal> attributions5 = {CreateAttribution(111, "App1"),
+                                                          CreateAttribution(444, "GMSCoreModule2"),
+                                                          CreateAttribution(333, "App3")};
+
+    // Multiple GMS core nodes.
+    std::vector<AttributionNodeInternal> attributions6 = {CreateAttribution(444, "GMSCoreModule2"),
+                                                          CreateAttribution(222, "GMSCoreModule1")};
+
+    // No GMS core nodes.
+    std::vector<AttributionNodeInternal> attributions7 = {CreateAttribution(111, "App1"),
+                                                          CreateAttribution(333, "App3")};
+    std::vector<AttributionNodeInternal> attributions8 = {CreateAttribution(111, "App1")};
+
+    // GMS core node with isolated uid.
+    const int isolatedUid = 666;
+    std::vector<AttributionNodeInternal> attributions9 = {
+            CreateAttribution(isolatedUid, "GMSCoreModule1")};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    // Events 1~4 are in the 1st bucket.
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions1, "wl1", bucketStartTimeNs + 2));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions2, "wl1", bucketStartTimeNs + 200));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions3, "wl1", bucketStartTimeNs + bucketSizeNs - 1));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions4, "wl1", bucketStartTimeNs + bucketSizeNs));
+
+    // Events 5~8 are in the 3rd bucket.
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions5, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 1));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions6, "wl2", bucketStartTimeNs + 2 * bucketSizeNs + 100));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions7, "wl2", bucketStartTimeNs + 3 * bucketSizeNs - 2));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions8, "wl2", bucketStartTimeNs + 3 * bucketSizeNs));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 1));
+    events.push_back(CreateAcquireWakelockEvent(
+        attributions9, "wl2", bucketStartTimeNs + 3 * bucketSizeNs + 100));
+    events.push_back(CreateIsolatedUidChangedEvent(
+        isolatedUid, 222, true/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs - 1));
+    events.push_back(CreateIsolatedUidChangedEvent(
+        isolatedUid, 222, false/* is_create*/, bucketStartTimeNs + 3 * bucketSizeNs + 10));
+
+    sortLogEventsByTimestamp(&events);
+
+    for (const auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 4 * bucketSizeNs + 1, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    EXPECT_EQ(reports.reports_size(), 1);
+    EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    EXPECT_EQ(countMetrics.data_size(), 6);
+
+    auto data = countMetrics.data(0);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(1, data.bucket_info(1).count());
+    EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
+              data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 4 * bucketSizeNs,
+              data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(1);
+    ValidateUidDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 222);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
+    ValidateUidDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+    EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
+
+    data = countMetrics.data(2);
+    ValidateUidDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 444);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2");
+    ValidateUidDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(3);
+    ValidateUidDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
+    ValidateUidDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
+    ValidateUidDimension(
+        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(bucketStartTimeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(4);
+    ValidateUidDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
+    ValidateUidDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
+    ValidateUidDimension(
+        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 222);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 222, "GMSCoreModule1");
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(bucketStartTimeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(5);
+    ValidateUidDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 0, android::util::WAKELOCK_STATE_CHANGED, 111, "App1");
+    ValidateUidDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 444);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 1, android::util::WAKELOCK_STATE_CHANGED, 444, "GMSCoreModule2");
+    ValidateUidDimension(
+        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333);
+    ValidateAttributionUidAndTagDimension(
+        data.dimensions_in_what(), 2, android::util::WAKELOCK_STATE_CHANGED, 333, "App3");
+    EXPECT_EQ(data.bucket_info_size(), 1);
+    EXPECT_EQ(data.bucket_info(0).count(), 1);
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 2678c8a..0f785df 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -500,12 +500,42 @@
         .value_tuple().dimensions_value(0).value_int(), uid);
 }
 
+void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid) {
+    EXPECT_EQ(value.field(), atomId);
+    EXPECT_GT(value.value_tuple().dimensions_value_size(), node_idx);
+    // Attribution field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(node_idx).field(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(node_idx)
+        .value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(node_idx)
+        .value_tuple().dimensions_value(0).value_int(), uid);
+}
+
+void ValidateAttributionUidAndTagDimension(
+    const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag) {
+    EXPECT_EQ(value.field(), atomId);
+    EXPECT_GT(value.value_tuple().dimensions_value_size(), node_idx);
+    // Attribution field.
+    EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx).field());
+    // Uid only.
+    EXPECT_EQ(2, value.value_tuple().dimensions_value(node_idx)
+        .value_tuple().dimensions_value_size());
+    EXPECT_EQ(1, value.value_tuple().dimensions_value(node_idx)
+        .value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(uid, value.value_tuple().dimensions_value(node_idx)
+        .value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(2, value.value_tuple().dimensions_value(node_idx)
+        .value_tuple().dimensions_value(1).field());
+    EXPECT_EQ(tag, value.value_tuple().dimensions_value(node_idx)
+        .value_tuple().dimensions_value(1).value_str());
+}
+
 void ValidateAttributionUidAndTagDimension(
     const DimensionsValue& value, int atomId, int uid, const std::string& tag) {
     EXPECT_EQ(value.field(), atomId);
-    EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(1, value.value_tuple().dimensions_value_size());
     // Attribution field.
-    EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(1, value.value_tuple().dimensions_value(0).field());
     // Uid only.
     EXPECT_EQ(value.value_tuple().dimensions_value(0)
         .value_tuple().dimensions_value_size(), 2);
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 14eba1f..1ac630c 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -180,9 +180,12 @@
 
 int64_t StringToId(const string& str);
 
+void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid);
 void ValidateAttributionUidAndTagDimension(
     const DimensionsValue& value, int atomId, int uid, const std::string& tag);
+void ValidateAttributionUidAndTagDimension(
+    const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag);
 
 struct DimensionsPair {
     DimensionsPair(DimensionsValue m1, DimensionsValue m2) : dimInWhat(m1), dimInCondition(m2){};