Merge "Init duration metric if condition existed"

This commit is contained in:
TreeHugger Robot
2020-12-02 18:37:32 +00:00
committed by Android (Google) Code Review
12 changed files with 419 additions and 83 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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:

View File

@@ -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; }

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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(

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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)