summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Tej Singh <singhtejinder@google.com> 2020-11-13 00:31:07 -0800
committer Tej Singh <singhtejinder@google.com> 2020-12-01 00:02:07 -0800
commit933b6e35312d6cd507f373d463a819a4f9c15f13 (patch)
tree6c63a845b93c1d083235b6076af31a2e7679d580
parente935342b74ec742da46cf3bef305cd4aaf5e55e6 (diff)
Init duration metric if condition existed
Initialize a duration metric with the dimensions that are true if the "what" predicate previously existed. Assumes that the internal dimensions are a superset of the dimensions_in_what, the condition links, and the state links Test: atest statsd_test Bug: 167491517 Change-Id: I8dd984fb9cb575d6d541f80cb3b1e626ce4e5f27
-rw-r--r--cmds/statsd/src/condition/CombinationConditionTracker.h10
-rw-r--r--cmds/statsd/src/condition/ConditionTracker.h5
-rw-r--r--cmds/statsd/src/condition/ConditionWizard.h6
-rw-r--r--cmds/statsd/src/condition/SimpleConditionTracker.h11
-rw-r--r--cmds/statsd/src/metrics/DurationMetricProducer.cpp74
-rw-r--r--cmds/statsd/src/metrics/DurationMetricProducer.h13
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp12
-rw-r--r--cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp297
-rw-r--r--cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp52
-rw-r--r--cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp5
-rw-r--r--cmds/statsd/tests/statsd_test_util.cpp15
-rw-r--r--cmds/statsd/tests/statsd_test_util.h2
12 files changed, 419 insertions, 83 deletions
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 672d61c82268..d355146c13ef 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -85,16 +85,14 @@ public:
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensions) const override;
- void getTrueSlicedDimensions(
- const std::vector<sp<ConditionTracker>>& allConditions,
- std::set<HashableDimensionKey>* dimensions) const override {
+ const std::map<HashableDimensionKey, int>* getSlicedDimensionMap(
+ const std::vector<sp<ConditionTracker>>& allConditions) const override {
if (mSlicedChildren.size() == 1) {
- return allConditions[mSlicedChildren.front()]->getTrueSlicedDimensions(
- allConditions, dimensions);
+ return allConditions[mSlicedChildren.front()]->getSlicedDimensionMap(allConditions);
}
+ return nullptr;
}
-
private:
LogicalOperation mLogicalOperation;
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 5a6b8cf334eb..7af022a5677a 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -135,9 +135,8 @@ public:
return mProtoHash;
}
- virtual void getTrueSlicedDimensions(
- const std::vector<sp<ConditionTracker>>& allConditions,
- std::set<HashableDimensionKey>* dimensions) const = 0;
+ virtual const std::map<HashableDimensionKey, int>* getSlicedDimensionMap(
+ const std::vector<sp<ConditionTracker>>& allConditions) const = 0;
virtual bool IsChangedDimensionTrackable() const = 0;
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index 892647910d9f..43db94cfa47d 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -53,9 +53,9 @@ public:
ConditionState getUnSlicedPartConditionState(const int index) {
return mAllConditions[index]->getUnSlicedPartConditionState();
}
- void getTrueSlicedDimensions(const int index,
- std::set<HashableDimensionKey>* trueDimensions) const {
- return mAllConditions[index]->getTrueSlicedDimensions(mAllConditions, trueDimensions);
+
+ const std::map<HashableDimensionKey, int>* getSlicedDimensionMap(const int index) const {
+ return mAllConditions[index]->getSlicedDimensionMap(mAllConditions);
}
private:
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 7a8b40108448..2fbc8a7b4ea8 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -74,14 +74,9 @@ public:
}
}
- void getTrueSlicedDimensions(
- const std::vector<sp<ConditionTracker>>& allConditions,
- std::set<HashableDimensionKey>* dimensions) const override {
- for (const auto& itr : mSlicedConditionState) {
- if (itr.second > 0) {
- dimensions->insert(itr.first);
- }
- }
+ const std::map<HashableDimensionKey, int>* getSlicedDimensionMap(
+ const std::vector<sp<ConditionTracker>>& allConditions) const override {
+ return &mSlicedConditionState;
}
bool IsChangedDimensionTrackable() const override { return true; }
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 8869241ab8aa..bbaa20f232a9 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -67,8 +67,8 @@ const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
DurationMetricProducer::DurationMetricProducer(
const ConfigKey& key, const DurationMetric& metric, const int conditionIndex,
- const vector<ConditionState>& initialConditionCache, const int startIndex,
- const int stopIndex, const int stopAllIndex, const bool nesting,
+ const vector<ConditionState>& initialConditionCache, const int whatIndex,
+ const int startIndex, const int stopIndex, const int stopAllIndex, const bool nesting,
const sp<ConditionWizard>& wizard, const uint64_t protoHash,
const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs,
const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
@@ -140,6 +140,8 @@ DurationMetricProducer::DurationMetricProducer(
mCurrentBucketStartTimeNs = startTimeNs;
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
(long long)mBucketSizeNs, (long long)mTimeBaseNs);
+
+ initTrueDimensions(whatIndex, startTimeNs);
}
DurationMetricProducer::~DurationMetricProducer() {
@@ -224,6 +226,23 @@ bool DurationMetricProducer::onConfigUpdatedLocked(
return true;
}
+void DurationMetricProducer::initTrueDimensions(const int whatIndex, const int64_t startTimeNs) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ // Currently whatIndex will only be -1 in tests. In the future, we might want to avoid creating
+ // a ConditionTracker if the condition is only used in the "what" of a duration metric. In that
+ // scenario, -1 can also be passed.
+ if (whatIndex == -1) {
+ return;
+ }
+ const map<HashableDimensionKey, int>* slicedWhatMap = mWizard->getSlicedDimensionMap(whatIndex);
+ for (const auto& [internalDimKey, count] : *slicedWhatMap) {
+ for (int i = 0; i < count; i++) {
+ // Fake start events.
+ handleMatchedLogEventValuesLocked(mStartIndex, internalDimKey.getValues(), startTimeNs);
+ }
+ }
+}
+
sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(
const Alert &alert, const sp<AlarmMonitor>& anomalyAlarmMonitor) {
std::lock_guard<std::mutex> lock(mMutex);
@@ -330,14 +349,14 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool conditio
// state based on the new unsliced condition state.
if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr ||
(dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) {
- std::set<HashableDimensionKey> trueConditionDimensions;
- mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, &trueConditionDimensions);
+ const map<HashableDimensionKey, int>* slicedConditionMap =
+ mWizard->getSlicedDimensionMap(mConditionTrackerIndex);
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
HashableDimensionKey linkedConditionDimensionKey;
getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0],
&linkedConditionDimensionKey);
- if (trueConditionDimensions.find(linkedConditionDimensionKey) !=
- trueConditionDimensions.end()) {
+ const auto& slicedConditionIt = slicedConditionMap->find(linkedConditionDimensionKey);
+ if (slicedConditionIt != slicedConditionMap->end() && slicedConditionIt->second > 0) {
whatIt.second->onConditionChanged(currentUnSlicedPartCondition, eventTime);
}
}
@@ -595,8 +614,9 @@ bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey
}
void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey,
- const ConditionKey& conditionKeys,
- bool condition, const LogEvent& event) {
+ const ConditionKey& conditionKeys, bool condition,
+ const int64_t eventTimeNs,
+ const vector<FieldValue>& eventValues) {
const auto& whatKey = eventKey.getDimensionKeyInWhat();
auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
@@ -608,20 +628,17 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey
auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
if (mUseWhatDimensionAsInternalDimension) {
- it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
+ it->second->noteStart(whatKey, condition, eventTimeNs, conditionKeys);
return;
}
if (mInternalDimensions.empty()) {
- it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(),
- conditionKeys);
+ it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, eventTimeNs, conditionKeys);
} else {
HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
- filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
- it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(),
- conditionKeys);
+ filterValues(mInternalDimensions, eventValues, &dimensionKey);
+ it->second->noteStart(dimensionKey, condition, eventTimeNs, conditionKeys);
}
-
}
void DurationMetricProducer::onMatchedLogEventInternalLocked(
@@ -633,26 +650,32 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked(
void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
const LogEvent& event) {
- int64_t eventTimeNs = event.GetElapsedTimestampNs();
+ handleMatchedLogEventValuesLocked(matcherIndex, event.getValues(),
+ event.GetElapsedTimestampNs());
+}
+
+void DurationMetricProducer::handleMatchedLogEventValuesLocked(const size_t matcherIndex,
+ const vector<FieldValue>& values,
+ const int64_t eventTimeNs) {
if (eventTimeNs < mTimeBaseNs) {
return;
}
if (mIsActive) {
- flushIfNeededLocked(event.GetElapsedTimestampNs());
+ flushIfNeededLocked(eventTimeNs);
}
// Handles Stopall events.
if ((int)matcherIndex == mStopAllIndex) {
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
- whatIt.second->noteStopAll(event.GetElapsedTimestampNs());
+ whatIt.second->noteStopAll(eventTimeNs);
}
return;
}
HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY;
if (!mDimensionsInWhat.empty()) {
- filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
+ filterValues(mDimensionsInWhat, values, &dimensionInWhat);
}
// Stores atom id to primary key pairs for each state atom that the metric is
@@ -663,8 +686,7 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
// field values from the log event. These values will form a primary key
// that will be used to query StateTracker for the correct state value.
for (const auto& stateLink : mMetric2StateLinks) {
- getDimensionForState(event.getValues(), stateLink,
- &statePrimaryKeys[stateLink.stateAtomId]);
+ getDimensionForState(values, stateLink, &statePrimaryKeys[stateLink.stateAtomId]);
}
// For each sliced state, query StateTracker for the state value using
@@ -695,19 +717,19 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
if (mUseWhatDimensionAsInternalDimension) {
auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
- whatIt->second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
+ whatIt->second->noteStop(dimensionInWhat, eventTimeNs, false);
}
return;
}
HashableDimensionKey internalDimensionKey = DEFAULT_DIMENSION_KEY;
if (!mInternalDimensions.empty()) {
- filterValues(mInternalDimensions, event.getValues(), &internalDimensionKey);
+ filterValues(mInternalDimensions, values, &internalDimensionKey);
}
auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
- whatIt->second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(), false);
+ whatIt->second->noteStop(internalDimensionKey, eventTimeNs, false);
}
return;
}
@@ -716,7 +738,7 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
ConditionKey conditionKey;
if (mConditionSliced) {
for (const auto& link : mMetric2ConditionLinks) {
- getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
+ getDimensionForCondition(values, link, &conditionKey[link.conditionId]);
}
auto conditionState =
@@ -731,7 +753,7 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
condition = condition && mIsActive;
handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition,
- event);
+ eventTimeNs, values);
}
size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 5feb09fc1c98..9fb586f7262f 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -40,8 +40,8 @@ class DurationMetricProducer : public MetricProducer {
public:
DurationMetricProducer(
const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex,
- const vector<ConditionState>& initialConditionCache, const int startIndex,
- const int stopIndex, const int stopAllIndex, const bool nesting,
+ const vector<ConditionState>& initialConditionCache, const int whatIndex,
+ const int startIndex, const int stopIndex, const int stopAllIndex, const bool nesting,
const sp<ConditionWizard>& wizard, const uint64_t protoHash,
const FieldMatcher& internalDimensions, const int64_t timeBaseNs,
const int64_t startTimeNs,
@@ -74,8 +74,15 @@ protected:
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
private:
+ // Initializes true dimensions of the 'what' predicate. Only to be called during initialization.
+ void initTrueDimensions(const int whatIndex, const int64_t startTimeNs);
+
+ void handleMatchedLogEventValuesLocked(const size_t matcherIndex,
+ const std::vector<FieldValue>& values,
+ const int64_t eventTimeNs);
void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys,
- bool condition, const LogEvent& event);
+ bool condition, const int64_t eventTimeNs,
+ const vector<FieldValue>& eventValues);
void onDumpReportLocked(const int64_t dumpTimeNs,
const bool include_current_partial_bucket,
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 4474df4346cf..ff7938cc7243 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -450,7 +450,8 @@ optional<sp<MetricProducer>> createDurationMetricProducerAndUpdateMetadata(
return nullopt;
}
- const Predicate& durationWhat = config.predicate(what_it->second);
+ const int whatIndex = what_it->second;
+ const Predicate& durationWhat = config.predicate(whatIndex);
if (durationWhat.contents_case() != Predicate::ContentsCase::kSimplePredicate) {
ALOGE("DurationMetric's \"what\" must be a simple condition");
return nullopt;
@@ -536,10 +537,11 @@ optional<sp<MetricProducer>> createDurationMetricProducerAndUpdateMetadata(
return nullopt;
}
- return {new DurationMetricProducer(
- key, metric, conditionIndex, initialConditionCache, startIndex, stopIndex, stopAllIndex,
- nesting, wizard, metricHash, internalDimensions, timeBaseNs, currentTimeNs,
- eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap)};
+ return {new DurationMetricProducer(key, metric, conditionIndex, initialConditionCache,
+ whatIndex, startIndex, stopIndex, stopAllIndex, nesting,
+ wizard, metricHash, internalDimensions, timeBaseNs,
+ currentTimeNs, eventActivationMap, eventDeactivationMap,
+ slicedStateAtoms, stateGroupMap)};
}
optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata(
diff --git a/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp b/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
index e01a0b63a0ca..22c4f5de2537 100644
--- a/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
@@ -14,8 +14,6 @@
#include <gtest/gtest.h>
-#include <thread> // std::this_thread::sleep_for
-
#include "android-base/stringprintf.h"
#include "src/StatsLogProcessor.h"
#include "src/storage/StorageManager.h"
@@ -28,6 +26,7 @@ namespace statsd {
#ifdef __ANDROID__
#define STATS_DATA_DIR "/data/misc/stats-data"
using android::base::StringPrintf;
+using namespace std;
namespace {
@@ -298,6 +297,300 @@ TEST_P(ConfigUpdateE2eTest, TestConfigTtl) {
&buffer);
}
+TEST(ConfigUpdateE2eTest, TestNewDurationExistingWhat) {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT");
+ *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+ *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+ Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+ *config.add_predicate() = holdingWakelockPredicate;
+
+ ConfigKey key(123, 987);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL;
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+
+ int app1Uid = 123;
+ vector<int> attributionUids1 = {app1Uid};
+ vector<string> attributionTags1 = {"App1"};
+ // Create a wakelock acquire, causing the condition to be true.
+ unique_ptr<LogEvent> event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC,
+ attributionUids1, attributionTags1,
+ "wl1"); // 0:10
+ processor->OnLogEvent(event.get());
+
+ // Add metric.
+ DurationMetric* durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("WakelockDuration"));
+ durationMetric->set_what(holdingWakelockPredicate.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00
+ processor->OnConfigUpdated(updateTimeNs, key, config, /*modular*/ true);
+
+ event = CreateReleaseWakelockEvent(bucketStartTimeNs + 80 * NS_PER_SEC, attributionUids1,
+ attributionTags1,
+ "wl1"); // 1:20
+ processor->OnLogEvent(event.get());
+ uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; // 1:30
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+ ASSERT_EQ(reports.reports_size(), 1);
+ ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+
+ StatsLogReport::DurationMetricDataWrapper metricData;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metricData);
+ ASSERT_EQ(metricData.data_size(), 1);
+ DurationMetricData data = metricData.data(0);
+ ASSERT_EQ(data.bucket_info_size(), 1);
+
+ DurationBucketInfo bucketInfo = data.bucket_info(0);
+ EXPECT_EQ(bucketInfo.start_bucket_elapsed_nanos(), updateTimeNs);
+ EXPECT_EQ(bucketInfo.end_bucket_elapsed_nanos(), dumpTimeNs);
+ EXPECT_EQ(bucketInfo.duration_nanos(), 20 * NS_PER_SEC);
+}
+
+TEST(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedCondition) {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT");
+ *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+ *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+ *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
+ *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
+
+ Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+ // The predicate is dimensioning by first attribution node by uid.
+ *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
+ CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ *config.add_predicate() = holdingWakelockPredicate;
+
+ Predicate isInBackgroundPredicate = CreateIsInBackgroundPredicate();
+ *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
+ CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid*/});
+ *config.add_predicate() = isInBackgroundPredicate;
+
+ ConfigKey key(123, 987);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL;
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+
+ int app1Uid = 123, app2Uid = 456;
+ vector<int> attributionUids1 = {app1Uid};
+ vector<string> attributionTags1 = {"App1"};
+ vector<int> attributionUids2 = {app2Uid};
+ vector<string> attributionTags2 = {"App2"};
+ unique_ptr<LogEvent> event = CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC,
+ attributionUids1, attributionTags1,
+ "wl1"); // 0:10
+ processor->OnLogEvent(event.get());
+ event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 22 * NS_PER_SEC, app1Uid); // 0:22
+ processor->OnLogEvent(event.get());
+ event = CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC, attributionUids2,
+ attributionTags2,
+ "wl1"); // 0:35
+ processor->OnLogEvent(event.get());
+
+ // Add metric.
+ DurationMetric* durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("WakelockDuration"));
+ durationMetric->set_what(holdingWakelockPredicate.id());
+ durationMetric->set_condition(isInBackgroundPredicate.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ // The metric is dimensioning by first attribution node and only by uid.
+ *durationMetric->mutable_dimensions_in_what() =
+ CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ durationMetric->set_bucket(FIVE_MINUTES);
+ // Links between wakelock state atom and condition of app is in background.
+ auto links = durationMetric->add_links();
+ links->set_condition(isInBackgroundPredicate.id());
+ *links->mutable_fields_in_what() =
+ CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ *links->mutable_fields_in_condition() =
+ CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid*/});
+
+ uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00
+ processor->OnConfigUpdated(updateTimeNs, key, config, /*modular*/ true);
+
+ event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 73 * NS_PER_SEC, app2Uid); // 1:13
+ processor->OnLogEvent(event.get());
+ event = CreateReleaseWakelockEvent(bucketStartTimeNs + 84 * NS_PER_SEC, attributionUids1,
+ attributionTags1, "wl1"); // 1:24
+ processor->OnLogEvent(event.get());
+
+ uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; // 1:30
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+ ASSERT_EQ(reports.reports_size(), 1);
+ ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+
+ StatsLogReport::DurationMetricDataWrapper metricData;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metricData);
+ ASSERT_EQ(metricData.data_size(), 2);
+
+ DurationMetricData data = metricData.data(0);
+ ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED,
+ app1Uid);
+ ASSERT_EQ(data.bucket_info_size(), 1);
+ DurationBucketInfo bucketInfo = data.bucket_info(0);
+ EXPECT_EQ(bucketInfo.duration_nanos(), 24 * NS_PER_SEC);
+
+ data = metricData.data(1);
+ ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED,
+ app2Uid);
+ ASSERT_EQ(data.bucket_info_size(), 1);
+ bucketInfo = data.bucket_info(0);
+ EXPECT_EQ(bucketInfo.duration_nanos(), 17 * NS_PER_SEC);
+}
+
+TEST(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedState) {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT");
+ *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+ *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+ Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+ // The predicate is dimensioning by first attribution node by uid.
+ *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
+ CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ *config.add_predicate() = holdingWakelockPredicate;
+
+ auto uidProcessState = CreateUidProcessState();
+ *config.add_state() = uidProcessState;
+
+ // Count metric. We don't care about this one. Only use it so the StateTracker gets persisted.
+ CountMetric* countMetric = config.add_count_metric();
+ countMetric->set_id(StringToId("Tmp"));
+ countMetric->set_what(config.atom_matcher(0).id());
+ countMetric->add_slice_by_state(uidProcessState.id());
+ // The metric is dimensioning by first attribution node and only by uid.
+ *countMetric->mutable_dimensions_in_what() =
+ CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ countMetric->set_bucket(FIVE_MINUTES);
+ auto stateLink = countMetric->add_state_link();
+ stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED);
+ *stateLink->mutable_fields_in_what() =
+ CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ *stateLink->mutable_fields_in_state() =
+ CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/});
+ config.add_no_report_metric(countMetric->id());
+
+ ConfigKey key(123, 987);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL;
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+
+ int app1Uid = 123, app2Uid = 456;
+ vector<int> attributionUids1 = {app1Uid};
+ vector<string> attributionTags1 = {"App1"};
+ vector<int> attributionUids2 = {app2Uid};
+ vector<string> attributionTags2 = {"App2"};
+ unique_ptr<LogEvent> event = CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 10 * NS_PER_SEC, app1Uid,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); // 0:10
+ processor->OnLogEvent(event.get());
+ event = CreateAcquireWakelockEvent(bucketStartTimeNs + 22 * NS_PER_SEC, attributionUids1,
+ attributionTags1,
+ "wl1"); // 0:22
+ processor->OnLogEvent(event.get());
+ event = CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 30 * NS_PER_SEC, app2Uid,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND); // 0:30
+ processor->OnLogEvent(event.get());
+
+ // Add metric.
+ DurationMetric* durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("WakelockDuration"));
+ durationMetric->set_what(holdingWakelockPredicate.id());
+ durationMetric->add_slice_by_state(uidProcessState.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ // The metric is dimensioning by first attribution node and only by uid.
+ *durationMetric->mutable_dimensions_in_what() =
+ CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ durationMetric->set_bucket(FIVE_MINUTES);
+ // Links between wakelock state atom and condition of app is in background.
+ stateLink = durationMetric->add_state_link();
+ stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED);
+ *stateLink->mutable_fields_in_what() =
+ CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ *stateLink->mutable_fields_in_state() =
+ CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/});
+
+ uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00
+ processor->OnConfigUpdated(updateTimeNs, key, config, /*modular*/ true);
+
+ event = CreateAcquireWakelockEvent(bucketStartTimeNs + 72 * NS_PER_SEC, attributionUids2,
+ attributionTags2,
+ "wl1"); // 1:13
+ processor->OnLogEvent(event.get());
+ event = CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 75 * NS_PER_SEC, app1Uid,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND); // 1:15
+ processor->OnLogEvent(event.get());
+ event = CreateReleaseWakelockEvent(bucketStartTimeNs + 84 * NS_PER_SEC, attributionUids1,
+ attributionTags1, "wl1"); // 1:24
+ processor->OnLogEvent(event.get());
+
+ uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC; // 1:30
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+ ASSERT_EQ(reports.reports_size(), 1);
+ ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+
+ StatsLogReport::DurationMetricDataWrapper metricData;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), &metricData);
+ ASSERT_EQ(metricData.data_size(), 3);
+
+ DurationMetricData data = metricData.data(0);
+ ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED,
+ app1Uid);
+ ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND);
+ ASSERT_EQ(data.bucket_info_size(), 1);
+ DurationBucketInfo bucketInfo = data.bucket_info(0);
+ EXPECT_EQ(bucketInfo.duration_nanos(), 15 * NS_PER_SEC);
+
+ data = metricData.data(1);
+ ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED,
+ app1Uid);
+ ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND);
+ ASSERT_EQ(data.bucket_info_size(), 1);
+ bucketInfo = data.bucket_info(0);
+ EXPECT_EQ(bucketInfo.duration_nanos(), 9 * NS_PER_SEC);
+
+ data = metricData.data(2);
+ ValidateAttributionUidDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED,
+ app2Uid);
+ ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND);
+ ASSERT_EQ(data.bucket_info_size(), 1);
+ bucketInfo = data.bucket_info(0);
+ EXPECT_EQ(bucketInfo.duration_nanos(), 18 * NS_PER_SEC);
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index d1f89775ed6a..bb2ede4ddc2b 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -73,9 +73,9 @@ TEST(DurationMetricTrackerTest, TestFirstBucket) {
FieldMatcher dimensions;
DurationMetricProducer durationProducer(
- kConfigKey, metric, -1 /*no condition*/, {}, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions, 5,
- 600 * NS_PER_SEC + NS_PER_SEC / 2);
+ kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/,
+ 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+ wizard, protoHash, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2);
EXPECT_EQ(600500000000, durationProducer.mCurrentBucketStartTimeNs);
EXPECT_EQ(10, durationProducer.mCurrentBucketNum);
@@ -101,9 +101,9 @@ TEST(DurationMetricTrackerTest, TestNoCondition) {
FieldMatcher dimensions;
DurationMetricProducer durationProducer(
- kConfigKey, metric, -1 /*no condition*/, {}, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
- bucketStartTimeNs, bucketStartTimeNs);
+ kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/,
+ 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+ wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
@@ -145,8 +145,9 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) {
DurationMetricProducer durationProducer(
kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
- 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
- wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+ -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
+ bucketStartTimeNs, bucketStartTimeNs);
durationProducer.mCondition = ConditionState::kFalse;
EXPECT_FALSE(durationProducer.mCondition);
@@ -195,8 +196,9 @@ TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState) {
DurationMetricProducer durationProducer(
kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
- 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
- wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+ -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
+ 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
+ bucketStartTimeNs, bucketStartTimeNs);
EXPECT_EQ(ConditionState::kUnknown, durationProducer.mCondition);
EXPECT_FALSE(durationProducer.isConditionSliced());
@@ -240,9 +242,9 @@ TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDuration) {
FieldMatcher dimensions;
DurationMetricProducer durationProducer(
- kConfigKey, metric, -1 /* no condition */, {}, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
- bucketStartTimeNs, bucketStartTimeNs);
+ kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
+ 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+ wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -303,9 +305,9 @@ TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationWithSplitInFollo
FieldMatcher dimensions;
DurationMetricProducer durationProducer(
- kConfigKey, metric, -1 /* no condition */, {}, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
- bucketStartTimeNs, bucketStartTimeNs);
+ kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
+ 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+ wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -367,9 +369,9 @@ TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) {
FieldMatcher dimensions;
DurationMetricProducer durationProducer(
- kConfigKey, metric, -1 /* no condition */, {}, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
- bucketStartTimeNs, bucketStartTimeNs);
+ kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
+ 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+ wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert, alarmMonitor);
EXPECT_TRUE(anomalyTracker != nullptr);
@@ -413,9 +415,9 @@ TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDuration) {
FieldMatcher dimensions;
DurationMetricProducer durationProducer(
- kConfigKey, metric, -1 /* no condition */, {}, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
- bucketStartTimeNs, bucketStartTimeNs);
+ kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
+ 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+ wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
int64_t startTimeNs = bucketStartTimeNs + 1;
LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -467,9 +469,9 @@ TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextB
FieldMatcher dimensions;
DurationMetricProducer durationProducer(
- kConfigKey, metric, -1 /* no condition */, {}, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
- bucketStartTimeNs, bucketStartTimeNs);
+ kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
+ 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+ wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
int64_t startTimeNs = bucketStartTimeNs + 1;
LogEvent event1(/*uid=*/0, /*pid=*/0);
diff --git a/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
index 9e2350b33018..88df2ff0caa8 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -875,8 +875,9 @@ TEST(MetricsManagerTest, TestCreateAnomalyTrackerDurationTooLong) {
FieldMatcher dimensions;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
vector<sp<MetricProducer>> metricProducers({new DurationMetricProducer(
- kConfigKey, metric, -1 /*no condition*/, {}, 1 /* start index */, 2 /* stop index */,
- 3 /* stop_all index */, false /*nesting*/, wizard, 0x0123456789, dimensions, 0, 0)});
+ kConfigKey, metric, -1 /*no condition*/, {}, -1 /* what index not needed*/,
+ 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
+ wizard, 0x0123456789, dimensions, 0, 0)});
sp<AlarmMonitor> anomalyAlarmMonitor;
EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, {{1, 0}}, metricProducers), nullopt);
}
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 1761d5d9e1fa..153b696808dc 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -1091,6 +1091,21 @@ void ValidateAttributionUidAndTagDimension(
.value_tuple().dimensions_value(1).value_str(), tag);
}
+void ValidateStateValue(const google::protobuf::RepeatedPtrField<StateValue>& stateValues,
+ int atomId, int64_t value) {
+ ASSERT_EQ(stateValues.size(), 1);
+ ASSERT_EQ(stateValues[0].atom_id(), atomId);
+ switch (stateValues[0].contents_case()) {
+ case StateValue::ContentsCase::kValue:
+ EXPECT_EQ(stateValues[0].value(), (int32_t)value);
+ break;
+ case StateValue::ContentsCase::kGroupId:
+ EXPECT_EQ(stateValues[0].group_id(), value);
+ break;
+ default:
+ FAIL() << "State value should have either a value or a group id";
+ }
+}
bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) {
if (s1.field() != s2.field()) {
return false;
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 1220019e2353..c51491244fd8 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -347,6 +347,8 @@ void ValidateAttributionUidAndTagDimension(
const DimensionsValue& value, int atomId, int uid, const std::string& tag);
void ValidateAttributionUidAndTagDimension(
const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag);
+void ValidateStateValue(const google::protobuf::RepeatedPtrField<StateValue>& stateValues,
+ int atomId, int64_t value);
struct DimensionsPair {
DimensionsPair(DimensionsValue m1, google::protobuf::RepeatedPtrField<StateValue> m2)