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,