// Copyright (C) 2017 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 #include #include #include #include "src/StatsLogProcessor.h" #include "src/StatsService.h" #include "src/stats_log_util.h" #include "tests/statsd_test_util.h" using::ndk::SharedRefBase; using std::shared_ptr; namespace android { namespace os { namespace statsd { #ifdef __ANDROID__ namespace { const string kApp1 = "app1.sharing.1"; const int kConfigKey = 789130123; // Randomly chosen to avoid collisions with existing configs. const int kCallingUid = 0; // Randomly chosen void SendConfig(shared_ptr& service, const StatsdConfig& config) { string str; config.SerializeToString(&str); std::vector configAsVec(str.begin(), str.end()); service->addConfiguration(kConfigKey, configAsVec, kCallingUid); } ConfigMetricsReport GetReports(sp processor, int64_t timestamp, bool include_current = false) { vector output; ConfigKey configKey(AIBinder_getCallingUid(), kConfigKey); processor->onDumpReport(configKey, timestamp, include_current /* include_current_bucket*/, true /* erase_data */, ADB_DUMP, NO_TIME_CONSTRAINTS, &output); ConfigMetricsReportList reports; reports.ParseFromArray(output.data(), output.size()); EXPECT_EQ(1, reports.reports_size()); return reports.reports(kCallingUid); } StatsdConfig MakeConfig() { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. auto appCrashMatcher = CreateProcessCrashAtomMatcher(); *config.add_atom_matcher() = appCrashMatcher; auto countMetric = config.add_count_metric(); countMetric->set_id(StringToId("AppCrashes")); countMetric->set_what(appCrashMatcher.id()); countMetric->set_bucket(FIVE_MINUTES); return config; } StatsdConfig MakeValueMetricConfig(int64_t minTime) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); *config.add_atom_matcher() = pulledAtomMatcher; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); auto valueMetric = config.add_value_metric(); valueMetric->set_id(123456); valueMetric->set_what(pulledAtomMatcher.id()); *valueMetric->mutable_value_field() = CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */}); *valueMetric->mutable_dimensions_in_what() = CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); valueMetric->set_bucket(FIVE_MINUTES); valueMetric->set_min_bucket_size_nanos(minTime); valueMetric->set_use_absolute_value_on_reset(true); valueMetric->set_skip_zero_diff_output(false); return config; } StatsdConfig MakeGaugeMetricConfig(int64_t minTime) { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root. auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE); *config.add_atom_matcher() = pulledAtomMatcher; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher(); auto gaugeMetric = config.add_gauge_metric(); gaugeMetric->set_id(123456); gaugeMetric->set_what(pulledAtomMatcher.id()); gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true); *gaugeMetric->mutable_dimensions_in_what() = CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */}); gaugeMetric->set_bucket(FIVE_MINUTES); gaugeMetric->set_min_bucket_size_nanos(minTime); return config; } } // anonymous namespace TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 2, 100).get()); ConfigMetricsReport report = GetReports(service->mProcessor, start + 3); // Expect no metrics since the bucket has not finished yet. ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(0, report.metrics(0).count_metrics().data_size()); } TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. // Force the uidmap to update at timestamp 2. service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); // This is a new installation, so there shouldn't be a split (should be same as the without // split case). service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); // Goes into the second bucket. service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(0, report.metrics(0).count_metrics().data_size()); } TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, {String16("")}); // Force the uidmap to update at timestamp 2. service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); service->mUidMap->updateApp(start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); // Goes into the second bucket. service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); EXPECT_TRUE(report.metrics(0) .count_metrics() .data(0) .bucket_info(0) .has_start_bucket_elapsed_nanos()); EXPECT_TRUE(report.metrics(0) .count_metrics() .data(0) .bucket_info(0) .has_end_bucket_elapsed_nanos()); EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); } TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. service->mUidMap->updateMap(start, {1}, {1}, {String16("v1")}, {String16(kApp1.c_str())}, {String16("")}); // Force the uidmap to update at timestamp 2. service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 1, 100).get()); service->mUidMap->removeApp(start + 2, String16(kApp1.c_str()), 1); // Goes into the second bucket. service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3, 100).get()); ConfigMetricsReport report = GetReports(service->mProcessor, start + 4); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); EXPECT_TRUE(report.metrics(0) .count_metrics() .data(0) .bucket_info(0) .has_start_bucket_elapsed_nanos()); EXPECT_TRUE(report.metrics(0) .count_metrics() .data(0) .bucket_info(0) .has_end_bucket_elapsed_nanos()); EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); } TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. // Goes into the first bucket service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + NS_PER_SEC, 100).get()); int64_t bootCompleteTimeNs = start + 2 * NS_PER_SEC; service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); // Goes into the second bucket. service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3 * NS_PER_SEC, 100).get()); ConfigMetricsReport report = GetReports(service->mProcessor, start + 4 * NS_PER_SEC); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); EXPECT_TRUE(report.metrics(0) .count_metrics() .data(0) .bucket_info(0) .has_start_bucket_elapsed_nanos()); EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), report.metrics(0).count_metrics().data(0).bucket_info(0).end_bucket_elapsed_nanos()); EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); } TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); service->mPullerManager->RegisterPullAtomCallback( /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, SharedRefBase::make()); // Partial buckets don't occur when app is first installed. service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeValueMetricConfig(0)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); int64_t appUpgradeTimeNs = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; service->mUidMap->updateApp(appUpgradeTimeNs, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(0, report.metrics(0).value_metrics().skipped_size()); // The fake subsystem state sleep puller returns two atoms. ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); ASSERT_EQ(2, report.metrics(0).value_metrics().data(0).bucket_info_size()); EXPECT_EQ(MillisToNano(NanoToMillis(appUpgradeTimeNs)), report.metrics(0).value_metrics().data(0).bucket_info(1).end_bucket_elapsed_nanos()); } TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); service->mPullerManager->RegisterPullAtomCallback( /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, SharedRefBase::make()); // Partial buckets don't occur when app is first installed. service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size()); EXPECT_TRUE(report.metrics(0).value_metrics().skipped(0).has_start_bucket_elapsed_nanos()); // Can't test the start time since it will be based on the actual time when the pulling occurs. EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); } TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); // Initial pull will fail since puller is not registered. SendConfig(service, MakeValueMetricConfig(0)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. service->mPullerManager->RegisterPullAtomCallback( /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, SharedRefBase::make()); int64_t bootCompleteTimeNs = start + NS_PER_SEC; service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); backfillStartEndTimestamp(&report); // First bucket is dropped due to the initial pull failing ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).value_metrics().skipped_size()); EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); // The fake subsystem state sleep puller returns two atoms. ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); EXPECT_EQ( MillisToNano(NanoToMillis(bootCompleteTimeNs)), report.metrics(0).value_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); } TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); service->mPullerManager->RegisterPullAtomCallback( /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, SharedRefBase::make()); // Partial buckets don't occur when app is first installed. service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeGaugeMetricConfig(0)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); // The fake subsystem state sleep puller returns two atoms. ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); ASSERT_EQ(2, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); } TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); // Partial buckets don't occur when app is first installed. service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); service->mPullerManager->RegisterPullAtomCallback( /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, SharedRefBase::make()); SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2; service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size()); // Can't test the start time since it will be based on the actual time when the pulling occurs. EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos()); EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), report.metrics(0).gauge_metrics().skipped(0).end_bucket_elapsed_nanos()); ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); } TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket) { shared_ptr service = SharedRefBase::make(nullptr, nullptr); // Initial pull will fail since puller hasn't been registered. SendConfig(service, MakeGaugeMetricConfig(0)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. service->mPullerManager->RegisterPullAtomCallback( /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, SharedRefBase::make()); int64_t bootCompleteTimeNs = start + NS_PER_SEC; service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); // The fake subsystem state sleep puller returns two atoms. ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); // No data in the first bucket, so nothing is reported ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); EXPECT_EQ( MillisToNano(NanoToMillis(bootCompleteTimeNs)), report.metrics(0).gauge_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); } #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif } // namespace statsd } // namespace os } // namespace android