blob: 0e5c5ba9fa4c66cb4d9f27667cec4fd3c344553a [file] [log] [blame]
// Copyright (C) 2020 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 <aidl/android/os/StatsDimensionsValueParcel.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android/binder_interface_utils.h>
#include <gtest/gtest.h>
#include <thread>
#include "flags/flags.h"
#include "src/StatsLogProcessor.h"
#include "src/StatsService.h"
#include "src/storage/StorageManager.h"
#include "src/subscriber/SubscriberReporter.h"
#include "tests/statsd_test_util.h"
namespace android {
namespace os {
namespace statsd {
#ifdef __ANDROID__
using aidl::android::os::StatsDimensionsValueParcel;
using android::base::SetProperty;
using android::base::StringPrintf;
using ::ndk::SharedRefBase;
using namespace std;
// Tests that only run with the partial config update feature turned on.
namespace {
// Setup for test fixture.
class ConfigUpdateE2eTest : public ::testing::Test {
private:
string originalFlagValue;
public:
void SetUp() override {
originalFlagValue = getFlagBool(PARTIAL_CONFIG_UPDATE_FLAG, "");
string rawFlagName =
StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(),
PARTIAL_CONFIG_UPDATE_FLAG.c_str());
SetProperty(rawFlagName, "true");
}
void TearDown() override {
string rawFlagName =
StringPrintf("persist.device_config.%s.%s", STATSD_NATIVE_NAMESPACE.c_str(),
PARTIAL_CONFIG_UPDATE_FLAG.c_str());
SetProperty(rawFlagName, originalFlagValue);
}
};
void ValidateSubsystemSleepDimension(const DimensionsValue& value, string name) {
EXPECT_EQ(value.field(), util::SUBSYSTEM_SLEEP_STATE);
ASSERT_EQ(value.value_tuple().dimensions_value_size(), 1);
EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1 /* subsystem name field */);
EXPECT_EQ(value.value_tuple().dimensions_value(0).value_str(), name);
}
} // Anonymous namespace.
TEST_F(ConfigUpdateE2eTest, TestCountMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher();
*config.add_atom_matcher() = syncStartMatcher;
AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
*config.add_atom_matcher() = wakelockAcquireMatcher;
AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher();
*config.add_atom_matcher() = wakelockReleaseMatcher;
AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
*config.add_atom_matcher() = screenOnMatcher;
AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
*config.add_atom_matcher() = screenOffMatcher;
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 screenOnPredicate = CreateScreenIsOnPredicate();
*config.add_predicate() = screenOnPredicate;
Predicate* combination = config.add_predicate();
combination->set_id(StringToId("ScreenOnAndHoldingWL)"));
combination->mutable_combination()->set_operation(LogicalOperation::AND);
addPredicateToPredicateCombination(screenOnPredicate, combination);
addPredicateToPredicateCombination(holdingWakelockPredicate, combination);
State uidProcessState = CreateUidProcessState();
*config.add_state() = uidProcessState;
CountMetric countPersist =
createCountMetric("CountSyncPerUidWhileScreenOnHoldingWLSliceProcessState",
syncStartMatcher.id(), combination->id(), {uidProcessState.id()});
*countPersist.mutable_dimensions_in_what() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
// Links between sync state atom and condition of uid is holding wakelock.
MetricConditionLink* links = countPersist.add_links();
links->set_condition(holdingWakelockPredicate.id());
*links->mutable_fields_in_what() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
*links->mutable_fields_in_condition() =
CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
MetricStateLink* stateLink = countPersist.add_state_link();
stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED);
*stateLink->mutable_fields_in_what() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
*stateLink->mutable_fields_in_state() =
CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/});
CountMetric countChange = createCountMetric("Count*WhileScreenOn", syncStartMatcher.id(),
screenOnPredicate.id(), {});
CountMetric countRemove = createCountMetric("CountSync", syncStartMatcher.id(), nullopt, {});
*config.add_count_metric() = countRemove;
*config.add_count_metric() = countPersist;
*config.add_count_metric() = countChange;
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_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"};
// Initialize log events before update. Counts are for countPersist since others are simpler.
std::vector<std::unique_ptr<LogEvent>> events;
events.push_back(CreateUidProcessStateChangedEvent(
bucketStartTimeNs + 2 * NS_PER_SEC, app1Uid,
android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));
events.push_back(CreateUidProcessStateChangedEvent(
bucketStartTimeNs + 3 * NS_PER_SEC, app2Uid,
android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 5 * NS_PER_SEC, attributionUids1,
attributionTags1, "sync_name")); // Not counted.
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_ON));
events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC,
attributionUids1, attributionTags1, "wl1"));
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 20 * NS_PER_SEC, attributionUids1,
attributionTags1, "sync_name")); // Counted. uid1 = 1.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 21 * NS_PER_SEC, attributionUids2,
attributionTags2, "sync_name")); // Not counted.
events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC,
attributionUids2, attributionTags2, "wl2"));
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 30 * NS_PER_SEC, attributionUids1,
attributionTags1, "sync_name")); // Counted. uid1 = 2.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 31 * NS_PER_SEC, attributionUids2,
attributionTags2, "sync_name")); // Counted. uid2 = 1
events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC,
attributionUids1, attributionTags1, "wl1"));
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->OnLogEvent(event.get());
}
// Do update. Add matchers/conditions in different order to force indices to change.
StatsdConfig newConfig;
newConfig.add_allowed_log_source("AID_ROOT");
*newConfig.add_atom_matcher() = screenOnMatcher;
*newConfig.add_atom_matcher() = screenOffMatcher;
*newConfig.add_atom_matcher() = syncStartMatcher;
*newConfig.add_atom_matcher() = wakelockAcquireMatcher;
*newConfig.add_atom_matcher() = wakelockReleaseMatcher;
*newConfig.add_predicate() = *combination;
*newConfig.add_predicate() = holdingWakelockPredicate;
*newConfig.add_predicate() = screenOnPredicate;
*newConfig.add_state() = uidProcessState;
countChange.set_what(screenOnMatcher.id());
*newConfig.add_count_metric() = countChange;
CountMetric countNew = createCountMetric("CountWlWhileScreenOn", wakelockAcquireMatcher.id(),
screenOnPredicate.id(), {});
*newConfig.add_count_metric() = countNew;
*newConfig.add_count_metric() = countPersist;
int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Send events after the update. Counts reset to 0 since this is a new bucket.
events.clear();
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 65 * NS_PER_SEC, attributionUids1,
attributionTags1, "sync_name")); // Not counted.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 66 * NS_PER_SEC, attributionUids2,
attributionTags2, "sync_name")); // Counted. uid2 = 1.
events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 70 * NS_PER_SEC,
attributionUids1, attributionTags1, "wl1"));
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 75 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF));
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 80 * NS_PER_SEC, attributionUids2,
attributionTags2, "sync_name")); // Not counted.
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 85 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_ON));
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 90 * NS_PER_SEC, attributionUids1,
attributionTags1, "sync_name")); // Counted. uid1 = 1.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 11 * NS_PER_SEC, attributionUids2,
attributionTags2, "sync_name")); // Counted. uid2 = 2.
// Flushes bucket.
events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + bucketSizeNs + NS_PER_SEC,
attributionUids1, attributionTags1, "wl2"));
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->OnLogEvent(event.get());
}
uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC;
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(), 2);
// Report from before update.
ConfigMetricsReport report = reports.reports(0);
ASSERT_EQ(report.metrics_size(), 3);
// Count syncs. There were 5 syncs before the update.
StatsLogReport countRemoveBefore = report.metrics(0);
EXPECT_EQ(countRemoveBefore.metric_id(), countRemove.id());
EXPECT_TRUE(countRemoveBefore.has_count_metrics());
ASSERT_EQ(countRemoveBefore.count_metrics().data_size(), 1);
auto data = countRemoveBefore.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 5);
// Uid 1 had 2 syncs, uid 2 had 1 sync.
StatsLogReport countPersistBefore = report.metrics(1);
EXPECT_EQ(countPersistBefore.metric_id(), countPersist.id());
EXPECT_TRUE(countPersistBefore.has_count_metrics());
StatsLogReport::CountMetricDataWrapper countMetrics;
sortMetricDataByDimensionsValue(countPersistBefore.count_metrics(), &countMetrics);
ASSERT_EQ(countMetrics.data_size(), 2);
data = countMetrics.data(0);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_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);
ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 2);
data = countMetrics.data(1);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_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);
ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 1);
// Counts syncs while screen on. There were 4 before the update.
StatsLogReport countChangeBefore = report.metrics(2);
EXPECT_EQ(countChangeBefore.metric_id(), countChange.id());
EXPECT_TRUE(countChangeBefore.has_count_metrics());
ASSERT_EQ(countChangeBefore.count_metrics().data_size(), 1);
data = countChangeBefore.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 4);
// Report from after update.
report = reports.reports(1);
ASSERT_EQ(report.metrics_size(), 3);
// Count screen on while screen is on. There was 1 after the update.
StatsLogReport countChangeAfter = report.metrics(0);
EXPECT_EQ(countChangeAfter.metric_id(), countChange.id());
EXPECT_TRUE(countChangeAfter.has_count_metrics());
ASSERT_EQ(countChangeAfter.count_metrics().data_size(), 1);
data = countChangeAfter.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 1);
// Count wl acquires while screen on. There were 2, one in each bucket.
StatsLogReport countNewAfter = report.metrics(1);
EXPECT_EQ(countNewAfter.metric_id(), countNew.id());
EXPECT_TRUE(countNewAfter.has_count_metrics());
ASSERT_EQ(countNewAfter.count_metrics().data_size(), 1);
data = countNewAfter.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 2);
ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 1);
ValidateCountBucket(data.bucket_info(1), bucketStartTimeNs + bucketSizeNs, dumpTimeNs, 1);
// Uid 1 had 1 sync, uid 2 had 2 syncs.
StatsLogReport countPersistAfter = report.metrics(2);
EXPECT_EQ(countPersistAfter.metric_id(), countPersist.id());
EXPECT_TRUE(countPersistAfter.has_count_metrics());
countMetrics.Clear();
sortMetricDataByDimensionsValue(countPersistAfter.count_metrics(), &countMetrics);
ASSERT_EQ(countMetrics.data_size(), 2);
data = countMetrics.data(0);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_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);
ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 1);
data = countMetrics.data(1);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_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);
ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 2);
}
TEST_F(ConfigUpdateE2eTest, TestDurationMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher();
*config.add_atom_matcher() = syncStartMatcher;
AtomMatcher syncStopMatcher = CreateSyncEndAtomMatcher();
*config.add_atom_matcher() = syncStopMatcher;
AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
*config.add_atom_matcher() = wakelockAcquireMatcher;
AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher();
*config.add_atom_matcher() = wakelockReleaseMatcher;
AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
*config.add_atom_matcher() = screenOnMatcher;
AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
*config.add_atom_matcher() = screenOffMatcher;
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 screenOnPredicate = CreateScreenIsOnPredicate();
*config.add_predicate() = screenOnPredicate;
Predicate syncPredicate = CreateIsSyncingPredicate();
// The predicate is dimensioning by first attribution node by uid.
*syncPredicate.mutable_simple_predicate()->mutable_dimensions() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
*config.add_predicate() = syncPredicate;
State uidProcessState = CreateUidProcessState();
*config.add_state() = uidProcessState;
DurationMetric durationSumPersist =
createDurationMetric("DurSyncPerUidWhileHoldingWLSliceProcessState", syncPredicate.id(),
holdingWakelockPredicate.id(), {uidProcessState.id()});
*durationSumPersist.mutable_dimensions_in_what() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
// Links between sync state atom and condition of uid is holding wakelock.
MetricConditionLink* links = durationSumPersist.add_links();
links->set_condition(holdingWakelockPredicate.id());
*links->mutable_fields_in_what() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
*links->mutable_fields_in_condition() =
CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
MetricStateLink* stateLink = durationSumPersist.add_state_link();
stateLink->set_state_atom_id(util::UID_PROCESS_STATE_CHANGED);
*stateLink->mutable_fields_in_what() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
*stateLink->mutable_fields_in_state() =
CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/});
DurationMetric durationMaxPersist =
createDurationMetric("DurMaxSyncPerUidWhileHoldingWL", syncPredicate.id(),
holdingWakelockPredicate.id(), {});
durationMaxPersist.set_aggregation_type(DurationMetric::MAX_SPARSE);
*durationMaxPersist.mutable_dimensions_in_what() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
// Links between sync state atom and condition of uid is holding wakelock.
links = durationMaxPersist.add_links();
links->set_condition(holdingWakelockPredicate.id());
*links->mutable_fields_in_what() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
*links->mutable_fields_in_condition() =
CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
DurationMetric durationChange = createDurationMetric("Dur*WhileScreenOn", syncPredicate.id(),
screenOnPredicate.id(), {});
DurationMetric durationRemove =
createDurationMetric("DurScreenOn", screenOnPredicate.id(), nullopt, {});
*config.add_duration_metric() = durationMaxPersist;
*config.add_duration_metric() = durationRemove;
*config.add_duration_metric() = durationSumPersist;
*config.add_duration_metric() = durationChange;
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
sp<StatsLogProcessor> processor =
CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
int app1Uid = 123, app2Uid = 456, app3Uid = 789;
vector<int> attributionUids1 = {app1Uid};
vector<string> attributionTags1 = {"App1"};
vector<int> attributionUids2 = {app2Uid};
vector<string> attributionTags2 = {"App2"};
vector<int> attributionUids3 = {app3Uid};
vector<string> attributionTags3 = {"App3"};
// Initialize log events before update. Comments provided for durations of persisted metrics.
std::vector<std::unique_ptr<LogEvent>> events;
events.push_back(CreateUidProcessStateChangedEvent(
bucketStartTimeNs + 2 * NS_PER_SEC, app1Uid,
android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));
events.push_back(CreateUidProcessStateChangedEvent(
bucketStartTimeNs + 3 * NS_PER_SEC, app2Uid,
android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));
events.push_back(CreateScreenStateChangedEvent(
bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON));
events.push_back(CreateUidProcessStateChangedEvent(
bucketStartTimeNs + 6 * NS_PER_SEC, app3Uid,
android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 10 * NS_PER_SEC, attributionUids1,
attributionTags1, "sync_name")); // uid1 paused.
events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC,
attributionUids2, attributionTags2,
"wl2")); // uid2 cond true.
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF));
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 25 * NS_PER_SEC, attributionUids2,
attributionTags2, "sync_name")); // Uid 2 start t=25.
events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 27 * NS_PER_SEC,
attributionUids1, attributionTags1,
"wl1")); // Uid 1 start t=27.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 35 * NS_PER_SEC, attributionUids3,
attributionTags3, "sync_name")); // Uid 3 paused.
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_ON));
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 45 * NS_PER_SEC, attributionUids2,
attributionTags2,
"sync_name")); // Uid 2 stop. sum/max = 20.
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->OnLogEvent(event.get());
}
// Do update. Add matchers/conditions in different order to force indices to change.
StatsdConfig newConfig;
newConfig.add_allowed_log_source("AID_ROOT");
*newConfig.add_atom_matcher() = wakelockReleaseMatcher;
*newConfig.add_atom_matcher() = syncStopMatcher;
*newConfig.add_atom_matcher() = screenOnMatcher;
*newConfig.add_atom_matcher() = screenOffMatcher;
*newConfig.add_atom_matcher() = syncStartMatcher;
*newConfig.add_atom_matcher() = wakelockAcquireMatcher;
*newConfig.add_predicate() = syncPredicate;
*newConfig.add_predicate() = screenOnPredicate;
*newConfig.add_predicate() = holdingWakelockPredicate;
*newConfig.add_state() = uidProcessState;
durationChange.set_what(holdingWakelockPredicate.id());
*newConfig.add_duration_metric() = durationChange;
DurationMetric durationNew =
createDurationMetric("DurationSync", syncPredicate.id(), nullopt, {});
*newConfig.add_duration_metric() = durationNew;
*newConfig.add_duration_metric() = durationMaxPersist;
*newConfig.add_duration_metric() = durationSumPersist;
// At update, only uid 1 is syncing & holding a wakelock, duration=33. Max is paused for uid3.
// Before the update, only uid2 will report a duration for max, since others are started/paused.
int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Send events after the update.
events.clear();
events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 65 * NS_PER_SEC,
attributionUids3, attributionTags3,
"wl3")); // Uid3 start t=65.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 70 * NS_PER_SEC, attributionUids2,
attributionTags2, "sync_name")); // Uid2 start t=70.
events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 75 * NS_PER_SEC,
attributionUids1, attributionTags1,
"wl1")); // Uid1 Pause. Dur = 15.
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 81 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF));
events.push_back(CreateUidProcessStateChangedEvent(
bucketStartTimeNs + 85 * NS_PER_SEC, app3Uid,
android::app::ProcessStateEnum::
PROCESS_STATE_IMPORTANT_FOREGROUND)); // Uid3 Sum in BG: 20. FG starts. Max is
// unchanged.
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 90 * NS_PER_SEC, attributionUids3,
attributionTags3,
"sync_name")); // Uid3 stop. Sum in FG: 5. MAX: 25.
// Flushes bucket. Sum: uid1=15, uid2=bucketSize - 70, uid3 = 20 in FG, 5 in BG. Max: uid3=25,
// others don't report.
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + NS_PER_SEC,
attributionUids1, attributionTags1,
"sync_name")); // Uid1 stop. Max=15+33=48, Sum is 0.
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->OnLogEvent(event.get());
}
uint64_t bucketEndTimeNs = bucketStartTimeNs + bucketSizeNs;
uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC;
ConfigMetricsReportList reports;
vector<uint8_t> buffer;
// In the partial bucket, Sum for uid2 = 10, Max for Uid1 = 48. Rest are unreported.
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(), 2);
// Report from before update.
ConfigMetricsReport report = reports.reports(0);
ASSERT_EQ(report.metrics_size(), 4);
// Max duration of syncs per uid while uid holding WL. Only uid2 should have data.
StatsLogReport durationMaxPersistBefore = report.metrics(0);
EXPECT_EQ(durationMaxPersistBefore.metric_id(), durationMaxPersist.id());
EXPECT_TRUE(durationMaxPersistBefore.has_duration_metrics());
StatsLogReport::DurationMetricDataWrapper durationMetrics;
sortMetricDataByDimensionsValue(durationMaxPersistBefore.duration_metrics(), &durationMetrics);
ASSERT_EQ(durationMetrics.data_size(), 1);
auto data = durationMetrics.data(0);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app2Uid);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 20 * NS_PER_SEC);
// Screen on time. ON: 5, OFF: 20, ON: 40. Update: 60. Result: 35
StatsLogReport durationRemoveBefore = report.metrics(1);
EXPECT_EQ(durationRemoveBefore.metric_id(), durationRemove.id());
EXPECT_TRUE(durationRemoveBefore.has_duration_metrics());
durationMetrics.Clear();
sortMetricDataByDimensionsValue(durationRemoveBefore.duration_metrics(), &durationMetrics);
ASSERT_EQ(durationMetrics.data_size(), 1);
data = durationMetrics.data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 35 * NS_PER_SEC);
// Duration of syncs per uid while uid holding WL, slice screen. Uid1=33, uid2=20.
StatsLogReport durationSumPersistBefore = report.metrics(2);
EXPECT_EQ(durationSumPersistBefore.metric_id(), durationSumPersist.id());
EXPECT_TRUE(durationSumPersistBefore.has_duration_metrics());
durationMetrics.Clear();
sortMetricDataByDimensionsValue(durationSumPersistBefore.duration_metrics(), &durationMetrics);
ASSERT_EQ(durationMetrics.data_size(), 2);
data = durationMetrics.data(0);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_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);
ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 33 * NS_PER_SEC);
data = durationMetrics.data(1);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_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);
ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 20 * NS_PER_SEC);
// Duration of syncs while screen on. Start: 10, pause: 20, start: 40 Update: 60. Total: 30.
StatsLogReport durationChangeBefore = report.metrics(3);
EXPECT_EQ(durationChangeBefore.metric_id(), durationChange.id());
EXPECT_TRUE(durationChangeBefore.has_duration_metrics());
durationMetrics.Clear();
sortMetricDataByDimensionsValue(durationChangeBefore.duration_metrics(), &durationMetrics);
ASSERT_EQ(durationMetrics.data_size(), 1);
data = durationMetrics.data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateDurationBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 30 * NS_PER_SEC);
// Report from after update.
report = reports.reports(1);
ASSERT_EQ(report.metrics_size(), 4);
// Duration of WL while screen on. Update: 60, pause: 81. Total: 21.
StatsLogReport durationChangeAfter = report.metrics(0);
EXPECT_EQ(durationChangeAfter.metric_id(), durationChange.id());
EXPECT_TRUE(durationChangeAfter.has_duration_metrics());
durationMetrics.Clear();
sortMetricDataByDimensionsValue(durationChangeAfter.duration_metrics(), &durationMetrics);
ASSERT_EQ(durationMetrics.data_size(), 1);
data = durationMetrics.data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 21 * NS_PER_SEC);
// Duration of syncs. Always true since at least 1 uid is always syncing.
StatsLogReport durationNewAfter = report.metrics(1);
EXPECT_EQ(durationNewAfter.metric_id(), durationNew.id());
EXPECT_TRUE(durationNewAfter.has_duration_metrics());
durationMetrics.Clear();
sortMetricDataByDimensionsValue(durationNewAfter.duration_metrics(), &durationMetrics);
ASSERT_EQ(durationMetrics.data_size(), 1);
data = durationMetrics.data(0);
ASSERT_EQ(data.bucket_info_size(), 2);
ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs,
bucketEndTimeNs - updateTimeNs);
ValidateDurationBucket(data.bucket_info(1), bucketEndTimeNs, dumpTimeNs,
dumpTimeNs - bucketEndTimeNs);
// Max duration of syncs per uid while uid holding WL.
StatsLogReport durationMaxPersistAfter = report.metrics(2);
EXPECT_EQ(durationMaxPersistAfter.metric_id(), durationMaxPersist.id());
EXPECT_TRUE(durationMaxPersistAfter.has_duration_metrics());
durationMetrics.Clear();
sortMetricDataByDimensionsValue(durationMaxPersistAfter.duration_metrics(), &durationMetrics);
ASSERT_EQ(durationMetrics.data_size(), 2);
// Uid 1. Duration = 48 in the later bucket.
data = durationMetrics.data(0);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app1Uid);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateDurationBucket(data.bucket_info(0), bucketEndTimeNs, dumpTimeNs, 48 * NS_PER_SEC);
// Uid 3. Duration = 25.
data = durationMetrics.data(1);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app3Uid);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 25 * NS_PER_SEC);
// Duration of syncs per uid while uid holding WL, slice screen.
StatsLogReport durationSumPersistAfter = report.metrics(3);
EXPECT_EQ(durationSumPersistAfter.metric_id(), durationSumPersist.id());
EXPECT_TRUE(durationSumPersistAfter.has_duration_metrics());
durationMetrics.Clear();
sortMetricDataByDimensionsValue(durationSumPersistAfter.duration_metrics(), &durationMetrics);
ASSERT_EQ(durationMetrics.data_size(), 4);
// Uid 1 in BG. Duration = 15.
data = durationMetrics.data(0);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_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);
ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 15 * NS_PER_SEC);
// Uid 2 in FG. Duration = bucketSize - 70 in first bucket, 10 in second bucket.
data = durationMetrics.data(1);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_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(), 2);
ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs,
bucketSizeNs - 70 * NS_PER_SEC);
ValidateDurationBucket(data.bucket_info(1), bucketEndTimeNs, dumpTimeNs, 10 * NS_PER_SEC);
// Uid 3 in FG. Duration = 5.
data = durationMetrics.data(2);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app3Uid);
ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED,
android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 5 * NS_PER_SEC);
// Uid 3 in BG. Duration = 20.
data = durationMetrics.data(3);
ValidateAttributionUidDimension(data.dimensions_in_what(), util::SYNC_STATE_CHANGED, app3Uid);
ValidateStateValue(data.slice_by_state(), util::UID_PROCESS_STATE_CHANGED,
android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 20 * NS_PER_SEC);
}
TEST_F(ConfigUpdateE2eTest, TestGaugeMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
AtomMatcher appStartMatcher = CreateSimpleAtomMatcher("AppStart", util::APP_START_OCCURRED);
*config.add_atom_matcher() = appStartMatcher;
AtomMatcher backgroundMatcher = CreateMoveToBackgroundAtomMatcher();
*config.add_atom_matcher() = backgroundMatcher;
AtomMatcher foregroundMatcher = CreateMoveToForegroundAtomMatcher();
*config.add_atom_matcher() = foregroundMatcher;
AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
*config.add_atom_matcher() = screenOnMatcher;
AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
*config.add_atom_matcher() = screenOffMatcher;
AtomMatcher subsystemSleepMatcher =
CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
*config.add_atom_matcher() = subsystemSleepMatcher;
Predicate isInBackgroundPredicate = CreateIsInBackgroundPredicate();
*isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid field*/});
*config.add_predicate() = isInBackgroundPredicate;
Predicate screenOnPredicate = CreateScreenIsOnPredicate();
*config.add_predicate() = screenOnPredicate;
GaugeMetric gaugePullPersist =
createGaugeMetric("SubsystemSleepWhileScreenOn", subsystemSleepMatcher.id(),
GaugeMetric::RANDOM_ONE_SAMPLE, screenOnPredicate.id(), {});
*gaugePullPersist.mutable_dimensions_in_what() =
CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
GaugeMetric gaugePushPersist =
createGaugeMetric("AppStartWhileInBg", appStartMatcher.id(),
GaugeMetric::FIRST_N_SAMPLES, isInBackgroundPredicate.id(), nullopt);
*gaugePushPersist.mutable_dimensions_in_what() =
CreateDimensions(util::APP_START_OCCURRED, {1 /*uid field*/});
// Links between sync state atom and condition of uid is holding wakelock.
MetricConditionLink* links = gaugePushPersist.add_links();
links->set_condition(isInBackgroundPredicate.id());
*links->mutable_fields_in_what() =
CreateDimensions(util::APP_START_OCCURRED, {1 /*uid field*/});
*links->mutable_fields_in_condition() =
CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid field*/});
GaugeMetric gaugeChange = createGaugeMetric("GaugeScrOn", screenOnMatcher.id(),
GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt);
GaugeMetric gaugeRemove =
createGaugeMetric("GaugeSubsysTriggerScr", subsystemSleepMatcher.id(),
GaugeMetric::FIRST_N_SAMPLES, nullopt, screenOnMatcher.id());
*gaugeRemove.mutable_dimensions_in_what() =
CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
*config.add_gauge_metric() = gaugeRemove;
*config.add_gauge_metric() = gaugePullPersist;
*config.add_gauge_metric() = gaugeChange;
*config.add_gauge_metric() = gaugePushPersist;
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = getElapsedRealtimeNs(); // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
bucketStartTimeNs, bucketStartTimeNs, config, key,
SharedRefBase::make<FakeSubsystemSleepCallback>(), util::SUBSYSTEM_SLEEP_STATE);
int app1Uid = 123, app2Uid = 456;
// Initialize log events before update.
std::vector<std::unique_ptr<LogEvent>> events;
events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 5 * NS_PER_SEC, app1Uid));
events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 10 * NS_PER_SEC, app1Uid,
"app1", AppStartOccurred::WARM, "", "", true,
/*start_msec*/ 101)); // Kept by gaugePushPersist.
events.push_back(
CreateAppStartOccurredEvent(bucketStartTimeNs + 15 * NS_PER_SEC, app2Uid, "app2",
AppStartOccurred::WARM, "", "", true,
/*start_msec*/ 201)); // Not kept by gaugePushPersist.
events.push_back(CreateScreenStateChangedEvent(
bucketStartTimeNs + 20 * NS_PER_SEC,
android::view::DISPLAY_STATE_ON)); // Pulls gaugePullPersist and gaugeRemove.
events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 25 * NS_PER_SEC, app2Uid));
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 30 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF));
events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 35 * NS_PER_SEC, app1Uid,
"app1", AppStartOccurred::WARM, "", "", true,
/*start_msec*/ 102)); // Kept by gaugePushPersist.
events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 40 * NS_PER_SEC, app2Uid,
"app2", AppStartOccurred::WARM, "", "", true,
/*start_msec*/ 202)); // Kept by gaugePushPersist.
events.push_back(CreateScreenStateChangedEvent(
bucketStartTimeNs + 45 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // Pulls gaugeRemove only.
events.push_back(CreateMoveToForegroundEvent(bucketStartTimeNs + 50 * NS_PER_SEC, app1Uid));
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->mPullerManager->ForceClearPullerCache();
processor->OnLogEvent(event.get());
}
processor->mPullerManager->ForceClearPullerCache();
// Do the update. Add matchers/conditions in different order to force indices to change.
StatsdConfig newConfig;
newConfig.add_allowed_log_source("AID_ROOT");
newConfig.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
*newConfig.add_atom_matcher() = screenOffMatcher;
*newConfig.add_atom_matcher() = foregroundMatcher;
*newConfig.add_atom_matcher() = appStartMatcher;
*newConfig.add_atom_matcher() = subsystemSleepMatcher;
*newConfig.add_atom_matcher() = backgroundMatcher;
*newConfig.add_atom_matcher() = screenOnMatcher;
*newConfig.add_predicate() = isInBackgroundPredicate;
*newConfig.add_predicate() = screenOnPredicate;
gaugeChange.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES);
*newConfig.add_gauge_metric() = gaugeChange;
GaugeMetric gaugeNew = createGaugeMetric("GaugeSubsys", subsystemSleepMatcher.id(),
GaugeMetric::RANDOM_ONE_SAMPLE, {}, {});
*gaugeNew.mutable_dimensions_in_what() =
CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
*newConfig.add_gauge_metric() = gaugeNew;
*newConfig.add_gauge_metric() = gaugePushPersist;
*newConfig.add_gauge_metric() = gaugePullPersist;
int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
// Update pulls gaugePullPersist and gaugeNew.
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Verify puller manager is properly set.
sp<StatsPullerManager> pullerManager = processor->mPullerManager;
EXPECT_EQ(pullerManager->mNextPullTimeNs, bucketStartTimeNs + bucketSizeNs);
ASSERT_EQ(pullerManager->mReceivers.size(), 1);
ASSERT_EQ(pullerManager->mReceivers.begin()->second.size(), 2);
// Send events after the update. Counts reset to 0 since this is a new bucket.
events.clear();
events.push_back(
CreateAppStartOccurredEvent(bucketStartTimeNs + 65 * NS_PER_SEC, app1Uid, "app1",
AppStartOccurred::WARM, "", "", true,
/*start_msec*/ 103)); // Not kept by gaugePushPersist.
events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 70 * NS_PER_SEC, app2Uid,
"app2", AppStartOccurred::WARM, "", "", true,
/*start_msec*/ 203)); // Kept by gaugePushPersist.
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 75 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF));
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 80 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_ON));
events.push_back(CreateMoveToBackgroundEvent(bucketStartTimeNs + 85 * NS_PER_SEC, app1Uid));
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 90 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF));
events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 95 * NS_PER_SEC, app1Uid,
"app1", AppStartOccurred::WARM, "", "", true,
/*start_msec*/ 104)); // Kept by gaugePushPersist.
events.push_back(CreateAppStartOccurredEvent(bucketStartTimeNs + 100 * NS_PER_SEC, app2Uid,
"app2", AppStartOccurred::WARM, "", "", true,
/*start_msec*/ 204)); // Kept by gaugePushPersist.
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 105 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_ON));
events.push_back(
CreateScreenStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF));
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->mPullerManager->ForceClearPullerCache();
processor->OnLogEvent(event.get());
}
processor->mPullerManager->ForceClearPullerCache();
// Pulling alarm arrive, triggering a bucket split. Only gaugeNew keeps the data since the
// condition is false for gaugeNew.
processor->informPullAlarmFired(bucketStartTimeNs + bucketSizeNs);
uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC;
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(), 2);
int64_t roundedBucketStartNs = MillisToNano(NanoToMillis(bucketStartTimeNs));
int64_t roundedUpdateTimeNs = MillisToNano(NanoToMillis(updateTimeNs));
int64_t roundedBucketEndNs = MillisToNano(NanoToMillis(bucketStartTimeNs + bucketSizeNs));
int64_t roundedDumpTimeNs = MillisToNano(NanoToMillis(dumpTimeNs));
// Report from before update.
ConfigMetricsReport report = reports.reports(0);
ASSERT_EQ(report.metrics_size(), 4);
// Gauge subsystem sleep state trigger screen on. 2 pulls occurred.
StatsLogReport gaugeRemoveBefore = report.metrics(0);
EXPECT_EQ(gaugeRemoveBefore.metric_id(), gaugeRemove.id());
EXPECT_TRUE(gaugeRemoveBefore.has_gauge_metrics());
StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
sortMetricDataByDimensionsValue(gaugeRemoveBefore.gauge_metrics(), &gaugeMetrics);
ASSERT_EQ(gaugeMetrics.data_size(), 2);
auto data = gaugeMetrics.data(0);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs,
{(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC),
(int64_t)(bucketStartTimeNs + 45 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 2);
EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 101);
EXPECT_EQ(data.bucket_info(0).atom(1).subsystem_sleep_state().time_millis(), 401);
data = gaugeMetrics.data(1);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs,
{(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC),
(int64_t)(bucketStartTimeNs + 45 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 2);
EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 102);
EXPECT_EQ(data.bucket_info(0).atom(1).subsystem_sleep_state().time_millis(), 402);
// Gauge subsystem sleep state when screen is on. One pull when the screen turned on
StatsLogReport gaugePullPersistBefore = report.metrics(1);
EXPECT_EQ(gaugePullPersistBefore.metric_id(), gaugePullPersist.id());
EXPECT_TRUE(gaugePullPersistBefore.has_gauge_metrics());
gaugeMetrics.Clear();
sortMetricDataByDimensionsValue(gaugePullPersistBefore.gauge_metrics(), &gaugeMetrics);
ASSERT_EQ(gaugeMetrics.data_size(), 2);
data = gaugeMetrics.data(0);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs,
{(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 1);
EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 101);
data = gaugeMetrics.data(1);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs,
{(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 1);
EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 102);
// Gauge screen on events, one per bucket.
StatsLogReport gaugeChangeBefore = report.metrics(2);
EXPECT_EQ(gaugeChangeBefore.metric_id(), gaugeChange.id());
EXPECT_TRUE(gaugeChangeBefore.has_gauge_metrics());
gaugeMetrics.Clear();
sortMetricDataByDimensionsValue(gaugeChangeBefore.gauge_metrics(), &gaugeMetrics);
ASSERT_EQ(gaugeMetrics.data_size(), 1);
data = gaugeMetrics.data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs,
{(int64_t)(bucketStartTimeNs + 20 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 1);
EXPECT_EQ(data.bucket_info(0).atom(0).screen_state_changed().state(),
android::view::DISPLAY_STATE_ON);
// Gauge app start while app is in the background. App 1 started twice, app 2 started once.
StatsLogReport gaugePushPersistBefore = report.metrics(3);
EXPECT_EQ(gaugePushPersistBefore.metric_id(), gaugePushPersist.id());
EXPECT_TRUE(gaugePushPersistBefore.has_gauge_metrics());
gaugeMetrics.Clear();
sortMetricDataByDimensionsValue(gaugePushPersistBefore.gauge_metrics(), &gaugeMetrics);
ASSERT_EQ(gaugeMetrics.data_size(), 2);
data = gaugeMetrics.data(0);
ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app1Uid);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs,
{(int64_t)(bucketStartTimeNs + 10 * NS_PER_SEC),
(int64_t)(bucketStartTimeNs + 35 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 2);
EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app1");
EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 101);
EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().pkg_name(), "app1");
EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis(), 102);
data = gaugeMetrics.data(1);
ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app2Uid);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs,
{(int64_t)(bucketStartTimeNs + 40 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 1);
EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app2");
EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 202);
// Report from after update.
report = reports.reports(1);
ASSERT_EQ(report.metrics_size(), 4);
// Gauge screen on events FIRST_N_SAMPLES. There were 2.
StatsLogReport gaugeChangeAfter = report.metrics(0);
EXPECT_EQ(gaugeChangeAfter.metric_id(), gaugeChange.id());
EXPECT_TRUE(gaugeChangeAfter.has_gauge_metrics());
gaugeMetrics.Clear();
sortMetricDataByDimensionsValue(gaugeChangeAfter.gauge_metrics(), &gaugeMetrics);
ASSERT_EQ(gaugeMetrics.data_size(), 1);
data = gaugeMetrics.data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs,
{(int64_t)(bucketStartTimeNs + 80 * NS_PER_SEC),
(int64_t)(bucketStartTimeNs + 105 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 2);
EXPECT_EQ(data.bucket_info(0).atom(0).screen_state_changed().state(),
android::view::DISPLAY_STATE_ON);
EXPECT_EQ(data.bucket_info(0).atom(1).screen_state_changed().state(),
android::view::DISPLAY_STATE_ON);
// Gauge subsystem sleep state, random one sample, no condition.
// Pulled at update time and after the normal bucket end.
StatsLogReport gaugeNewAfter = report.metrics(1);
EXPECT_EQ(gaugeNewAfter.metric_id(), gaugeNew.id());
EXPECT_TRUE(gaugeNewAfter.has_gauge_metrics());
gaugeMetrics.Clear();
sortMetricDataByDimensionsValue(gaugeNewAfter.gauge_metrics(), &gaugeMetrics);
ASSERT_EQ(gaugeMetrics.data_size(), 2);
data = gaugeMetrics.data(0);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1");
ASSERT_EQ(data.bucket_info_size(), 2);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs,
{updateTimeNs});
ASSERT_EQ(data.bucket_info(0).atom_size(), 1);
EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 901);
ValidateGaugeBucketTimes(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs,
{(int64_t)(bucketStartTimeNs + bucketSizeNs)});
ASSERT_EQ(data.bucket_info(1).atom_size(), 1);
EXPECT_EQ(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 1601);
data = gaugeMetrics.data(1);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2");
ASSERT_EQ(data.bucket_info_size(), 2);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs,
{updateTimeNs});
ASSERT_EQ(data.bucket_info(0).atom_size(), 1);
EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 902);
ValidateGaugeBucketTimes(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs,
{(int64_t)(bucketStartTimeNs + bucketSizeNs)});
ASSERT_EQ(data.bucket_info(1).atom_size(), 1);
EXPECT_EQ(data.bucket_info(1).atom(0).subsystem_sleep_state().time_millis(), 1602);
// Gauge app start while app is in the background. App 1 started once, app 2 started twice.
StatsLogReport gaugePushPersistAfter = report.metrics(2);
EXPECT_EQ(gaugePushPersistAfter.metric_id(), gaugePushPersist.id());
EXPECT_TRUE(gaugePushPersistAfter.has_gauge_metrics());
gaugeMetrics.Clear();
sortMetricDataByDimensionsValue(gaugePushPersistAfter.gauge_metrics(), &gaugeMetrics);
ASSERT_EQ(gaugeMetrics.data_size(), 2);
data = gaugeMetrics.data(0);
ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app1Uid);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs,
{(int64_t)(bucketStartTimeNs + 95 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 1);
EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app1");
EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 104);
data = gaugeMetrics.data(1);
ValidateUidDimension(data.dimensions_in_what(), util::APP_START_OCCURRED, app2Uid);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs,
{(int64_t)(bucketStartTimeNs + 70 * NS_PER_SEC),
(int64_t)(bucketStartTimeNs + 100 * NS_PER_SEC)});
ASSERT_EQ(data.bucket_info(0).atom_size(), 2);
EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().pkg_name(), "app2");
EXPECT_EQ(data.bucket_info(0).atom(0).app_start_occurred().activity_start_millis(), 203);
EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().pkg_name(), "app2");
EXPECT_EQ(data.bucket_info(0).atom(1).app_start_occurred().activity_start_millis(), 204);
// Gauge subsystem sleep state when screen is on. One pull at update since screen is on then.
StatsLogReport gaugePullPersistAfter = report.metrics(3);
EXPECT_EQ(gaugePullPersistAfter.metric_id(), gaugePullPersist.id());
EXPECT_TRUE(gaugePullPersistAfter.has_gauge_metrics());
gaugeMetrics.Clear();
sortMetricDataByDimensionsValue(gaugePullPersistAfter.gauge_metrics(), &gaugeMetrics);
ASSERT_EQ(gaugeMetrics.data_size(), 2);
data = gaugeMetrics.data(0);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs,
{updateTimeNs});
ASSERT_EQ(data.bucket_info(0).atom_size(), 1);
EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 901);
data = gaugeMetrics.data(1);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateGaugeBucketTimes(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs,
{updateTimeNs});
ASSERT_EQ(data.bucket_info(0).atom_size(), 1);
EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 902);
}
TEST_F(ConfigUpdateE2eTest, TestValueMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
AtomMatcher brightnessMatcher = CreateScreenBrightnessChangedAtomMatcher();
*config.add_atom_matcher() = brightnessMatcher;
AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
*config.add_atom_matcher() = screenOnMatcher;
AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
*config.add_atom_matcher() = screenOffMatcher;
AtomMatcher batteryPluggedUsbMatcher = CreateBatteryStateUsbMatcher();
*config.add_atom_matcher() = batteryPluggedUsbMatcher;
AtomMatcher unpluggedMatcher = CreateBatteryStateNoneMatcher();
*config.add_atom_matcher() = unpluggedMatcher;
AtomMatcher subsystemSleepMatcher =
CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
*config.add_atom_matcher() = subsystemSleepMatcher;
Predicate screenOnPredicate = CreateScreenIsOnPredicate();
*config.add_predicate() = screenOnPredicate;
Predicate unpluggedPredicate = CreateDeviceUnpluggedPredicate();
*config.add_predicate() = unpluggedPredicate;
State screenState = CreateScreenState();
*config.add_state() = screenState;
ValueMetric valuePullPersist =
createValueMetric("SubsystemSleepWhileUnpluggedSliceScreen", subsystemSleepMatcher, 4,
unpluggedPredicate.id(), {screenState.id()});
*valuePullPersist.mutable_dimensions_in_what() =
CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
ValueMetric valuePushPersist = createValueMetric(
"MinScreenBrightnessWhileScreenOn", brightnessMatcher, 1, screenOnPredicate.id(), {});
valuePushPersist.set_aggregation_type(ValueMetric::MIN);
ValueMetric valueChange =
createValueMetric("SubsystemSleep", subsystemSleepMatcher, 4, nullopt, {});
*valueChange.mutable_dimensions_in_what() =
CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
ValueMetric valueRemove =
createValueMetric("AvgScreenBrightness", brightnessMatcher, 1, nullopt, {});
valueRemove.set_aggregation_type(ValueMetric::AVG);
*config.add_value_metric() = valuePullPersist;
*config.add_value_metric() = valueRemove;
*config.add_value_metric() = valuePushPersist;
*config.add_value_metric() = valueChange;
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = getElapsedRealtimeNs();
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
// Config creation triggers pull #1.
sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
bucketStartTimeNs, bucketStartTimeNs, config, key,
SharedRefBase::make<FakeSubsystemSleepCallback>(), util::SUBSYSTEM_SLEEP_STATE);
// Initialize log events before update.
// ValuePushPersist and ValuePullPersist will skip the bucket due to condition unknown.
std::vector<std::unique_ptr<LogEvent>> events;
events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 5 * NS_PER_SEC, 5));
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC,
android::view::DISPLAY_STATE_ON));
events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC, 15));
events.push_back(CreateBatteryStateChangedEvent(
bucketStartTimeNs + 20 * NS_PER_SEC,
BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // Pull #2.
events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 40));
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->mPullerManager->ForceClearPullerCache();
processor->OnLogEvent(event.get());
}
processor->mPullerManager->ForceClearPullerCache();
// Do the update. Add matchers/conditions in different order to force indices to change.
StatsdConfig newConfig;
newConfig.add_allowed_log_source("AID_ROOT");
newConfig.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
*newConfig.add_atom_matcher() = screenOffMatcher;
*newConfig.add_atom_matcher() = unpluggedMatcher;
*newConfig.add_atom_matcher() = batteryPluggedUsbMatcher;
*newConfig.add_atom_matcher() = subsystemSleepMatcher;
*newConfig.add_atom_matcher() = brightnessMatcher;
*newConfig.add_atom_matcher() = screenOnMatcher;
*newConfig.add_predicate() = unpluggedPredicate;
*newConfig.add_predicate() = screenOnPredicate;
*config.add_state() = screenState;
valueChange.set_condition(screenOnPredicate.id());
*newConfig.add_value_metric() = valueChange;
ValueMetric valueNew = createValueMetric("MaxScrBrightness", brightnessMatcher, 1, nullopt, {});
valueNew.set_aggregation_type(ValueMetric::MAX);
*newConfig.add_value_metric() = valueNew;
*newConfig.add_value_metric() = valuePushPersist;
*newConfig.add_value_metric() = valuePullPersist;
int64_t updateTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC;
// Update pulls valuePullPersist and valueNew. Pull #3.
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Verify puller manager is properly set.
sp<StatsPullerManager> pullerManager = processor->mPullerManager;
EXPECT_EQ(pullerManager->mNextPullTimeNs, bucketStartTimeNs + bucketSizeNs);
ASSERT_EQ(pullerManager->mReceivers.size(), 1);
ASSERT_EQ(pullerManager->mReceivers.begin()->second.size(), 2);
// Send events after the update. Values reset since this is a new bucket.
events.clear();
events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 35 * NS_PER_SEC, 30));
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC,
android::view::DISPLAY_STATE_OFF)); // Pull #4.
events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 45 * NS_PER_SEC, 20));
events.push_back(CreateBatteryStateChangedEvent(
bucketStartTimeNs + 50 * NS_PER_SEC,
BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // Pull #5.
events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 55 * NS_PER_SEC, 25));
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 60 * NS_PER_SEC,
android::view::DISPLAY_STATE_ON)); // Pull #6.
events.push_back(CreateBatteryStateChangedEvent(
bucketStartTimeNs + 65 * NS_PER_SEC,
BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // Pull #7.
events.push_back(CreateScreenBrightnessChangedEvent(bucketStartTimeNs + 70 * NS_PER_SEC, 40));
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->mPullerManager->ForceClearPullerCache();
processor->OnLogEvent(event.get());
}
processor->mPullerManager->ForceClearPullerCache();
// Pulling alarm arrive, triggering a bucket split.
// Both valuePullPersist and valueChange use the value since both conditions are true. Pull #8.
processor->informPullAlarmFired(bucketStartTimeNs + bucketSizeNs);
processor->OnLogEvent(CreateScreenBrightnessChangedEvent(
bucketStartTimeNs + bucketSizeNs + 5 * NS_PER_SEC, 50)
.get());
uint64_t dumpTimeNs = bucketStartTimeNs + bucketSizeNs + 10 * NS_PER_SEC;
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(), 2);
int64_t roundedBucketStartNs = MillisToNano(NanoToMillis(bucketStartTimeNs));
int64_t roundedUpdateTimeNs = MillisToNano(NanoToMillis(updateTimeNs));
int64_t roundedBucketEndNs = MillisToNano(NanoToMillis(bucketStartTimeNs + bucketSizeNs));
int64_t roundedDumpTimeNs = MillisToNano(NanoToMillis(dumpTimeNs));
// Report from before update.
ConfigMetricsReport report = reports.reports(0);
ASSERT_EQ(report.metrics_size(), 4);
// Pull subsystem sleep while unplugged slice screen. Bucket skipped due to condition unknown.
StatsLogReport valuePullPersistBefore = report.metrics(0);
EXPECT_EQ(valuePullPersistBefore.metric_id(), valuePullPersist.id());
EXPECT_TRUE(valuePullPersistBefore.has_value_metrics());
ASSERT_EQ(valuePullPersistBefore.value_metrics().data_size(), 0);
ASSERT_EQ(valuePullPersistBefore.value_metrics().skipped_size(), 1);
StatsLogReport::SkippedBuckets skipBucket = valuePullPersistBefore.value_metrics().skipped(0);
EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketStartNs);
EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedUpdateTimeNs);
ASSERT_EQ(skipBucket.drop_event_size(), 1);
EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::CONDITION_UNKNOWN);
// Average screen brightness. Values were 5, 15, 40. Avg: 20.
StatsLogReport valueRemoveBefore = report.metrics(1);
EXPECT_EQ(valueRemoveBefore.metric_id(), valueRemove.id());
EXPECT_TRUE(valueRemoveBefore.has_value_metrics());
StatsLogReport::ValueMetricDataWrapper valueMetrics;
sortMetricDataByDimensionsValue(valueRemoveBefore.value_metrics(), &valueMetrics);
ASSERT_EQ(valueMetrics.data_size(), 1);
ValueMetricData data = valueMetrics.data(0);
EXPECT_FALSE(data.has_dimensions_in_what());
EXPECT_EQ(data.slice_by_state_size(), 0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateValueBucket(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, 20, 0);
// Min screen brightness while screen on. Bucket skipped due to condition unknown.
StatsLogReport valuePushPersistBefore = report.metrics(2);
EXPECT_EQ(valuePushPersistBefore.metric_id(), valuePushPersist.id());
EXPECT_TRUE(valuePushPersistBefore.has_value_metrics());
ASSERT_EQ(valuePushPersistBefore.value_metrics().data_size(), 0);
ASSERT_EQ(valuePushPersistBefore.value_metrics().skipped_size(), 1);
skipBucket = valuePushPersistBefore.value_metrics().skipped(0);
EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketStartNs);
EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedUpdateTimeNs);
ASSERT_EQ(skipBucket.drop_event_size(), 1);
EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::CONDITION_UNKNOWN);
// Pull Subsystem sleep state. Value is Pull #3 (900) - Pull#1 (100).
StatsLogReport valueChangeBefore = report.metrics(3);
EXPECT_EQ(valueChangeBefore.metric_id(), valueChange.id());
EXPECT_TRUE(valueChangeBefore.has_value_metrics());
valueMetrics.Clear();
sortMetricDataByDimensionsValue(valueChangeBefore.value_metrics(), &valueMetrics);
ASSERT_EQ(valueMetrics.data_size(), 2);
data = valueMetrics.data(0);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateValueBucket(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, 800, 0);
data = valueMetrics.data(1);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateValueBucket(data.bucket_info(0), roundedBucketStartNs, roundedUpdateTimeNs, 800, 0);
// Report from after update.
report = reports.reports(1);
ASSERT_EQ(report.metrics_size(), 4);
// Pull subsystem sleep while screen on.
// Pull#4 (1600) - pull#3 (900) + pull#8 (6400) - pull#6 (3600)
StatsLogReport valueChangeAfter = report.metrics(0);
EXPECT_EQ(valueChangeAfter.metric_id(), valueChange.id());
EXPECT_TRUE(valueChangeAfter.has_value_metrics());
valueMetrics.Clear();
sortMetricDataByDimensionsValue(valueChangeAfter.value_metrics(), &valueMetrics);
int64_t conditionTrueNs = bucketSizeNs - 60 * NS_PER_SEC + 10 * NS_PER_SEC;
ASSERT_EQ(valueMetrics.data_size(), 2);
data = valueMetrics.data(0);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 3500,
conditionTrueNs);
ASSERT_EQ(valueMetrics.data_size(), 2);
data = valueMetrics.data(1);
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2");
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 3500,
conditionTrueNs);
ASSERT_EQ(valueChangeAfter.value_metrics().skipped_size(), 1);
skipBucket = valueChangeAfter.value_metrics().skipped(0);
EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketEndNs);
EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedDumpTimeNs);
ASSERT_EQ(skipBucket.drop_event_size(), 1);
EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::DUMP_REPORT_REQUESTED);
// Max screen brightness, no condition. Val is 40 in first bucket, 50 in second.
StatsLogReport valueNewAfter = report.metrics(1);
EXPECT_EQ(valueNewAfter.metric_id(), valueNew.id());
EXPECT_TRUE(valueNewAfter.has_value_metrics());
valueMetrics.Clear();
sortMetricDataByDimensionsValue(valueNewAfter.value_metrics(), &valueMetrics);
ASSERT_EQ(valueMetrics.data_size(), 1);
data = valueMetrics.data(0);
ASSERT_EQ(data.bucket_info_size(), 2);
ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 40, 0);
ValidateValueBucket(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, 50, 0);
// Min screen brightness when screen on. Val is 30 in first bucket, 50 in second.
StatsLogReport valuePushPersistAfter = report.metrics(2);
EXPECT_EQ(valuePushPersistAfter.metric_id(), valuePushPersist.id());
EXPECT_TRUE(valuePushPersistAfter.has_value_metrics());
valueMetrics.Clear();
sortMetricDataByDimensionsValue(valuePushPersistAfter.value_metrics(), &valueMetrics);
ASSERT_EQ(valueMetrics.data_size(), 1);
data = valueMetrics.data(0);
ASSERT_EQ(data.bucket_info_size(), 2);
conditionTrueNs = bucketSizeNs - 60 * NS_PER_SEC + 10 * NS_PER_SEC;
ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 30,
conditionTrueNs);
ValidateValueBucket(data.bucket_info(1), roundedBucketEndNs, roundedDumpTimeNs, 50,
10 * NS_PER_SEC);
// TODO(b/179725160): fix assertions.
// Subsystem sleep state while unplugged slice screen.
StatsLogReport valuePullPersistAfter = report.metrics(3);
EXPECT_EQ(valuePullPersistAfter.metric_id(), valuePullPersist.id());
EXPECT_TRUE(valuePullPersistAfter.has_value_metrics());
valueMetrics.Clear();
sortMetricDataByDimensionsValue(valuePullPersistAfter.value_metrics(), &valueMetrics);
ASSERT_EQ(valueMetrics.data_size(), 4);
// Name 1, screen OFF. Pull#5 (2500) - pull#4 (1600).
data = valueMetrics.data(0);
conditionTrueNs = 10 * NS_PER_SEC;
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1");
ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 900, -1);
// Name 1, screen ON. Pull#4 (1600) - pull#3 (900) + pull#8 (6400) - pull#7 (4900).
data = valueMetrics.data(1);
conditionTrueNs = 10 * NS_PER_SEC + bucketSizeNs - 65 * NS_PER_SEC;
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_1");
ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED,
android::view::DisplayStateEnum::DISPLAY_STATE_ON);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 2200, -1);
// Name 2, screen OFF. Pull#5 (2500) - pull#4 (1600).
data = valueMetrics.data(2);
conditionTrueNs = 10 * NS_PER_SEC;
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2");
ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 900, -1);
// Name 2, screen ON. Pull#4 (1600) - pull#3 (900) + pull#8 (6400) - pull#7 (4900).
data = valueMetrics.data(3);
conditionTrueNs = 10 * NS_PER_SEC + bucketSizeNs - 65 * NS_PER_SEC;
ValidateSubsystemSleepDimension(data.dimensions_in_what(), "subsystem_name_2");
ValidateStateValue(data.slice_by_state(), util::SCREEN_STATE_CHANGED,
android::view::DisplayStateEnum::DISPLAY_STATE_ON);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateValueBucket(data.bucket_info(0), roundedUpdateTimeNs, roundedBucketEndNs, 2200, -1);
ASSERT_EQ(valuePullPersistAfter.value_metrics().skipped_size(), 1);
skipBucket = valuePullPersistAfter.value_metrics().skipped(0);
EXPECT_EQ(skipBucket.start_bucket_elapsed_nanos(), roundedBucketEndNs);
EXPECT_EQ(skipBucket.end_bucket_elapsed_nanos(), roundedDumpTimeNs);
ASSERT_EQ(skipBucket.drop_event_size(), 1);
EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::DUMP_REPORT_REQUESTED);
}
TEST_F(ConfigUpdateE2eTest, TestMetricActivation) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
string immediateTag = "immediate", bootTag = "boot", childTag = "child";
AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher();
*config.add_atom_matcher() = syncStartMatcher;
AtomMatcher immediateMatcher =
CreateSimpleAtomMatcher("immediateMatcher", util::WAKELOCK_STATE_CHANGED);
FieldValueMatcher* fvm =
immediateMatcher.mutable_simple_atom_matcher()->add_field_value_matcher();
fvm->set_field(3); // Tag.
fvm->set_eq_string(immediateTag);
*config.add_atom_matcher() = immediateMatcher;
AtomMatcher bootMatcher = CreateSimpleAtomMatcher("bootMatcher", util::WAKELOCK_STATE_CHANGED);
fvm = bootMatcher.mutable_simple_atom_matcher()->add_field_value_matcher();
fvm->set_field(3); // Tag.
fvm->set_eq_string(bootTag);
*config.add_atom_matcher() = bootMatcher;
AtomMatcher childMatcher =
CreateSimpleAtomMatcher("childMatcher", util::WAKELOCK_STATE_CHANGED);
fvm = childMatcher.mutable_simple_atom_matcher()->add_field_value_matcher();
fvm->set_field(3); // Tag.
fvm->set_eq_string(childTag);
*config.add_atom_matcher() = childMatcher;
AtomMatcher acquireMatcher = CreateAcquireWakelockAtomMatcher();
*config.add_atom_matcher() = acquireMatcher;
AtomMatcher combinationMatcher;
combinationMatcher.set_id(StringToId("combination"));
AtomMatcher_Combination* combination = combinationMatcher.mutable_combination();
combination->set_operation(LogicalOperation::OR);
combination->add_matcher(acquireMatcher.id());
combination->add_matcher(childMatcher.id());
*config.add_atom_matcher() = combinationMatcher;
CountMetric immediateMetric =
createCountMetric("ImmediateMetric", syncStartMatcher.id(), nullopt, {});
CountMetric bootMetric = createCountMetric("BootMetric", syncStartMatcher.id(), nullopt, {});
CountMetric combinationMetric =
createCountMetric("CombinationMetric", syncStartMatcher.id(), nullopt, {});
*config.add_count_metric() = immediateMetric;
*config.add_count_metric() = bootMetric;
*config.add_count_metric() = combinationMetric;
MetricActivation immediateMetricActivation;
immediateMetricActivation.set_metric_id(immediateMetric.id());
auto eventActivation = immediateMetricActivation.add_event_activation();
eventActivation->set_activation_type(ActivationType::ACTIVATE_IMMEDIATELY);
eventActivation->set_atom_matcher_id(immediateMatcher.id());
eventActivation->set_ttl_seconds(60); // One minute.
*config.add_metric_activation() = immediateMetricActivation;
MetricActivation bootMetricActivation;
bootMetricActivation.set_metric_id(bootMetric.id());
eventActivation = bootMetricActivation.add_event_activation();
eventActivation->set_activation_type(ActivationType::ACTIVATE_ON_BOOT);
eventActivation->set_atom_matcher_id(bootMatcher.id());
eventActivation->set_ttl_seconds(60); // One minute.
*config.add_metric_activation() = bootMetricActivation;
MetricActivation combinationMetricActivation;
combinationMetricActivation.set_metric_id(combinationMetric.id());
eventActivation = combinationMetricActivation.add_event_activation();
eventActivation->set_activation_type(ActivationType::ACTIVATE_IMMEDIATELY);
eventActivation->set_atom_matcher_id(combinationMatcher.id());
eventActivation->set_ttl_seconds(60); // One minute.
*config.add_metric_activation() = combinationMetricActivation;
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
sp<StatsLogProcessor> processor =
CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
int uid1 = 55555;
// Initialize log events before update.
// Counts provided in order of immediate, boot, and combination metric.
std::vector<std::unique_ptr<LogEvent>> events;
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 5 * NS_PER_SEC, {uid1}, {""},
"")); // Count: 0, 0, 0.
events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC, {uid1}, {""},
immediateTag)); // Activate immediate metric.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 15 * NS_PER_SEC, {uid1}, {""},
"")); // Count: 1, 0, 0.
events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC, {uid1}, {""},
"foo")); // Activate combination metric.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 25 * NS_PER_SEC, {uid1}, {""},
"")); // Count: 2, 0, 1.
events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC, {uid1}, {""},
bootTag)); // Boot metric pending activation.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 35 * NS_PER_SEC, {uid1}, {""},
"")); // Count: 3, 0, 2.
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->OnLogEvent(event.get());
}
// Do update. Add matchers/conditions in different order to force indices to change.
StatsdConfig newConfig;
newConfig.add_allowed_log_source("AID_ROOT");
newConfig.set_hash_strings_in_metric_report(false); // Modify metadata for fun.
// Change combination matcher, will mean combination metric isn't active after update.
combinationMatcher.mutable_combination()->set_operation(LogicalOperation::AND);
*newConfig.add_atom_matcher() = acquireMatcher;
*newConfig.add_atom_matcher() = bootMatcher;
*newConfig.add_atom_matcher() = combinationMatcher;
*newConfig.add_atom_matcher() = childMatcher;
*newConfig.add_atom_matcher() = syncStartMatcher;
*newConfig.add_atom_matcher() = immediateMatcher;
*newConfig.add_count_metric() = bootMetric;
*newConfig.add_count_metric() = combinationMetric;
*newConfig.add_count_metric() = immediateMetric;
*newConfig.add_metric_activation() = bootMetricActivation;
*newConfig.add_metric_activation() = combinationMetricActivation;
*newConfig.add_metric_activation() = immediateMetricActivation;
int64_t updateTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC;
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// The reboot will write to disk again, so sleep for 1 second to avoid this.
// TODO(b/178887128): clean this up.
std::this_thread::sleep_for(1000ms);
// Send event after the update. Counts reset to 0 since this is a new bucket.
processor->OnLogEvent(
CreateSyncStartEvent(bucketStartTimeNs + 45 * NS_PER_SEC, {uid1}, {""}, "")
.get()); // Count: 1, 0, 0.
// Fake a reboot. Code is from StatsService::informDeviceShutdown.
int64_t shutDownTimeNs = bucketStartTimeNs + 50 * NS_PER_SEC;
processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, shutDownTimeNs);
processor->SaveActiveConfigsToDisk(shutDownTimeNs);
processor->SaveMetadataToDisk(getWallClockNs(), shutDownTimeNs);
// On boot, use StartUp. However, skip config manager for simplicity.
int64_t bootTimeNs = bucketStartTimeNs + 55 * NS_PER_SEC;
processor = CreateStatsLogProcessor(bootTimeNs, bootTimeNs, newConfig, key);
processor->LoadActiveConfigsFromDisk();
processor->LoadMetadataFromDisk(getWallClockNs(), bootTimeNs);
// Send events after boot. Counts reset to 0 since this is a new bucket. Boot metric now active.
events.clear();
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, {uid1}, {""},
"")); // Count: 1, 1, 0.
int64_t deactivationTimeNs = bucketStartTimeNs + 76 * NS_PER_SEC;
events.push_back(CreateScreenStateChangedEvent(
deactivationTimeNs,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // TTLs immediate metric.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 80 * NS_PER_SEC, {uid1}, {""},
"")); // Count: 1, 2, 0.
events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 85 * NS_PER_SEC, {uid1}, {""},
childTag)); // Activate combination metric.
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 90 * NS_PER_SEC, {uid1}, {""},
"")); // Count: 1, 3, 1.
// Send log events to StatsLogProcessor.
for (auto& event : events) {
processor->OnLogEvent(event.get());
}
uint64_t dumpTimeNs = bucketStartTimeNs + 90 * NS_PER_SEC;
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(), 3);
// Report from before update.
ConfigMetricsReport report = reports.reports(0);
EXPECT_EQ(report.last_report_elapsed_nanos(), bucketStartTimeNs);
EXPECT_EQ(report.current_report_elapsed_nanos(), updateTimeNs);
ASSERT_EQ(report.metrics_size(), 3);
// Immediate metric. Count = 3.
StatsLogReport metricReport = report.metrics(0);
EXPECT_EQ(metricReport.metric_id(), immediateMetric.id());
EXPECT_TRUE(metricReport.is_active());
EXPECT_TRUE(metricReport.has_count_metrics());
ASSERT_EQ(metricReport.count_metrics().data_size(), 1);
auto data = metricReport.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 3);
// Boot metric. Count = 0.
metricReport = report.metrics(1);
EXPECT_EQ(metricReport.metric_id(), bootMetric.id());
EXPECT_FALSE(metricReport.is_active());
EXPECT_FALSE(metricReport.has_count_metrics());
// Combination metric. Count = 2.
metricReport = report.metrics(2);
EXPECT_EQ(metricReport.metric_id(), combinationMetric.id());
EXPECT_TRUE(metricReport.is_active());
EXPECT_TRUE(metricReport.has_count_metrics());
ASSERT_EQ(metricReport.count_metrics().data_size(), 1);
data = metricReport.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, updateTimeNs, 2);
// Report from after update, before boot.
report = reports.reports(1);
EXPECT_EQ(report.last_report_elapsed_nanos(), updateTimeNs);
EXPECT_EQ(report.current_report_elapsed_nanos(), shutDownTimeNs);
ASSERT_EQ(report.metrics_size(), 3);
// Boot metric. Count = 0.
metricReport = report.metrics(0);
EXPECT_EQ(metricReport.metric_id(), bootMetric.id());
EXPECT_FALSE(metricReport.is_active());
EXPECT_FALSE(metricReport.has_count_metrics());
// Combination metric. Count = 0.
metricReport = report.metrics(1);
EXPECT_EQ(metricReport.metric_id(), combinationMetric.id());
EXPECT_FALSE(metricReport.is_active());
EXPECT_FALSE(metricReport.has_count_metrics());
// Immediate metric. Count = 1.
metricReport = report.metrics(2);
EXPECT_EQ(metricReport.metric_id(), immediateMetric.id());
EXPECT_TRUE(metricReport.is_active());
EXPECT_TRUE(metricReport.has_count_metrics());
ASSERT_EQ(metricReport.count_metrics().data_size(), 1);
data = metricReport.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateCountBucket(data.bucket_info(0), updateTimeNs, shutDownTimeNs, 1);
// Report from after reboot.
report = reports.reports(2);
EXPECT_EQ(report.last_report_elapsed_nanos(), bootTimeNs);
EXPECT_EQ(report.current_report_elapsed_nanos(), dumpTimeNs);
ASSERT_EQ(report.metrics_size(), 3);
// Boot metric. Count = 3.
metricReport = report.metrics(0);
EXPECT_EQ(metricReport.metric_id(), bootMetric.id());
EXPECT_TRUE(metricReport.is_active());
EXPECT_TRUE(metricReport.has_count_metrics());
ASSERT_EQ(metricReport.count_metrics().data_size(), 1);
data = metricReport.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateCountBucket(data.bucket_info(0), bootTimeNs, dumpTimeNs, 3);
// Combination metric. Count = 1.
metricReport = report.metrics(1);
EXPECT_EQ(metricReport.metric_id(), combinationMetric.id());
EXPECT_TRUE(metricReport.is_active());
EXPECT_TRUE(metricReport.has_count_metrics());
ASSERT_EQ(metricReport.count_metrics().data_size(), 1);
data = metricReport.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateCountBucket(data.bucket_info(0), bootTimeNs, dumpTimeNs, 1);
// Immediate metric. Count = 1.
metricReport = report.metrics(2);
EXPECT_EQ(metricReport.metric_id(), immediateMetric.id());
EXPECT_FALSE(metricReport.is_active());
EXPECT_TRUE(metricReport.has_count_metrics());
ASSERT_EQ(metricReport.count_metrics().data_size(), 1);
data = metricReport.count_metrics().data(0);
ASSERT_EQ(data.bucket_info_size(), 1);
ValidateCountBucket(data.bucket_info(0), bootTimeNs, deactivationTimeNs, 1);
}
TEST_F(ConfigUpdateE2eTest, TestAnomalyCountMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher();
*config.add_atom_matcher() = syncStartMatcher;
AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
*config.add_atom_matcher() = wakelockAcquireMatcher;
CountMetric countWakelock =
createCountMetric("CountWakelock", wakelockAcquireMatcher.id(), nullopt, {});
*countWakelock.mutable_dimensions_in_what() =
CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
CountMetric countSync = createCountMetric("CountSync", syncStartMatcher.id(), nullopt, {});
*countSync.mutable_dimensions_in_what() =
CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
*config.add_count_metric() = countWakelock;
*config.add_count_metric() = countSync;
Alert alertPreserve =
createAlert("AlertPreserve", countWakelock.id(), /*buckets=*/2, /*triggerSumGt=*/1);
alertPreserve.set_refractory_period_secs(20);
Alert alertReplace = createAlert("AlertReplace", countSync.id(), 1, 1);
alertReplace.set_refractory_period_secs(1);
Alert alertRemove = createAlert("AlertRemove", countWakelock.id(), 1, 0);
alertRemove.set_refractory_period_secs(1);
*config.add_alert() = alertReplace;
*config.add_alert() = alertPreserve;
*config.add_alert() = alertRemove;
int preserveSubId = 1, replaceSubId = 2, removeSubId = 3;
Subscription preserveSub = createSubscription("S1", Subscription::ALERT, alertPreserve.id());
preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId);
Subscription replaceSub = createSubscription("S2", Subscription::ALERT, alertReplace.id());
replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId);
Subscription removeSub = createSubscription("S3", Subscription::ALERT, alertRemove.id());
removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId);
*config.add_subscription() = preserveSub;
*config.add_subscription() = removeSub;
*config.add_subscription() = replaceSub;
int app1Uid = 123, app2Uid = 456;
vector<int> attributionUids1 = {app1Uid};
vector<string> attributionTags1 = {"App1"};
vector<int> attributionUids2 = {app2Uid};
vector<string> attributionTags2 = {"App2"};
int64_t configUid = 123, configId = 987;
ConfigKey key(configUid, configId);
int alertPreserveCount = 0, alertRemoveCount = 0;
StatsDimensionsValueParcel alertPreserveDims;
StatsDimensionsValueParcel alertRemoveDims;
// The binder calls here will happen synchronously because they are in-process.
shared_ptr<MockPendingIntentRef> preserveBroadcast =
SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(),
alertPreserve.id(), _, _))
.Times(2)
.WillRepeatedly(
Invoke([&alertPreserveCount, &alertPreserveDims](
int64_t, int64_t, int64_t, int64_t, const vector<string>&,
const StatsDimensionsValueParcel& dimensionsValueParcel) {
alertPreserveCount++;
alertPreserveDims = dimensionsValueParcel;
return Status::ok();
}));
shared_ptr<MockPendingIntentRef> replaceBroadcast =
SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(),
alertReplace.id(), _, _))
.Times(0);
shared_ptr<MockPendingIntentRef> removeBroadcast =
SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(),
alertRemove.id(), _, _))
.Times(3)
.WillRepeatedly(
Invoke([&alertRemoveCount, &alertRemoveDims](
int64_t, int64_t, int64_t, int64_t, const vector<string>&,
const StatsDimensionsValueParcel& dimensionsValueParcel) {
alertRemoveCount++;
alertRemoveDims = dimensionsValueParcel;
return Status::ok();
}));
SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast);
SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast);
SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast);
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
sp<StatsLogProcessor> processor =
CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
StatsDimensionsValueParcel wlUid1 =
CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid);
StatsDimensionsValueParcel wlUid2 =
CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app2Uid);
processor->OnLogEvent(CreateAcquireWakelockEvent(bucketStartTimeNs + 15 * NS_PER_SEC,
attributionUids1, attributionTags1, "wl1")
.get());
EXPECT_EQ(alertPreserveCount, 0);
EXPECT_EQ(alertRemoveCount, 1);
EXPECT_EQ(alertRemoveDims, wlUid1);
processor->OnLogEvent(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC,
attributionUids2, attributionTags2, "wl2")
.get());
EXPECT_EQ(alertPreserveCount, 0);
EXPECT_EQ(alertRemoveCount, 2);
EXPECT_EQ(alertRemoveDims, wlUid2);
processor->OnLogEvent(CreateSyncStartEvent(bucket2StartTimeNs + 5 * NS_PER_SEC,
attributionUids1, attributionTags1, "sync1")
.get());
EXPECT_EQ(alertPreserveCount, 0);
EXPECT_EQ(alertRemoveCount, 2);
// AlertPreserve enters 30 sec refractory period for uid2.
processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 10 * NS_PER_SEC,
attributionUids2, attributionTags2, "wl2")
.get());
EXPECT_EQ(alertPreserveCount, 1);
EXPECT_EQ(alertPreserveDims, wlUid2);
EXPECT_EQ(alertRemoveCount, 3);
EXPECT_EQ(alertRemoveDims, wlUid2);
// Do config update.
StatsdConfig newConfig;
newConfig.add_allowed_log_source("AID_ROOT");
*newConfig.add_atom_matcher() = wakelockAcquireMatcher;
*newConfig.add_atom_matcher() = syncStartMatcher;
// Clear dims of sync metric, will result in alertReplace getting replaced.
countSync.clear_dimensions_in_what();
*newConfig.add_count_metric() = countSync;
*newConfig.add_count_metric() = countWakelock;
// New alert on existing metric. Should get current full bucket, but not history of 1st bucket.
Alert alertNew = createAlert("AlertNew", countWakelock.id(), /*buckets=*/1, /*triggerSumGt=*/1);
*newConfig.add_alert() = alertPreserve;
*newConfig.add_alert() = alertNew;
*newConfig.add_alert() = alertReplace;
int newSubId = 4;
Subscription newSub = createSubscription("S4", Subscription::ALERT, alertNew.id());
newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId);
*newConfig.add_subscription() = newSub;
*newConfig.add_subscription() = replaceSub;
*newConfig.add_subscription() = preserveSub;
int alertNewCount = 0;
StatsDimensionsValueParcel alertNewDims;
shared_ptr<MockPendingIntentRef> newBroadcast =
SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
EXPECT_CALL(*newBroadcast,
sendSubscriberBroadcast(configUid, configId, newSub.id(), alertNew.id(), _, _))
.Times(1)
.WillRepeatedly(
Invoke([&alertNewCount, &alertNewDims](
int64_t, int64_t, int64_t, int64_t, const vector<string>&,
const StatsDimensionsValueParcel& dimensionsValueParcel) {
alertNewCount++;
alertNewDims = dimensionsValueParcel;
return Status::ok();
}));
SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
int64_t updateTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC;
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Within refractory of AlertPreserve, but AlertNew should fire since the full bucket has 2.
processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 20 * NS_PER_SEC,
attributionUids2, attributionTags2, "wl2")
.get());
EXPECT_EQ(alertPreserveCount, 1);
EXPECT_EQ(alertNewCount, 1);
EXPECT_EQ(alertNewDims, wlUid2);
// Wakelock for uid1 fired in first bucket, alert preserve should keep the history and fire.
processor->OnLogEvent(CreateAcquireWakelockEvent(bucket2StartTimeNs + 25 * NS_PER_SEC,
attributionUids1, attributionTags1, "wl1")
.get());
EXPECT_EQ(alertPreserveCount, 2);
EXPECT_EQ(alertPreserveDims, wlUid1);
EXPECT_EQ(alertNewCount, 1);
processor->OnLogEvent(CreateSyncStartEvent(bucket2StartTimeNs + 30 * NS_PER_SEC,
attributionUids1, attributionTags1, "sync1")
.get());
EXPECT_EQ(alertPreserveCount, 2);
EXPECT_EQ(alertNewCount, 1);
EXPECT_EQ(alertRemoveCount, 3);
// Clear data so it doesn't stay on disk.
vector<uint8_t> buffer;
processor->onDumpReport(key, bucket2StartTimeNs + 100 * NS_PER_SEC, true, true, ADB_DUMP, FAST,
&buffer);
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId);
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId);
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId);
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
}
TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
*config.add_atom_matcher() = wakelockAcquireMatcher;
AtomMatcher wakelockReleaseMatcher = CreateReleaseWakelockAtomMatcher();
*config.add_atom_matcher() = wakelockReleaseMatcher;
AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
*config.add_atom_matcher() = screenOnMatcher;
AtomMatcher screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
*config.add_atom_matcher() = screenOffMatcher;
Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate();
*holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
*config.add_predicate() = holdingWakelockPredicate;
Predicate screenOnPredicate = CreateScreenIsOnPredicate();
*config.add_predicate() = screenOnPredicate;
DurationMetric durationWakelock =
createDurationMetric("DurWakelock", holdingWakelockPredicate.id(), nullopt, {});
*durationWakelock.mutable_dimensions_in_what() =
CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
DurationMetric durationScreen =
createDurationMetric("DurScreen", screenOnPredicate.id(), nullopt, {});
*config.add_duration_metric() = durationScreen;
*config.add_duration_metric() = durationWakelock;
Alert alertPreserve = createAlert("AlertPreserve", durationWakelock.id(), /*buckets=*/2,
/*triggerSumGt=*/30 * NS_PER_SEC);
alertPreserve.set_refractory_period_secs(300);
Alert alertReplace = createAlert("AlertReplace", durationScreen.id(), 2, 30 * NS_PER_SEC);
alertReplace.set_refractory_period_secs(1);
Alert alertRemove = createAlert("AlertRemove", durationWakelock.id(), 5, 10 * NS_PER_SEC);
alertRemove.set_refractory_period_secs(1);
*config.add_alert() = alertReplace;
*config.add_alert() = alertPreserve;
*config.add_alert() = alertRemove;
int preserveSubId = 1, replaceSubId = 2, removeSubId = 3;
Subscription preserveSub = createSubscription("S1", Subscription::ALERT, alertPreserve.id());
preserveSub.mutable_broadcast_subscriber_details()->set_subscriber_id(preserveSubId);
Subscription replaceSub = createSubscription("S2", Subscription::ALERT, alertReplace.id());
replaceSub.mutable_broadcast_subscriber_details()->set_subscriber_id(replaceSubId);
Subscription removeSub = createSubscription("S3", Subscription::ALERT, alertRemove.id());
removeSub.mutable_broadcast_subscriber_details()->set_subscriber_id(removeSubId);
*config.add_subscription() = preserveSub;
*config.add_subscription() = removeSub;
*config.add_subscription() = replaceSub;
int app1Uid = 123, app2Uid = 456;
vector<int> attributionUids1 = {app1Uid};
vector<string> attributionTags1 = {"App1"};
vector<int> attributionUids2 = {app2Uid};
vector<string> attributionTags2 = {"App2"};
int64_t configUid = 123, configId = 987;
ConfigKey key(configUid, configId);
int alertPreserveCount = 0, alertRemoveCount = 0;
StatsDimensionsValueParcel alertPreserveDims;
StatsDimensionsValueParcel alertRemoveDims;
// The binder calls here will happen synchronously because they are in-process.
shared_ptr<MockPendingIntentRef> preserveBroadcast =
SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
EXPECT_CALL(*preserveBroadcast, sendSubscriberBroadcast(configUid, configId, preserveSub.id(),
alertPreserve.id(), _, _))
.Times(2)
.WillRepeatedly(
Invoke([&alertPreserveCount, &alertPreserveDims](
int64_t, int64_t, int64_t, int64_t, const vector<string>&,
const StatsDimensionsValueParcel& dimensionsValueParcel) {
alertPreserveCount++;
alertPreserveDims = dimensionsValueParcel;
return Status::ok();
}));
shared_ptr<MockPendingIntentRef> replaceBroadcast =
SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
EXPECT_CALL(*replaceBroadcast, sendSubscriberBroadcast(configUid, configId, replaceSub.id(),
alertReplace.id(), _, _))
.Times(0);
shared_ptr<MockPendingIntentRef> removeBroadcast =
SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
EXPECT_CALL(*removeBroadcast, sendSubscriberBroadcast(configUid, configId, removeSub.id(),
alertRemove.id(), _, _))
.Times(2)
.WillRepeatedly(
Invoke([&alertRemoveCount, &alertRemoveDims](
int64_t, int64_t, int64_t, int64_t, const vector<string>&,
const StatsDimensionsValueParcel& dimensionsValueParcel) {
alertRemoveCount++;
alertRemoveDims = dimensionsValueParcel;
return Status::ok();
}));
SubscriberReporter::getInstance().setBroadcastSubscriber(key, preserveSubId, preserveBroadcast);
SubscriberReporter::getInstance().setBroadcastSubscriber(key, replaceSubId, replaceBroadcast);
SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast);
shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
sp<StatsLogProcessor> processor = service->mProcessor;
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
int64_t bucketStartTimeNs = processor->mTimeBaseNs;
int64_t roundedBucketStartTimeNs = bucketStartTimeNs / NS_PER_SEC * NS_PER_SEC;
uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
processor->OnConfigUpdated(bucketStartTimeNs, key, config);
StatsDimensionsValueParcel wlUid1 =
CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid);
StatsDimensionsValueParcel wlUid2 =
CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app2Uid);
int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
processor->OnLogEvent(
CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1")
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 0);
EXPECT_EQ(alertRemoveCount, 0);
eventTimeNs = bucketStartTimeNs + 20 * NS_PER_SEC;
processor->OnLogEvent(CreateScreenStateChangedEvent(
eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON)
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 0);
EXPECT_EQ(alertRemoveCount, 0);
eventTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC;
processor->OnLogEvent(
CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1")
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 0);
EXPECT_EQ(alertRemoveCount, 1);
EXPECT_EQ(alertRemoveDims, wlUid1);
eventTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC;
processor->OnLogEvent(CreateScreenStateChangedEvent(
eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF)
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 0);
EXPECT_EQ(alertRemoveCount, 1);
eventTimeNs = bucket2StartTimeNs + 5 * NS_PER_SEC;
processor->OnLogEvent(
CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2")
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 0);
EXPECT_EQ(alertRemoveCount, 1);
// Acts as the alarm for uid 2 wakelock, also starts the timer for screen on.
// Preserve enters 5 min refractory for uid 2.
eventTimeNs = bucket2StartTimeNs + 36 * NS_PER_SEC;
processor->OnLogEvent(CreateScreenStateChangedEvent(
eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_ON)
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 1);
EXPECT_EQ(alertPreserveDims, wlUid2);
EXPECT_EQ(alertRemoveCount, 2);
EXPECT_EQ(alertRemoveDims, wlUid2);
eventTimeNs = bucket2StartTimeNs + 37 * NS_PER_SEC;
processor->OnLogEvent(
CreateReleaseWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2")
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 1);
EXPECT_EQ(alertRemoveCount, 2);
// Do config update.
StatsdConfig newConfig;
newConfig.add_allowed_log_source("AID_ROOT");
*newConfig.add_atom_matcher() = wakelockAcquireMatcher;
*newConfig.add_atom_matcher() = screenOffMatcher;
*newConfig.add_atom_matcher() = wakelockReleaseMatcher;
*newConfig.add_atom_matcher() = screenOnMatcher;
*newConfig.add_predicate() = screenOnPredicate;
*newConfig.add_predicate() = holdingWakelockPredicate;
*newConfig.add_duration_metric() = durationWakelock;
*newConfig.add_duration_metric() = durationScreen;
alertReplace.set_refractory_period_secs(2); // Causes replacement.
// New alert on existing metric. Should get current full bucket, but not history of 1st bucket.
Alert alertNew = createAlert("AlertNew", durationWakelock.id(), /*buckets=*/2,
/*triggerSumGt=*/40 * NS_PER_SEC);
*newConfig.add_alert() = alertPreserve;
*newConfig.add_alert() = alertNew;
*newConfig.add_alert() = alertReplace;
int newSubId = 4;
Subscription newSub = createSubscription("S4", Subscription::ALERT, alertNew.id());
newSub.mutable_broadcast_subscriber_details()->set_subscriber_id(newSubId);
*newConfig.add_subscription() = newSub;
*newConfig.add_subscription() = replaceSub;
*newConfig.add_subscription() = preserveSub;
int alertNewCount = 0;
StatsDimensionsValueParcel alertNewDims;
shared_ptr<MockPendingIntentRef> newBroadcast =
SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
EXPECT_CALL(*newBroadcast,
sendSubscriberBroadcast(configUid, configId, newSub.id(), alertNew.id(), _, _))
.Times(1)
.WillRepeatedly(
Invoke([&alertNewCount, &alertNewDims](
int64_t, int64_t, int64_t, int64_t, const vector<string>&,
const StatsDimensionsValueParcel& dimensionsValueParcel) {
alertNewCount++;
alertNewDims = dimensionsValueParcel;
return Status::ok();
}));
SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
int64_t updateTimeNs = bucket2StartTimeNs + 40 * NS_PER_SEC;
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Alert preserve will set alarm after the refractory period, but alert new will set it for
// 8 seconds after this event.
eventTimeNs = bucket2StartTimeNs + 45 * NS_PER_SEC;
processor->OnLogEvent(
CreateAcquireWakelockEvent(eventTimeNs, attributionUids2, attributionTags2, "wl2")
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 1);
EXPECT_EQ(alertNewCount, 0);
eventTimeNs = bucket2StartTimeNs + 50 * NS_PER_SEC;
// Alert replace doesn't fire because it has lost history.
processor->OnLogEvent(CreateScreenStateChangedEvent(
eventTimeNs, android::view::DisplayStateEnum::DISPLAY_STATE_OFF)
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 1);
EXPECT_EQ(alertNewCount, 0);
// Alert preserve has 15 seconds from 1st bucket, so alert should fire at bucket2Start + 70.
// Serves as alarm for alert new for uid2.
eventTimeNs = bucket2StartTimeNs + 55 * NS_PER_SEC;
processor->OnLogEvent(
CreateAcquireWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1")
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 1);
EXPECT_EQ(alertNewCount, 1);
EXPECT_EQ(alertNewDims, wlUid2);
eventTimeNs = bucket2StartTimeNs + 71 * NS_PER_SEC;
processor->OnLogEvent(
CreateReleaseWakelockEvent(eventTimeNs, attributionUids1, attributionTags1, "wl1")
.get(),
eventTimeNs);
EXPECT_EQ(alertPreserveCount, 2);
EXPECT_EQ(alertPreserveDims, wlUid1);
EXPECT_EQ(alertNewCount, 1);
// Clear data so it doesn't stay on disk.
vector<uint8_t> buffer;
processor->onDumpReport(key, bucket2StartTimeNs + 100 * NS_PER_SEC, true, true, ADB_DUMP, FAST,
&buffer);
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, preserveSubId);
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, replaceSubId);
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, removeSubId);
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
}
TEST_F(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);
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_F(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);
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_F(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);
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
} // namespace statsd
} // namespace os
} // namespace android