Dimensional sampling for count, duration, value, kll, and gauge metrics

Test: atest statsd_test
Bug: 259164633
Merged-In: Ib1982e15b521981388bd225ea97e5dc8bfaa09ab
Change-Id: I081397a7dce4c4f277af618342b970d02ec40814
diff --git a/statsd/Android.bp b/statsd/Android.bp
index 30eed16..d8cb209 100644
--- a/statsd/Android.bp
+++ b/statsd/Android.bp
@@ -87,6 +87,7 @@
         "src/subscriber/SubscriberReporter.cpp",
         "src/uid_data.proto",
         "src/utils/MultiConditionTrigger.cpp",
+        "src/utils/ShardOffsetProvider.cpp",
     ],
 
     local_include_dirs: [
diff --git a/statsd/src/FieldValue.cpp b/statsd/src/FieldValue.cpp
index 406c19f..9e1f21f 100644
--- a/statsd/src/FieldValue.cpp
+++ b/statsd/src/FieldValue.cpp
@@ -16,8 +16,11 @@
 
 #define STATSD_DEBUG false
 #include "Log.h"
+
 #include "FieldValue.h"
+
 #include "HashableDimensionKey.h"
+#include "hash.h"
 #include "math.h"
 
 namespace android {
@@ -521,6 +524,38 @@
     return totalSize;
 }
 
+bool shouldKeepSample(const FieldValue& sampleFieldValue, int shardOffset, int shardCount) {
+    int hashValue = 0;
+    switch (sampleFieldValue.mValue.type) {
+        case INT:
+            hashValue = Hash32(reinterpret_cast<const char*>(&sampleFieldValue.mValue.int_value),
+                               sizeof(sampleFieldValue.mValue.int_value));
+            break;
+        case LONG:
+            hashValue = Hash32(reinterpret_cast<const char*>(&sampleFieldValue.mValue.long_value),
+                               sizeof(sampleFieldValue.mValue.long_value));
+            break;
+        case FLOAT:
+            hashValue = Hash32(reinterpret_cast<const char*>(&sampleFieldValue.mValue.float_value),
+                               sizeof(sampleFieldValue.mValue.float_value));
+            break;
+        case DOUBLE:
+            hashValue = Hash32(reinterpret_cast<const char*>(&sampleFieldValue.mValue.double_value),
+                               sizeof(sampleFieldValue.mValue.double_value));
+            break;
+        case STRING:
+            hashValue = Hash32(sampleFieldValue.mValue.str_value);
+            break;
+        case STORAGE:
+            hashValue = Hash32((const char*)sampleFieldValue.mValue.storage_value.data(),
+                               sampleFieldValue.mValue.storage_value.size());
+            break;
+        default:
+            return true;
+    }
+    return (hashValue + shardOffset) % shardCount == 0;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/FieldValue.h b/statsd/src/FieldValue.h
index b868602..963e2c0 100644
--- a/statsd/src/FieldValue.h
+++ b/statsd/src/FieldValue.h
@@ -465,6 +465,9 @@
 // Estimate the memory size of the FieldValues. This is different from sizeof(FieldValue) because
 // the size is computed at runtime using the actual contents stored in the FieldValue.
 size_t getSize(const std::vector<FieldValue>& fieldValues);
+
+bool shouldKeepSample(const FieldValue& sampleFieldValue, int shardOffset, int shardCount);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/guardrail/invalid_config_reason_enum.proto b/statsd/src/guardrail/invalid_config_reason_enum.proto
index 6de0711..74e32c2 100644
--- a/statsd/src/guardrail/invalid_config_reason_enum.proto
+++ b/statsd/src/guardrail/invalid_config_reason_enum.proto
@@ -102,4 +102,7 @@
     INVALID_CONFIG_REASON_ALARM_PERIOD_LESS_THAN_OR_EQUAL_ZERO = 77;
     INVALID_CONFIG_REASON_SUBSCRIPTION_SUBSCRIBER_INFO_MISSING = 78;
     INVALID_CONFIG_REASON_SUBSCRIPTION_RULE_NOT_FOUND = 79;
+    INVALID_CONFIG_REASON_METRIC_DIMENSIONAL_SAMPLING_INFO_INCORRECT_SHARD_COUNT = 80;
+    INVALID_CONFIG_REASON_METRIC_DIMENSIONAL_SAMPLING_INFO_MISSING_SAMPLED_FIELD = 81;
+    INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELD_INCORRECT_SIZE = 82;
 };
diff --git a/statsd/src/metrics/DurationMetricProducer.cpp b/statsd/src/metrics/DurationMetricProducer.cpp
index a8bdcc2..04cecbb 100644
--- a/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/statsd/src/metrics/DurationMetricProducer.cpp
@@ -713,6 +713,10 @@
         return;
     }
 
+    if (!passesSampleCheckLocked(values)) {
+        return;
+    }
+
     HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY;
     if (!mDimensionsInWhat.empty()) {
         filterValues(mDimensionsInWhat, values, &dimensionInWhat);
diff --git a/statsd/src/metrics/GaugeMetricProducer.h b/statsd/src/metrics/GaugeMetricProducer.h
index b5bd013..eb0ee37 100644
--- a/statsd/src/metrics/GaugeMetricProducer.h
+++ b/statsd/src/metrics/GaugeMetricProducer.h
@@ -221,6 +221,7 @@
     FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket);
     FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger);
     FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput);
+    FRIEND_TEST(GaugeMetricProducerTest, TestPullDimensionalSampling);
 
     FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents);
     FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled);
diff --git a/statsd/src/metrics/MetricProducer.cpp b/statsd/src/metrics/MetricProducer.cpp
index 3862d99..137510e 100644
--- a/statsd/src/metrics/MetricProducer.cpp
+++ b/statsd/src/metrics/MetricProducer.cpp
@@ -74,7 +74,9 @@
       mSlicedStateAtoms(slicedStateAtoms),
       mStateGroupMap(stateGroupMap),
       mSplitBucketForAppUpgrade(splitBucketForAppUpgrade),
-      mHasHitGuardrail(false) {
+      mHasHitGuardrail(false),
+      mSampledWhatFields({}),
+      mShardCount(0) {
 }
 
 optional<InvalidConfigReason> MetricProducer::onConfigUpdatedLocked(
@@ -120,6 +122,10 @@
         return;
     }
 
+    if (!passesSampleCheckLocked(event.getValues())) {
+        return;
+    }
+
     bool condition;
     ConditionKey conditionKey;
     if (mConditionSliced) {
@@ -362,6 +368,21 @@
     return mCurrentSkippedBucket.dropEvents.size() >= StatsdStats::kMaxLoggedBucketDropEvents;
 }
 
+bool MetricProducer::passesSampleCheckLocked(const vector<FieldValue>& values) const {
+    // Only perform sampling if shard count is correct and there is a sampled what field.
+    if (mShardCount <= 1 || mSampledWhatFields.size() == 0) {
+        return true;
+    }
+    // If filtering fails, don't perform sampling. Event could be a gauge trigger event or stop all
+    // event.
+    FieldValue sampleFieldValue;
+    if (!filterValues(mSampledWhatFields[0], values, &sampleFieldValue)) {
+        return true;
+    }
+    return shouldKeepSample(sampleFieldValue, ShardOffsetProvider::getInstance().getShardOffset(),
+                            mShardCount);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/metrics/MetricProducer.h b/statsd/src/metrics/MetricProducer.h
index 8ab5872..b8bd6fa 100644
--- a/statsd/src/metrics/MetricProducer.h
+++ b/statsd/src/metrics/MetricProducer.h
@@ -32,6 +32,7 @@
 #include "packages/PackageInfoListener.h"
 #include "state/StateListener.h"
 #include "state/StateManager.h"
+#include "utils/ShardOffsetProvider.h"
 
 namespace android {
 namespace os {
@@ -133,6 +134,13 @@
     }
 };
 
+struct SamplingInfo {
+    // Matchers for sampled fields. Currently only one sampled dimension is supported.
+    std::vector<Matcher> sampledWhatFields;
+
+    int shardCount = 0;
+};
+
 template <class T>
 optional<bool> getAppUpgradeBucketSplit(const T& metric) {
     return metric.has_split_bucket_for_app_upgrade()
@@ -372,6 +380,12 @@
         std::lock_guard<std::mutex> lock(mMutex);
         mAnomalyTrackers.push_back(anomalyTracker);
     }
+
+    void setSamplingInfo(SamplingInfo samplingInfo) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mSampledWhatFields.swap(samplingInfo.sampledWhatFields);
+        mShardCount = samplingInfo.shardCount;
+    }
     // End: getters/setters
 protected:
     /**
@@ -486,6 +500,8 @@
     // exceeded the maximum number allowed, which is currently capped at 10.
     bool maxDropEventsReached() const;
 
+    bool passesSampleCheckLocked(const vector<FieldValue>& values) const;
+
     const int64_t mMetricId;
 
     // Hash of the Metric's proto bytes from StatsdConfig, including any activations.
@@ -567,6 +583,11 @@
     // If hard dimension guardrail is hit, do not spam logcat
     bool mHasHitGuardrail;
 
+    // Matchers for sampled fields. Currently only one sampled dimension is supported.
+    std::vector<Matcher> mSampledWhatFields;
+
+    int mShardCount;
+
     FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
     FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
     FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
@@ -605,6 +626,7 @@
     FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
 
     FRIEND_TEST(MetricsManagerUtilTest, TestInitialConditions);
+    FRIEND_TEST(MetricsManagerUtilTest, TestSampledMetrics);
 
     FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricActivations);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateCountMetrics);
diff --git a/statsd/src/metrics/MetricsManager.h b/statsd/src/metrics/MetricsManager.h
index 811981c..4e2c7b3 100644
--- a/statsd/src/metrics/MetricsManager.h
+++ b/statsd/src/metrics/MetricsManager.h
@@ -352,6 +352,7 @@
     FRIEND_TEST(MetricsManagerTest, TestLogSources);
     FRIEND_TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate);
     FRIEND_TEST(MetricsManagerTest_SPlus, TestAtomMatcherOptimizationEnabledFlag);
+    FRIEND_TEST(MetricsManagerUtilTest, TestSampledMetrics);
 
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
@@ -386,6 +387,7 @@
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
+    FRIEND_TEST(GaugeMetricE2ePushedTest, TestDimensionalSampling);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 47888f4..250b93e 100644
--- a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -249,6 +249,40 @@
     return nullopt;
 }
 
+optional<InvalidConfigReason> handleMetricWithSampling(
+        const int64_t metricId, const DimensionalSamplingInfo& dimSamplingInfo,
+        SamplingInfo& samplingInfo) {
+    if (!dimSamplingInfo.has_sampled_what_field()) {
+        ALOGE("metric DimensionalSamplingInfo missing sampledWhatField");
+        return InvalidConfigReason(
+                INVALID_CONFIG_REASON_METRIC_DIMENSIONAL_SAMPLING_INFO_MISSING_SAMPLED_FIELD,
+                metricId);
+    }
+
+    if (dimSamplingInfo.shard_count() <= 1) {
+        ALOGE("metric shardCount must be > 1");
+        return InvalidConfigReason(
+                INVALID_CONFIG_REASON_METRIC_DIMENSIONAL_SAMPLING_INFO_INCORRECT_SHARD_COUNT,
+                metricId);
+    }
+    samplingInfo.shardCount = dimSamplingInfo.shard_count();
+
+    if (HasPositionALL(dimSamplingInfo.sampled_what_field()) ||
+        HasPositionANY(dimSamplingInfo.sampled_what_field())) {
+        ALOGE("metric has repeated field with position ALL or ANY as the sampled dimension");
+        return InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELD_INCORRECT_SIZE,
+                                   metricId);
+    }
+
+    translateFieldMatcher(dimSamplingInfo.sampled_what_field(), &samplingInfo.sampledWhatFields);
+    if (samplingInfo.sampledWhatFields.size() != 1) {
+        ALOGE("metric has incorrect number of sampled dimension fields");
+        return InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELD_INCORRECT_SIZE,
+                                   metricId);
+    }
+    return nullopt;
+}
+
 // Validates a metricActivation and populates state.
 // EventActivationMap and EventDeactivationMap are supplied to a MetricProducer
 //      to provide the producer with state about its activators and deactivators.
@@ -488,9 +522,22 @@
         return nullopt;
     }
 
-    return {new CountMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
+    sp<MetricProducer> metricProducer =
+            new CountMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
                                     metricHash, timeBaseNs, currentTimeNs, eventActivationMap,
-                                    eventDeactivationMap, slicedStateAtoms, stateGroupMap)};
+                                    eventDeactivationMap, slicedStateAtoms, stateGroupMap);
+
+    SamplingInfo samplingInfo;
+    if (metric.has_dimensional_sampling_info()) {
+        invalidConfigReason = handleMetricWithSampling(
+                metric.id(), metric.dimensional_sampling_info(), samplingInfo);
+        if (invalidConfigReason.has_value()) {
+            return nullopt;
+        }
+        metricProducer->setSamplingInfo(samplingInfo);
+    }
+
+    return metricProducer;
 }
 
 optional<sp<MetricProducer>> createDurationMetricProducerAndUpdateMetadata(
@@ -653,18 +700,29 @@
         }
     }
 
-    sp<MetricProducer> producer = new DurationMetricProducer(
+    sp<MetricProducer> metricProducer = new DurationMetricProducer(
             key, metric, conditionIndex, initialConditionCache, whatIndex, startIndex, stopIndex,
             stopAllIndex, nesting, wizard, metricHash, internalDimensions, timeBaseNs,
             currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
             stateGroupMap);
-    if (!producer->isValid()) {
+    if (!metricProducer->isValid()) {
         // TODO: Remove once invalidConfigReason is added to the DurationMetricProducer constructor
         invalidConfigReason = InvalidConfigReason(
                 INVALID_CONFIG_REASON_DURATION_METRIC_PRODUCER_INVALID, metric.id());
         return nullopt;
     }
-    return {producer};
+
+    SamplingInfo samplingInfo;
+    if (metric.has_dimensional_sampling_info()) {
+        invalidConfigReason = handleMetricWithSampling(
+                metric.id(), metric.dimensional_sampling_info(), samplingInfo);
+        if (invalidConfigReason.has_value()) {
+            return nullopt;
+        }
+        metricProducer->setSamplingInfo(samplingInfo);
+    }
+
+    return metricProducer;
 }
 
 optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata(
@@ -868,7 +926,7 @@
                     ? optional<int64_t>(metric.condition_correction_threshold_nanos())
                     : nullopt;
 
-    return new NumericValueMetricProducer(
+    sp<MetricProducer> metricProducer = new NumericValueMetricProducer(
             key, metric, metricHash, {pullTagId, pullerManager},
             {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
              conditionCorrectionThresholdNs, getAppUpgradeBucketSplit(metric)},
@@ -877,6 +935,18 @@
             {conditionIndex, metric.links(), initialConditionCache, wizard},
             {metric.state_link(), slicedStateAtoms, stateGroupMap},
             {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit});
+
+    SamplingInfo samplingInfo;
+    if (metric.has_dimensional_sampling_info()) {
+        invalidConfigReason = handleMetricWithSampling(
+                metric.id(), metric.dimensional_sampling_info(), samplingInfo);
+        if (invalidConfigReason.has_value()) {
+            return nullopt;
+        }
+        metricProducer->setSamplingInfo(samplingInfo);
+    }
+
+    return metricProducer;
 }
 
 optional<sp<MetricProducer>> createKllMetricProducerAndUpdateMetadata(
@@ -1007,7 +1077,7 @@
     const auto [dimensionSoftLimit, dimensionHardLimit] =
             StatsdStats::getAtomDimensionKeySizeLimits(atomTagId);
 
-    return new KllMetricProducer(
+    sp<MetricProducer> metricProducer = new KllMetricProducer(
             key, metric, metricHash, {/*pullTagId=*/-1, pullerManager},
             {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
              /*conditionCorrectionThresholdNs=*/nullopt, getAppUpgradeBucketSplit(metric)},
@@ -1016,6 +1086,18 @@
             {conditionIndex, metric.links(), initialConditionCache, wizard},
             {metric.state_link(), slicedStateAtoms, stateGroupMap},
             {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit});
+
+    SamplingInfo samplingInfo;
+    if (metric.has_dimensional_sampling_info()) {
+        invalidConfigReason = handleMetricWithSampling(
+                metric.id(), metric.dimensional_sampling_info(), samplingInfo);
+        if (invalidConfigReason.has_value()) {
+            return nullopt;
+        }
+        metricProducer->setSamplingInfo(samplingInfo);
+    }
+
+    return metricProducer;
 }
 
 optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata(
@@ -1143,11 +1225,23 @@
     const auto [dimensionSoftLimit, dimensionHardLimit] =
             StatsdStats::getAtomDimensionKeySizeLimits(pullTagId);
 
-    return {new GaugeMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
-                                    metricHash, trackerIndex, matcherWizard, pullTagId,
-                                    triggerAtomId, atomTagId, timeBaseNs, currentTimeNs,
-                                    pullerManager, eventActivationMap, eventDeactivationMap,
-                                    dimensionSoftLimit, dimensionHardLimit)};
+    sp<MetricProducer> metricProducer = new GaugeMetricProducer(
+            key, metric, conditionIndex, initialConditionCache, wizard, metricHash, trackerIndex,
+            matcherWizard, pullTagId, triggerAtomId, atomTagId, timeBaseNs, currentTimeNs,
+            pullerManager, eventActivationMap, eventDeactivationMap, dimensionSoftLimit,
+            dimensionHardLimit);
+
+    SamplingInfo samplingInfo;
+    if (metric.has_dimensional_sampling_info()) {
+        invalidConfigReason = handleMetricWithSampling(
+                metric.id(), metric.dimensional_sampling_info(), samplingInfo);
+        if (invalidConfigReason.has_value()) {
+            return nullopt;
+        }
+        metricProducer->setSamplingInfo(samplingInfo);
+    }
+
+    return metricProducer;
 }
 
 optional<sp<AnomalyTracker>> createAnomalyTracker(
diff --git a/statsd/src/statsd_config.proto b/statsd/src/statsd_config.proto
index ca1934b..758a8d1 100644
--- a/statsd/src/statsd_config.proto
+++ b/statsd/src/statsd_config.proto
@@ -214,6 +214,12 @@
     }
 }
 
+message DimensionalSamplingInfo {
+    optional FieldMatcher sampled_what_field = 1;
+
+    optional int32 shard_count = 2;
+}
+
 message EventMetric {
   optional int64 id = 1;
 
@@ -250,6 +256,8 @@
 
   optional FieldMatcher dimensions_in_condition = 7 [deprecated = true];
 
+  optional DimensionalSamplingInfo dimensional_sampling_info = 12;
+
   reserved 100;
   reserved 101;
 }
@@ -284,6 +292,8 @@
 
   optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
 
+  optional DimensionalSamplingInfo dimensional_sampling_info = 13;
+
   reserved 100;
   reserved 101;
 }
@@ -323,6 +333,8 @@
 
   optional bool split_bucket_for_app_upgrade = 14;
 
+  optional DimensionalSamplingInfo dimensional_sampling_info = 15;
+
   reserved 100;
   reserved 101;
 }
@@ -384,6 +396,8 @@
 
   optional FieldMatcher dimensions_in_condition = 9 [deprecated = true];
 
+  optional DimensionalSamplingInfo dimensional_sampling_info = 23;
+
   reserved 100;
   reserved 101;
 }
@@ -411,6 +425,8 @@
 
     repeated MetricStateLink state_link = 11;
 
+    optional DimensionalSamplingInfo dimensional_sampling_info = 12;
+
     reserved 100;
     reserved 101;
 }
diff --git a/statsd/src/utils/ShardOffsetProvider.cpp b/statsd/src/utils/ShardOffsetProvider.cpp
new file mode 100644
index 0000000..6d847df
--- /dev/null
+++ b/statsd/src/utils/ShardOffsetProvider.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ShardOffsetProvider.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+ShardOffsetProvider::ShardOffsetProvider(const int shardOffset) : mShardOffset(shardOffset) {
+}
+
+ShardOffsetProvider& ShardOffsetProvider::getInstance() {
+    static ShardOffsetProvider sShardOffsetProvider(rand());
+    return sShardOffsetProvider;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/utils/ShardOffsetProvider.h b/statsd/src/utils/ShardOffsetProvider.h
new file mode 100644
index 0000000..0623e0f
--- /dev/null
+++ b/statsd/src/utils/ShardOffsetProvider.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHARD_OFFSET_PROVIDER_H
+#define SHARD_OFFSET_PROVIDER_H
+
+#include <gtest/gtest_prod.h>
+#include <stdlib.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/*
+ * Class is not guarded by any mutex. It is currently thread safe.
+ * Thread safety needs to be considered on all future changes to this class.
+ */
+class ShardOffsetProvider final {
+public:
+    ~ShardOffsetProvider(){};
+
+    int getShardOffset() const {
+        return mShardOffset;
+    }
+
+    static ShardOffsetProvider& getInstance();
+
+private:
+    ShardOffsetProvider(const int shardOffset);
+
+    // Only used for testing.
+    void setShardOffset(const int shardOffset) {
+        mShardOffset = shardOffset;
+    }
+
+    int mShardOffset;
+
+    FRIEND_TEST(CountMetricE2eTest, TestDimensionalSampling);
+    FRIEND_TEST(DurationMetricE2eTest, TestDimensionalSampling);
+    FRIEND_TEST(GaugeMetricE2ePushedTest, TestDimensionalSampling);
+    FRIEND_TEST(GaugeMetricProducerTest, TestPullDimensionalSampling);
+    FRIEND_TEST(KllMetricE2eTest, TestDimensionalSampling);
+    FRIEND_TEST(NumericValueMetricProducerTest, TestDimensionalSampling);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // METRIC_PRODUCER_H
\ No newline at end of file
diff --git a/statsd/tests/FieldValue_test.cpp b/statsd/tests/FieldValue_test.cpp
index d490b43..64be559 100644
--- a/statsd/tests/FieldValue_test.cpp
+++ b/statsd/tests/FieldValue_test.cpp
@@ -929,6 +929,111 @@
     EXPECT_FALSE(isPrimitiveRepeatedField(field7));
 }
 
+TEST(FieldValueTest, TestShouldKeepSampleInt) {
+    int shardOffset = 5;
+    int shardCount = 2;
+    int pos1[] = {1, 1, 1};
+
+    Field field(1, pos1, 2);
+
+    Value value1((int32_t)1001);
+    Value value2((int32_t)1002);
+
+    FieldValue fieldValue1(field, value1);
+    FieldValue fieldValue2(field, value2);
+
+    EXPECT_TRUE(shouldKeepSample(fieldValue1, shardOffset, shardCount));
+    EXPECT_FALSE(shouldKeepSample(fieldValue2, shardOffset, shardCount));
+}
+
+TEST(FieldValueTest, TestShouldKeepSampleLong) {
+    int shardOffset = 5;
+    int shardCount = 2;
+    int pos1[] = {1, 1, 1};
+
+    Field field(1, pos1, 2);
+
+    Value value1((int64_t)1001L);
+    Value value2((int64_t)1005L);
+
+    FieldValue fieldValue1(field, value1);
+    FieldValue fieldValue2(field, value2);
+
+    EXPECT_FALSE(shouldKeepSample(fieldValue1, shardOffset, shardCount));
+    EXPECT_TRUE(shouldKeepSample(fieldValue2, shardOffset, shardCount));
+}
+
+TEST(FieldValueTest, TestShouldKeepSampleFloat) {
+    int shardOffset = 5;
+    int shardCount = 2;
+    int pos1[] = {1, 1, 1};
+
+    Field field(1, pos1, 2);
+
+    Value value1((float)10.5);
+    Value value2((float)3.9);
+
+    FieldValue fieldValue1(field, value1);
+    FieldValue fieldValue2(field, value2);
+
+    EXPECT_TRUE(shouldKeepSample(fieldValue1, shardOffset, shardCount));
+    EXPECT_FALSE(shouldKeepSample(fieldValue2, shardOffset, shardCount));
+}
+
+TEST(FieldValueTest, TestShouldKeepSampleDouble) {
+    int shardOffset = 5;
+    int shardCount = 2;
+    int pos1[] = {1, 1, 1};
+
+    Field field(1, pos1, 2);
+
+    Value value1((double)1.5);
+    Value value2((double)3.9);
+
+    FieldValue fieldValue1(field, value1);
+    FieldValue fieldValue2(field, value2);
+
+    EXPECT_TRUE(shouldKeepSample(fieldValue1, shardOffset, shardCount));
+    EXPECT_FALSE(shouldKeepSample(fieldValue2, shardOffset, shardCount));
+}
+
+TEST(FieldValueTest, TestShouldKeepSampleString) {
+    int shardOffset = 5;
+    int shardCount = 2;
+    int pos1[] = {1, 1, 1};
+
+    Field field(1, pos1, 2);
+
+    Value value1("str1");
+    Value value2("str2");
+
+    FieldValue fieldValue1(field, value1);
+    FieldValue fieldValue2(field, value2);
+
+    EXPECT_FALSE(shouldKeepSample(fieldValue1, shardOffset, shardCount));
+    EXPECT_TRUE(shouldKeepSample(fieldValue2, shardOffset, shardCount));
+}
+
+TEST(FieldValueTest, TestShouldKeepSampleByteArray) {
+    int shardOffset = 5;
+    int shardCount = 2;
+    int pos1[] = {1, 1, 1};
+
+    Field field(1, pos1, 2);
+
+    vector<uint8_t> message1 = {'\t', 'e', '\0', 's', 't'};
+    vector<uint8_t> message2 = {'\t', 'e', '\0', 's', 't', 't'};
+
+    Value value1(message1);
+    Value value2(message2);
+
+    FieldValue fieldValue1(field, value1);
+    FieldValue fieldValue2(field, value2);
+
+    EXPECT_FALSE(shouldKeepSample(fieldValue1, shardOffset, shardCount));
+    EXPECT_TRUE(shouldKeepSample(fieldValue2, shardOffset, shardCount));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/e2e/CountMetric_e2e_test.cpp b/statsd/tests/e2e/CountMetric_e2e_test.cpp
index 2589f00..6212c43 100644
--- a/statsd/tests/e2e/CountMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -1783,9 +1783,92 @@
                         1);
 }
 
+TEST(CountMetricE2eTest, TestDimensionalSampling) {
+    ShardOffsetProvider::getInstance().setShardOffset(5);
+
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    CountMetric sampledCountMetric =
+            createCountMetric("CountSampledAppCrashesPerUid", appCrashMatcher.id(), nullopt, {});
+    *sampledCountMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    *sampledCountMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    sampledCountMetric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = sampledCountMetric;
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            bucketStartTimeNs, bucketStartTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    int appUid1 = 1001;  // odd hash value
+    int appUid2 = 1002;  // even hash value
+    int appUid3 = 1003;  // odd hash value
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, appUid1));  // 0:30
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 40 * NS_PER_SEC, appUid2));  // 0:50
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, appUid3));  // 1:10
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 80 * NS_PER_SEC, appUid1));  // 1:20
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 90 * NS_PER_SEC, appUid2));  // 1:30
+    events.push_back(
+            CreateAppCrashOccurredEvent(bucketStartTimeNs + 100 * NS_PER_SEC, appUid3));  // 1:40
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(2, countMetrics.data_size());
+
+    // Only Uid 1 and 3 are logged. (odd hash value) + (offset of 5) % (shard count of 2) = 0
+    CountMetricData data = countMetrics.data(0);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, appUid1);
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+
+    data = countMetrics.data(1);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, appUid3);
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
+#endif
\ No newline at end of file
diff --git a/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index 3202368..4a3ab11 100644
--- a/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -1649,6 +1649,106 @@
     EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
 }
 
+TEST(DurationMetricE2eTest, TestDimensionalSampling) {
+    ShardOffsetProvider::getInstance().setShardOffset(5);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
+    *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
+    AtomMatcher stopAllMatcher = CreateScheduleScheduledJobAtomMatcher();
+    *config.add_atom_matcher() = stopAllMatcher;
+
+    Predicate scheduledJobPredicate = CreateScheduledJobPredicate();
+    *scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions() =
+            CreateAttributionUidDimensions(util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+    SimplePredicate* simplePredicate = scheduledJobPredicate.mutable_simple_predicate();
+    simplePredicate->set_stop_all(stopAllMatcher.id());
+    *config.add_predicate() = scheduledJobPredicate;
+
+    DurationMetric sampledDurationMetric = createDurationMetric(
+            "DurationSampledScheduledJobPerUid", scheduledJobPredicate.id(), nullopt, {});
+    sampledDurationMetric.set_aggregation_type(DurationMetric::SUM);
+    *sampledDurationMetric.mutable_dimensions_in_what() =
+            CreateAttributionUidDimensions(util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+    *sampledDurationMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateAttributionUidDimensions(util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+    sampledDurationMetric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_duration_metric() = sampledDurationMetric;
+
+    const int64_t configAddedTimeNs = 1 * NS_PER_SEC;  // 0:01
+    const int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            configAddedTimeNs, configAddedTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    int uid1 = 1001;  // odd hash value
+    int uid2 = 1002;  // even hash value
+    int uid3 = 1003;  // odd hash value
+
+    const int64_t durationStartNs1 = 20 * NS_PER_SEC;
+    const int64_t durationStartNs2 = 40 * NS_PER_SEC;
+    const int64_t durationStartNs3 = 60 * NS_PER_SEC;
+    const int64_t durationStartNs4 = 80 * NS_PER_SEC;
+    const int64_t durationEndNs1 = 100 * NS_PER_SEC;
+    const int64_t durationEndNs2 = 110 * NS_PER_SEC;
+    const int64_t stopAllNs = 130 * NS_PER_SEC;
+    const int64_t durationEndNs3 = 150 * NS_PER_SEC;
+    const int64_t durationEndNs4 = 200 * NS_PER_SEC;
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateStartScheduledJobEvent(durationStartNs1, {uid1}, {"App1"}, "job1"));
+    events.push_back(CreateStartScheduledJobEvent(durationStartNs2, {uid2}, {"App2"}, "job2"));
+    events.push_back(CreateStartScheduledJobEvent(durationStartNs3, {uid3}, {"App3"}, "job3"));
+    events.push_back(CreateFinishScheduledJobEvent(durationEndNs1, {uid1}, {"App1"}, "job1"));
+    events.push_back(CreateFinishScheduledJobEvent(durationEndNs2, {uid2}, {"App2"}, "job2"));
+    // This event should pass the sample check regardless of the uid.
+    events.push_back(CreateScheduleScheduledJobEvent(stopAllNs, {uid2}, {"App2"}, "job2"));
+    // These events shouldn't do anything since all jobs were stopped with the cancel event.
+    events.push_back(CreateFinishScheduledJobEvent(durationEndNs3, {uid3}, {"App3"}, "job3"));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(2, durationMetrics.data_size());
+
+    // Only Uid 1 and 3 are logged. (odd hash value) + (offset of 5) % (shard count of 2) = 0
+    DurationMetricData data = durationMetrics.data(0);
+    ValidateAttributionUidDimension(data.dimensions_in_what(), util::SCHEDULED_JOB_STATE_CHANGED,
+                                    uid1);
+    ValidateDurationBucket(data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+                           durationEndNs1 - durationStartNs1);
+
+    data = durationMetrics.data(1);
+    ValidateAttributionUidDimension(data.dimensions_in_what(), util::SCHEDULED_JOB_STATE_CHANGED,
+                                    uid3);
+    ValidateDurationBucket(data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+                           stopAllNs - durationStartNs3);
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index f5a0158..81192b2 100644
--- a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -414,6 +414,93 @@
     }
 }
 
+TEST_F(GaugeMetricE2ePushedTest, TestDimensionalSampling) {
+    ShardOffsetProvider::getInstance().setShardOffset(5);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    GaugeMetric sampledGaugeMetric =
+            createGaugeMetric("GaugeSampledAppCrashesPerUid", appCrashMatcher.id(),
+                              GaugeMetric::FIRST_N_SAMPLES, nullopt, nullopt);
+    *sampledGaugeMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /* uid */});
+    *sampledGaugeMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    sampledGaugeMetric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_gauge_metric() = sampledGaugeMetric;
+
+    const int64_t configAddedTimeNs = 10 * NS_PER_SEC;  // 0:10
+    const int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            configAddedTimeNs, configAddedTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    int appUid1 = 1001;  // odd hash value
+    int appUid2 = 1002;  // even hash value
+    int appUid3 = 1003;  // odd hash value
+
+    const int64_t gaugeEventTimeNs1 = configAddedTimeNs + 20 * NS_PER_SEC;
+    const int64_t gaugeEventTimeNs2 = configAddedTimeNs + 40 * NS_PER_SEC;
+    const int64_t gaugeEventTimeNs3 = configAddedTimeNs + 60 * NS_PER_SEC;
+    const int64_t gaugeEventTimeNs4 = configAddedTimeNs + 100 * NS_PER_SEC;
+    const int64_t gaugeEventTimeNs5 = configAddedTimeNs + 110 * NS_PER_SEC;
+    const int64_t gaugeEventTimeNs6 = configAddedTimeNs + 150 * NS_PER_SEC;
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateAppCrashOccurredEvent(gaugeEventTimeNs1, appUid1));  // 0:30
+    events.push_back(CreateAppCrashOccurredEvent(gaugeEventTimeNs2, appUid2));  // 0:50
+    events.push_back(CreateAppCrashOccurredEvent(gaugeEventTimeNs3, appUid3));  // 1:10
+    events.push_back(CreateAppCrashOccurredEvent(gaugeEventTimeNs4, appUid1));  // 1:50
+    events.push_back(CreateAppCrashOccurredEvent(gaugeEventTimeNs5, appUid2));  // 2:00
+    events.push_back(CreateAppCrashOccurredEvent(gaugeEventTimeNs6, appUid3));  // 2:40
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_gauge_metrics());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ(2, gaugeMetrics.data_size());
+
+    // Only Uid 1 and 3 are logged. (odd hash value) + (offset of 5) % (shard count of 2) = 0
+    GaugeMetricData data = gaugeMetrics.data(0);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, appUid1);
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs,
+                             {gaugeEventTimeNs1, gaugeEventTimeNs4});
+
+    data = gaugeMetrics.data(1);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, appUid3);
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs,
+                             {gaugeEventTimeNs3, gaugeEventTimeNs6});
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/KllMetric_e2e_test.cpp b/statsd/tests/e2e/KllMetric_e2e_test.cpp
index 89ef229..a6100c2 100644
--- a/statsd/tests/e2e/KllMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/KllMetric_e2e_test.cpp
@@ -204,6 +204,97 @@
     ASSERT_EQ(0, processor->mMetricsManagers.size());
 }
 
+TEST_F(KllMetricE2eTest, TestDimensionalSampling) {
+    ShardOffsetProvider::getInstance().setShardOffset(5);
+
+    // Create config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher bleScanResultReceivedMatcher = CreateSimpleAtomMatcher(
+            "BleScanResultReceivedAtomMatcher", util::BLE_SCAN_RESULT_RECEIVED);
+    *config.add_atom_matcher() = bleScanResultReceivedMatcher;
+
+    // Create kll metric.
+    KllMetric sampledKllMetric =
+            createKllMetric("KllSampledBleScanResultsPerUid", bleScanResultReceivedMatcher,
+                            /*num_results=*/2, nullopt);
+    *sampledKllMetric.mutable_dimensions_in_what() =
+            CreateAttributionUidDimensions(util::BLE_SCAN_RESULT_RECEIVED, {Position::FIRST});
+    *sampledKllMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateAttributionUidDimensions(util::BLE_SCAN_RESULT_RECEIVED, {Position::FIRST});
+    sampledKllMetric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_kll_metric() = sampledKllMetric;
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            bucketStartTimeNs, bucketStartTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    int appUid1 = 1001;  // odd hash value
+    int appUid2 = 1002;  // even hash value
+    int appUid3 = 1003;  // odd hash value
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 20 * NS_PER_SEC,
+                                                      {appUid1}, {"tag1"}, 10));
+    events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 40 * NS_PER_SEC,
+                                                      {appUid2}, {"tag2"}, 10));
+    events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 60 * NS_PER_SEC,
+                                                      {appUid3}, {"tag3"}, 10));
+
+    events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 120 * NS_PER_SEC,
+                                                      {appUid1}, {"tag1"}, 11));
+    events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 140 * NS_PER_SEC,
+                                                      {appUid2}, {"tag2"}, 12));
+    events.push_back(CreateBleScanResultReceivedEvent(bucketStartTimeNs + 160 * NS_PER_SEC,
+                                                      {appUid3}, {"tag3"}, 13));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+
+    ConfigMetricsReport report = reports.reports(0);
+    ASSERT_EQ(report.metrics_size(), 1);
+    StatsLogReport metricReport = report.metrics(0);
+    EXPECT_EQ(metricReport.metric_id(), sampledKllMetric.id());
+    EXPECT_TRUE(metricReport.has_kll_metrics());
+    StatsLogReport::KllMetricDataWrapper kllMetrics;
+    sortMetricDataByDimensionsValue(metricReport.kll_metrics(), &kllMetrics);
+    ASSERT_EQ(kllMetrics.data_size(), 2);
+    EXPECT_EQ(kllMetrics.skipped_size(), 0);
+
+    // Only Uid 1 and 3 are logged. (odd hash value) + (offset of 5) % (shard count of 2) = 0
+    KllMetricData data = kllMetrics.data(0);
+    ValidateAttributionUidDimension(data.dimensions_in_what(), util::BLE_SCAN_RESULT_RECEIVED,
+                                    appUid1);
+    ValidateKllBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, {2},
+                      0);
+
+    data = kllMetrics.data(1);
+    ValidateAttributionUidDimension(data.dimensions_in_what(), util::BLE_SCAN_RESULT_RECEIVED,
+                                    appUid3);
+    ValidateKllBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, {2},
+                      0);
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index c64fb7b..190c5fb 100644
--- a/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -848,6 +848,96 @@
     EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), dropEvent.drop_time_millis());
 }
 
+TEST(GaugeMetricProducerTest, TestPullDimensionalSampling) {
+    ShardOffsetProvider::getInstance().setShardOffset(5);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    int triggerId = 5;
+    int shardCount = 2;
+    GaugeMetric sampledGaugeMetric = createGaugeMetric(
+            "GaugePullSampled", metricId, GaugeMetric::FIRST_N_SAMPLES, nullopt, triggerId);
+    sampledGaugeMetric.set_max_pull_delay_sec(INT_MAX);
+    *sampledGaugeMetric.mutable_dimensions_in_what() = CreateDimensions(tagId, {1});
+    *sampledGaugeMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(tagId, {1});
+    sampledGaugeMetric.mutable_dimensional_sampling_info()->set_shard_count(shardCount);
+    *config.add_gauge_metric() = sampledGaugeMetric;
+
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+    sp<EventMatcherWizard> eventMatcherWizard =
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 10, 1001, 5, 10));
+                data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 10, 1002, 10, 10));
+                data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 10, 1003, 15, 10));
+                return true;
+            }))
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 20, 1001, 6, 10));
+                data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 20, 1002, 12, 10));
+                data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 20, 1003, 18, 10));
+                return true;
+            }));
+
+    GaugeMetricProducer gaugeProducer(kConfigKey, sampledGaugeMetric,
+                                      -1 /*-1 meaning no condition*/, {}, wizard, protoHash,
+                                      logEventMatcherIndex, eventMatcherWizard, tagId, triggerId,
+                                      tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    SamplingInfo samplingInfo;
+    samplingInfo.shardCount = shardCount;
+    translateFieldMatcher(sampledGaugeMetric.dimensional_sampling_info().sampled_what_field(),
+                          &samplingInfo.sampledWhatFields);
+    gaugeProducer.setSamplingInfo(samplingInfo);
+    gaugeProducer.prepareFirstBucket();
+
+    LogEvent triggerEvent(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&triggerEvent, triggerId, bucketStartTimeNs + 10, 5);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
+
+    triggerEvent.setElapsedTimestampNs(bucketStartTimeNs + 20);
+    gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, triggerEvent);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000;
+    gaugeProducer.onDumpReport(dumpReportTimeNs, true /* include current buckets */, true,
+                               NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    backfillDimensionPath(&report);
+    backfillStartEndTimestamp(&report);
+    backfillAggregatedAtoms(&report);
+
+    EXPECT_TRUE(report.has_gauge_metrics());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(report.gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ(2, gaugeMetrics.data_size());
+    EXPECT_EQ(0, report.gauge_metrics().skipped_size());
+
+    // Only Uid 1 and 3 are logged. (odd hash value) + (offset of 5) % (shard count of 2) = 0
+    GaugeMetricData data = gaugeMetrics.data(0);
+    ValidateUidDimension(data.dimensions_in_what(), tagId, 1001);
+    ValidateGaugeBucketTimes(data.bucket_info(0), bucketStartTimeNs, dumpReportTimeNs,
+                             {bucketStartTimeNs + 10, bucketStartTimeNs + 20});
+
+    data = gaugeMetrics.data(1);
+    ValidateUidDimension(data.dimensions_in_what(), tagId, 1003);
+    ValidateGaugeBucketTimes(data.bucket_info(0), bucketStartTimeNs, dumpReportTimeNs,
+                             {bucketStartTimeNs + 10, bucketStartTimeNs + 20});
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
index 511413f..644b331 100644
--- a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
+++ b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
@@ -147,6 +147,22 @@
                                    stateGroupMap);
     }
 
+    static sp<NumericValueMetricProducer> createValueProducerWithSampling(
+            sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric,
+            const int pullAtomId = tagId) {
+        sp<NumericValueMetricProducer> valueProducer = createValueProducer(
+                pullerManager, metric, pullAtomId, /*conditionAfterFirstBucketPrepared=*/nullopt,
+                /*slicedStateAtoms=*/{}, /*stateGroupMap=*/{}, bucketStartTimeNs, bucketStartTimeNs,
+                /*eventMatcherWizard=*/nullptr);
+
+        SamplingInfo samplingInfo;
+        samplingInfo.shardCount = metric.dimensional_sampling_info().shard_count();
+        translateFieldMatcher(metric.dimensional_sampling_info().sampled_what_field(),
+                              &samplingInfo.sampledWhatFields);
+        valueProducer->setSamplingInfo(samplingInfo);
+        return valueProducer;
+    }
+
     static sp<NumericValueMetricProducer> createValueProducerWithBucketParams(
             sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric,
             const int64_t timeBaseNs, const int64_t startTimeNs, const int pullAtomId = tagId) {
@@ -7644,6 +7660,79 @@
     EXPECT_EQ(45, data.bucket_info(0).values(0).value_long());
 }
 
+TEST(NumericValueMetricProducerTest, TestDimensionalSampling) {
+    ShardOffsetProvider::getInstance().setShardOffset(5);
+
+    int shardCount = 2;
+    ValueMetric sampledValueMetric = NumericValueMetricProducerTestHelper::createMetric();
+    *sampledValueMetric.mutable_dimensions_in_what() = CreateDimensions(tagId, {1 /*uid*/});
+    *sampledValueMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(tagId, {1 /*uid*/});
+    sampledValueMetric.mutable_dimensional_sampling_info()->set_shard_count(shardCount);
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+            // First field is a dimension field and sampled what field.
+            // Second field is the value field.
+            // NumericValueMetricProducer initialized.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 1, 1001, 5, 10));
+                data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 1, 1002, 10, 10));
+                data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 1, 1003, 15, 10));
+                return true;
+            }))
+            // Dump report pull.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(
+                        makeUidLogEvent(tagId, bucketStartTimeNs + 10000000000, 1001, 6, 10));
+                data->push_back(
+                        makeUidLogEvent(tagId, bucketStartTimeNs + 10000000000, 1002, 12, 10));
+                data->push_back(
+                        makeUidLogEvent(tagId, bucketStartTimeNs + 10000000000, 1003, 18, 10));
+                return true;
+            }));
+
+    sp<NumericValueMetricProducer> valueProducer =
+            NumericValueMetricProducerTestHelper::createValueProducerWithSampling(
+                    pullerManager, sampledValueMetric);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000;
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include current buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    backfillDimensionPath(&report);
+    backfillStartEndTimestamp(&report);
+    EXPECT_TRUE(report.has_value_metrics());
+    StatsLogReport::ValueMetricDataWrapper valueMetrics;
+    sortMetricDataByDimensionsValue(report.value_metrics(), &valueMetrics);
+    ASSERT_EQ(2, valueMetrics.data_size());
+    EXPECT_EQ(0, report.value_metrics().skipped_size());
+
+    // Only Uid 1, 3, 4 are logged. (odd hash value) + (offset of 5) % (shard count of 2) = 0
+    ValueMetricData data = valueMetrics.data(0);
+    ValidateUidDimension(data.dimensions_in_what(), tagId, 1001);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + 10000000000,
+                        {1}, -1,
+                        0);  // Diff of 5 and 6
+
+    data = valueMetrics.data(1);
+    ValidateUidDimension(data.dimensions_in_what(), tagId, 1003);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + 10000000000,
+                        {3}, -1,
+                        0);  // Diff of 15 and 18
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
index 85cc34f..2567936 100644
--- a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -1462,6 +1462,305 @@
     EXPECT_FALSE(metricsManager->isConfigValid());
 }
 
+TEST_F(MetricsManagerUtilTest, TestSampledMetrics) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");
+
+    AtomMatcher appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+    AtomMatcher bleScanResultReceivedMatcher = CreateSimpleAtomMatcher(
+            "BleScanResultReceivedAtomMatcher", util::BLE_SCAN_RESULT_RECEIVED);
+    *config.add_atom_matcher() = bleScanResultReceivedMatcher;
+
+    Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
+            CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    CountMetric sampledCountMetric =
+            createCountMetric("CountSampledAppCrashesPerUid", appCrashMatcher.id(), nullopt, {});
+    *sampledCountMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    *sampledCountMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    sampledCountMetric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = sampledCountMetric;
+
+    CountMetric unsampledCountMetric =
+            createCountMetric("CountAppCrashesPerUid", appCrashMatcher.id(), nullopt, {});
+    *unsampledCountMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    *config.add_count_metric() = unsampledCountMetric;
+
+    DurationMetric sampledDurationMetric = createDurationMetric(
+            "DurationSampledWakelockPerUid", holdingWakelockPredicate.id(), nullopt, {});
+    *sampledDurationMetric.mutable_dimensions_in_what() =
+            CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    *sampledDurationMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    sampledDurationMetric.mutable_dimensional_sampling_info()->set_shard_count(4);
+    *config.add_duration_metric() = sampledDurationMetric;
+
+    DurationMetric unsampledDurationMetric = createDurationMetric(
+            "DurationWakelockPerUid", holdingWakelockPredicate.id(), nullopt, {});
+    unsampledDurationMetric.set_aggregation_type(DurationMetric::SUM);
+    *unsampledDurationMetric.mutable_dimensions_in_what() =
+            CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    *config.add_duration_metric() = unsampledDurationMetric;
+
+    ValueMetric sampledValueMetric =
+            createValueMetric("ValueSampledBleScanResultsPerUid", bleScanResultReceivedMatcher,
+                              /*num_results=*/2, nullopt, {});
+    *sampledValueMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::BLE_SCAN_RESULT_RECEIVED, {1 /* uid */});
+    *sampledValueMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(util::BLE_SCAN_RESULT_RECEIVED, {1 /*uid*/});
+    sampledValueMetric.mutable_dimensional_sampling_info()->set_shard_count(6);
+    *config.add_value_metric() = sampledValueMetric;
+
+    ValueMetric unsampledValueMetric =
+            createValueMetric("ValueBleScanResultsPerUid", bleScanResultReceivedMatcher,
+                              /*num_results=*/2, nullopt, {});
+    *unsampledValueMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::BLE_SCAN_RESULT_RECEIVED, {1 /* uid */});
+    *config.add_value_metric() = unsampledValueMetric;
+
+    KllMetric sampledKllMetric =
+            createKllMetric("KllSampledBleScanResultsPerUid", bleScanResultReceivedMatcher,
+                            /*num_results=*/2, nullopt);
+    *sampledKllMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::BLE_SCAN_RESULT_RECEIVED, {1 /* uid */});
+    *sampledKllMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(util::BLE_SCAN_RESULT_RECEIVED, {1 /*uid*/});
+    sampledKllMetric.mutable_dimensional_sampling_info()->set_shard_count(8);
+    *config.add_kll_metric() = sampledKllMetric;
+
+    KllMetric unsampledKllMetric = createKllMetric(
+            "KllBleScanResultsPerUid", bleScanResultReceivedMatcher, /*num_results=*/2, nullopt);
+    *unsampledKllMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::BLE_SCAN_RESULT_RECEIVED, {1 /* uid */});
+    *config.add_kll_metric() = unsampledKllMetric;
+
+    GaugeMetric sampledGaugeMetric =
+            createGaugeMetric("GaugeSampledAppCrashesPerUid", appCrashMatcher.id(),
+                              GaugeMetric::FIRST_N_SAMPLES, nullopt, nullopt);
+    *sampledGaugeMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /* uid */});
+    *sampledGaugeMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    sampledGaugeMetric.mutable_dimensional_sampling_info()->set_shard_count(10);
+    *config.add_gauge_metric() = sampledGaugeMetric;
+
+    GaugeMetric unsampledGaugeMetric =
+            createGaugeMetric("GaugeAppCrashesPerUid", appCrashMatcher.id(),
+                              GaugeMetric::FIRST_N_SAMPLES, nullopt, nullopt);
+    *unsampledGaugeMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /* uid */});
+    *config.add_gauge_metric() = unsampledGaugeMetric;
+
+    ConfigKey key(123, 987);
+    uint64_t timeNs = 456;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    sp<UidMap> uidMap;
+    sp<MetricsManager> metricsManager =
+            new MetricsManager(key, config, timeNs, timeNs, uidMap, pullerManager,
+                               anomalyAlarmMonitor, periodicAlarmMonitor);
+    ASSERT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(10, metricsManager->mAllMetricProducers.size());
+
+    sp<MetricProducer> sampledCountMetricProducer = metricsManager->mAllMetricProducers[0];
+    sp<MetricProducer> unsampledCountMetricProducer = metricsManager->mAllMetricProducers[1];
+    sp<MetricProducer> sampledDurationMetricProducer = metricsManager->mAllMetricProducers[2];
+    sp<MetricProducer> unsampledDurationMetricProducer = metricsManager->mAllMetricProducers[3];
+    sp<MetricProducer> sampledValueMetricProducer = metricsManager->mAllMetricProducers[4];
+    sp<MetricProducer> unsampledValueMetricProducer = metricsManager->mAllMetricProducers[5];
+    sp<MetricProducer> sampledKllMetricProducer = metricsManager->mAllMetricProducers[6];
+    sp<MetricProducer> unsampledKllMetricProducer = metricsManager->mAllMetricProducers[7];
+    sp<MetricProducer> sampledGaugeMetricProducer = metricsManager->mAllMetricProducers[8];
+    sp<MetricProducer> unsampledGaugeMetricProducer = metricsManager->mAllMetricProducers[9];
+
+    // Check shard count is set correctly for sampled metrics or set to default.
+    EXPECT_EQ(2, sampledCountMetricProducer->mShardCount);
+    EXPECT_EQ(0, unsampledCountMetricProducer->mShardCount);
+    EXPECT_EQ(4, sampledDurationMetricProducer->mShardCount);
+    EXPECT_EQ(0, unsampledDurationMetricProducer->mShardCount);
+    EXPECT_EQ(6, sampledValueMetricProducer->mShardCount);
+    EXPECT_EQ(0, unsampledValueMetricProducer->mShardCount);
+    EXPECT_EQ(8, sampledKllMetricProducer->mShardCount);
+    EXPECT_EQ(0, unsampledKllMetricProducer->mShardCount);
+    EXPECT_EQ(10, sampledGaugeMetricProducer->mShardCount);
+    EXPECT_EQ(0, unsampledGaugeMetricProducer->mShardCount);
+
+    // Check sampled what fields is set correctly or empty.
+    EXPECT_EQ(1, sampledCountMetricProducer->mSampledWhatFields.size());
+    EXPECT_EQ(true, unsampledCountMetricProducer->mSampledWhatFields.empty());
+    EXPECT_EQ(1, sampledDurationMetricProducer->mSampledWhatFields.size());
+    EXPECT_EQ(true, unsampledDurationMetricProducer->mSampledWhatFields.empty());
+    EXPECT_EQ(1, sampledValueMetricProducer->mSampledWhatFields.size());
+    EXPECT_EQ(true, unsampledValueMetricProducer->mSampledWhatFields.empty());
+    EXPECT_EQ(1, sampledKllMetricProducer->mSampledWhatFields.size());
+    EXPECT_EQ(true, unsampledKllMetricProducer->mSampledWhatFields.empty());
+    EXPECT_EQ(1, sampledGaugeMetricProducer->mSampledWhatFields.size());
+    EXPECT_EQ(true, unsampledGaugeMetricProducer->mSampledWhatFields.empty());
+}
+
+TEST_F(MetricsManagerUtilTest, TestMetricHasShardCountButNoSampledField) {
+    AtomMatcher appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    CountMetric metric =
+            createCountMetric("CountSampledAppCrashesPerUid", appCrashMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(
+                      INVALID_CONFIG_REASON_METRIC_DIMENSIONAL_SAMPLING_INFO_MISSING_SAMPLED_FIELD,
+                      metric.id()));
+}
+
+TEST_F(MetricsManagerUtilTest, TestMetricHasSampledFieldIncorrectShardCount) {
+    AtomMatcher appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    CountMetric metric =
+            createCountMetric("CountSampledAppCrashesPerUid", appCrashMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    *config.add_count_metric() = metric;
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(
+                      INVALID_CONFIG_REASON_METRIC_DIMENSIONAL_SAMPLING_INFO_INCORRECT_SHARD_COUNT,
+                      metric.id()));
+}
+
+TEST_F(MetricsManagerUtilTest, TestMetricHasMultipleSampledFields) {
+    AtomMatcher appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    CountMetric metric =
+            createCountMetric("CountSampledAppCrashesPerUid", appCrashMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/, 2 /*event_type*/});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELD_INCORRECT_SIZE,
+                                  metric.id()));
+}
+
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_PositionALL) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::ALL});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::ALL});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELD_INCORRECT_SIZE,
+                                  metric.id()));
+}
+
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_PositionFIRST) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::FIRST});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::FIRST});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_PositionLAST) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::LAST});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::LAST});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_PositionANY) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::ANY});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::ALL});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELD_INCORRECT_SIZE,
+                                  metric.id()));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/statsd_test_util.cpp b/statsd/tests/statsd_test_util.cpp
index c584c81..fae6993 100644
--- a/statsd/tests/statsd_test_util.cpp
+++ b/statsd/tests/statsd_test_util.cpp
@@ -86,6 +86,11 @@
                                                      ScheduledJobStateChanged::FINISHED);
 }
 
+AtomMatcher CreateScheduleScheduledJobAtomMatcher() {
+    return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobSchedule",
+                                                     ScheduledJobStateChanged::SCHEDULED);
+}
+
 AtomMatcher CreateScreenBrightnessChangedAtomMatcher() {
     AtomMatcher atom_matcher;
     atom_matcher.set_id(StringToId("ScreenBrightnessChanged"));
@@ -1018,6 +1023,15 @@
                                                ScheduledJobStateChanged::FINISHED, timestampNs);
 }
 
+// Create log event when scheduled job is scheduled.
+std::unique_ptr<LogEvent> CreateScheduleScheduledJobEvent(uint64_t timestampNs,
+                                                          const vector<int>& attributionUids,
+                                                          const vector<string>& attributionTags,
+                                                          const string& jobName) {
+    return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName,
+                                               ScheduledJobStateChanged::SCHEDULED, timestampNs);
+}
+
 std::unique_ptr<LogEvent> CreateTestAtomReportedEventVariableRepeatedFields(
         uint64_t timestampNs, const vector<int>& repeatedIntField,
         const vector<int64_t>& repeatedLongField, const vector<float>& repeatedFloatField,
@@ -1311,6 +1325,22 @@
     return logEvent;
 }
 
+std::unique_ptr<LogEvent> CreateBleScanResultReceivedEvent(uint64_t timestampNs,
+                                                           const vector<int>& attributionUids,
+                                                           const vector<string>& attributionTags,
+                                                           const int numResults) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::BLE_SCAN_RESULT_RECEIVED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_writeInt32(statsEvent, numResults);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
 sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
                                               const StatsdConfig& config, const ConfigKey& key,
                                               const shared_ptr<IPullAtomCallback>& puller,
diff --git a/statsd/tests/statsd_test_util.h b/statsd/tests/statsd_test_util.h
index 4513731..af5ce3e 100644
--- a/statsd/tests/statsd_test_util.h
+++ b/statsd/tests/statsd_test_util.h
@@ -107,6 +107,9 @@
 // Create AtomMatcher proto for a scheduled job is done.
 AtomMatcher CreateFinishScheduledJobAtomMatcher();
 
+// Create AtomMatcher proto for cancelling a scheduled job.
+AtomMatcher CreateScheduleScheduledJobAtomMatcher();
+
 // Create AtomMatcher proto for screen brightness state changed.
 AtomMatcher CreateScreenBrightnessChangedAtomMatcher();
 
@@ -378,6 +381,12 @@
                                                         const vector<string>& attributionTags,
                                                         const string& jobName);
 
+// Create log event when scheduled job schedules.
+std::unique_ptr<LogEvent> CreateScheduleScheduledJobEvent(uint64_t timestampNs,
+                                                          const vector<int>& attributionUids,
+                                                          const vector<string>& attributionTags,
+                                                          const string& jobName);
+
 // Create log event when battery saver starts.
 std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs);
 // Create log event when battery saver stops.
@@ -441,6 +450,11 @@
         AppStartOccurred::TransitionType type, const string& activity_name,
         const string& calling_pkg_name, const bool is_instant_app, int64_t activity_start_msec);
 
+std::unique_ptr<LogEvent> CreateBleScanResultReceivedEvent(uint64_t timestampNs,
+                                                           const vector<int>& attributionUids,
+                                                           const vector<string>& attributionTags,
+                                                           const int numResults);
+
 std::unique_ptr<LogEvent> CreateTestAtomReportedEventVariableRepeatedFields(
         uint64_t timestampNs, const vector<int>& repeatedIntField,
         const vector<int64_t>& repeatedLongField, const vector<float>& repeatedFloatField,