/* * Copyright (C) 2021 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 "reporter.h" #include "gtest/gtest.h" #include "common_runtime_test.h" #include "base/metrics/metrics.h" #pragma clang diagnostic push #pragma clang diagnostic error "-Wconversion" namespace art HIDDEN { namespace metrics { // Helper class to verify the metrics reporter. // The functionality is identical to the MetricsReporter with the exception of // the metrics source. Instead of taking its metrics from the current Runtime, // this class will keep its own copy so that it does not get interference from // other runtime setup logic. class MockMetricsReporter : public MetricsReporter { protected: MockMetricsReporter(const ReportingConfig& config, Runtime* runtime) : MetricsReporter(config, runtime), art_metrics_(std::make_unique()) {} ArtMetrics* GetMetrics() override { return art_metrics_.get(); } std::unique_ptr art_metrics_; friend class MetricsReporterTest; }; // A test backend which keeps track of all metrics reporting. class TestBackend : public MetricsBackend { public: struct Report { uint64_t timestamp_millis; SafeMap data; }; void BeginOrUpdateSession(const SessionData& session_data) override { session_data_ = session_data; } void BeginReport(uint64_t timestamp_millis) override { current_report_.reset(new Report()); current_report_->timestamp_millis = timestamp_millis; } void ReportCounter(DatumId counter_type, uint64_t value) override { current_report_->data.Put(counter_type, value); } void ReportHistogram([[maybe_unused]] DatumId histogram_type, [[maybe_unused]] int64_t low_value, [[maybe_unused]] int64_t high_value, [[maybe_unused]] const std::vector& buckets) override { // TODO: nothing yet. We should implement and test histograms as well. } void EndReport() override { reports_.push_back(*current_report_); current_report_ = nullptr; } const std::vector& GetReports() { return reports_; } const SessionData& GetSessionData() { return session_data_; } private: SessionData session_data_; std::vector reports_; std::unique_ptr current_report_; }; // The actual metrics test class class MetricsReporterTest : public CommonRuntimeTest { protected: void SetUp() override { // Do the normal setup. CommonRuntimeTest::SetUp(); // We need to start the runtime in order to run threads. Thread::Current()->TransitionFromSuspendedToRunnable(); bool started = runtime_->Start(); CHECK(started); } // Configures the metric reporting. void SetupReporter(const char* period_spec, uint32_t session_id = 1, uint32_t reporting_mods = 100) { ReportingConfig config; if (period_spec != nullptr) { std::string error; config.reporting_mods = reporting_mods; config.period_spec = ReportingPeriodSpec::Parse(period_spec, &error); ASSERT_TRUE(config.period_spec.has_value()); } reporter_.reset(new MockMetricsReporter(std::move(config), Runtime::Current())); backend_ = new TestBackend(); reporter_->backends_.emplace_back(backend_); session_data_ = metrics::SessionData::CreateDefault(); session_data_.session_id = session_id; } void TearDown() override { reporter_ = nullptr; backend_ = nullptr; } bool ShouldReportAtStartup() { return reporter_->ShouldReportAtStartup(); } bool ShouldContinueReporting() { return reporter_->ShouldContinueReporting(); } uint32_t GetNextPeriodSeconds() { return reporter_->GetNextPeriodSeconds(); } void ReportMetrics() { reporter_->ReportMetrics(); } void NotifyStartupCompleted() { reporter_->NotifyStartupCompleted(); } // Starts the reporting thread and adds some metrics if necessary. bool MaybeStartBackgroundThread(bool add_metrics) { // TODO: set session_data.compilation_reason and session_data.compiler_filter bool result = reporter_->MaybeStartBackgroundThread(session_data_); if (add_metrics) { reporter_->art_metrics_->JitMethodCompileCount()->Add(1); reporter_->art_metrics_->ClassVerificationCount()->Add(2); } return result; } // Right now we either: // 1) don't add metrics (with_metrics = false) // 2) or always add the same metrics (see MaybeStartBackgroundThread) // So we can write a global verify method. void VerifyReports( uint32_t size, bool with_metrics, CompilerFilterReporting filter = CompilerFilterReporting::kUnknown, CompilationReason reason = CompilationReason::kUnknown) { // TODO: we should iterate through all the other metrics to make sure they were not // reported. However, we don't have an easy to use iteration mechanism over metrics yet. // We should add one ASSERT_EQ(backend_->GetReports().size(), size); for (const TestBackend::Report& report : backend_->GetReports()) { ASSERT_EQ(report.data.Get(DatumId::kClassVerificationCount), with_metrics ? 2u : 0u); ASSERT_EQ(report.data.Get(DatumId::kJitMethodCompileCount), with_metrics ? 1u : 0u); } ASSERT_EQ(backend_->GetSessionData().compiler_filter, filter); ASSERT_EQ(backend_->GetSessionData().compilation_reason, reason); } // Sleeps until the backend received the give number of reports. void WaitForReport(uint32_t report_count, uint32_t sleep_period_ms) { while (true) { if (backend_->GetReports().size() == report_count) { return; } usleep(sleep_period_ms * 1000); } } void NotifyAppInfoUpdated(AppInfo* app_info) { reporter_->NotifyAppInfoUpdated(app_info); } private: std::unique_ptr reporter_; TestBackend* backend_; metrics::SessionData session_data_; }; // Verifies startup reporting. TEST_F(MetricsReporterTest, StartupOnly) { SetupReporter("S"); // Verify startup conditions ASSERT_TRUE(ShouldReportAtStartup()); ASSERT_FALSE(ShouldContinueReporting()); // Start the thread and notify the startup. This will advance the state. MaybeStartBackgroundThread(/*add_metrics=*/ true); NotifyStartupCompleted(); WaitForReport(/*report_count=*/ 1, /*sleep_period_ms=*/ 50); VerifyReports(/*size=*/ 1, /*with_metrics*/ true); // We should still not report continuously. ASSERT_FALSE(ShouldContinueReporting()); } // LARGE TEST: This test takes 1s to run. // Verifies startup reporting, followed by a fixed, one time only reporting. TEST_F(MetricsReporterTest, StartupAndPeriod) { SetupReporter("S,1"); // Verify startup conditions ASSERT_TRUE(ShouldReportAtStartup()); ASSERT_FALSE(ShouldContinueReporting()); // Start the thread and notify the startup. This will advance the state. MaybeStartBackgroundThread(/*add_metrics=*/ true); NotifyStartupCompleted(); // We're waiting for 2 reports: the startup one, and the 1s one. WaitForReport(/*report_count=*/ 2, /*sleep_period_ms=*/ 500); VerifyReports(/*size=*/ 2, /*with_metrics*/ true); // We should no longer report continuously. ASSERT_FALSE(ShouldContinueReporting()); } // LARGE TEST: This test take 2s to run. // Verifies startup reporting, followed by continuous reporting. TEST_F(MetricsReporterTest, StartupAndPeriodContinuous) { SetupReporter("S,1,*"); // Verify startup conditions ASSERT_TRUE(ShouldReportAtStartup()); ASSERT_FALSE(ShouldContinueReporting()); // Start the thread and notify the startup. This will advance the state. MaybeStartBackgroundThread(/*add_metrics=*/ true); NotifyStartupCompleted(); // We're waiting for 3 reports: the startup one, and the 1s one. WaitForReport(/*report_count=*/ 3, /*sleep_period_ms=*/ 500); VerifyReports(/*size=*/ 3, /*with_metrics*/ true); // We should keep reporting continuously. ASSERT_TRUE(ShouldContinueReporting()); } // LARGE TEST: This test takes 1s to run. // Verifies a fixed, one time only reporting. TEST_F(MetricsReporterTest, OneTime) { SetupReporter("1"); // Verify startup conditions ASSERT_FALSE(ShouldReportAtStartup()); ASSERT_TRUE(ShouldContinueReporting()); // Start the thread and notify the startup. This will advance the state. MaybeStartBackgroundThread(/*add_metrics=*/ true); // We're waiting for 1 report WaitForReport(/*report_count=*/ 1, /*sleep_period_ms=*/ 500); VerifyReports(/*size=*/ 1, /*with_metrics*/ true); // We should no longer report continuously. ASSERT_FALSE(ShouldContinueReporting()); } // LARGE TEST: This test takes 5s to run. // Verifies a sequence of reporting, at different interval of times. TEST_F(MetricsReporterTest, PeriodContinuous) { SetupReporter("1,2,*"); // Verify startup conditions ASSERT_FALSE(ShouldReportAtStartup()); ASSERT_TRUE(ShouldContinueReporting()); // Start the thread and notify the startup. This will advance the state. MaybeStartBackgroundThread(/*add_metrics=*/ true); NotifyStartupCompleted(); // We're waiting for 2 reports: the startup one, and the 1s one. WaitForReport(/*report_count=*/ 3, /*sleep_period_ms=*/ 500); VerifyReports(/*size=*/ 3, /*with_metrics*/ true); // We should keep reporting continuously. ASSERT_TRUE(ShouldContinueReporting()); } // LARGE TEST: This test takes 1s to run. // Verifies reporting when no metrics where recorded. TEST_F(MetricsReporterTest, NoMetrics) { SetupReporter("1"); // Verify startup conditions ASSERT_FALSE(ShouldReportAtStartup()); ASSERT_TRUE(ShouldContinueReporting()); // Start the thread and notify the startup. This will advance the state. MaybeStartBackgroundThread(/*add_metrics=*/ false); // We're waiting for 1 report WaitForReport(/*report_count=*/ 1, /*sleep_period_ms=*/ 500); VerifyReports(/*size=*/ 1, /*with_metrics*/ false); // We should no longer report continuously. ASSERT_FALSE(ShouldContinueReporting()); } // Verify we don't start reporting if the sample rate is set to 0. TEST_F(MetricsReporterTest, SampleRateDisable) { SetupReporter("1,*", /*session_id=*/ 1, /*reporting_mods=*/ 0); // The background thread should not start. ASSERT_FALSE(MaybeStartBackgroundThread(/*add_metrics=*/ false)); ASSERT_FALSE(ShouldReportAtStartup()); ASSERT_FALSE(ShouldContinueReporting()); } // Verify we don't start reporting if the sample rate is low and the session does // not meet conditions. TEST_F(MetricsReporterTest, SampleRateDisable24) { SetupReporter("1,*", /*session_id=*/ 125, /*reporting_mods=*/ 24); // The background thread should not start. ASSERT_FALSE(MaybeStartBackgroundThread(/*add_metrics=*/ false)); ASSERT_FALSE(ShouldReportAtStartup()); ASSERT_FALSE(ShouldContinueReporting()); } // Verify we start reporting if the sample rate and the session meet // reporting conditions TEST_F(MetricsReporterTest, SampleRateEnable50) { SetupReporter("1,*", /*session_id=*/ 125, /*reporting_mods=*/ 50); // The background thread should start. ASSERT_TRUE(MaybeStartBackgroundThread(/*add_metrics=*/ false)); ASSERT_FALSE(ShouldReportAtStartup()); ASSERT_TRUE(ShouldContinueReporting()); } // Verify we start reporting if the sample rate and the session meet // reporting conditions TEST_F(MetricsReporterTest, SampleRateEnableAll) { SetupReporter("1,*", /*session_id=*/ 1099, /*reporting_mods=*/ 100); // The background thread should start. ASSERT_TRUE(MaybeStartBackgroundThread(/*add_metrics=*/ false)); ASSERT_FALSE(ShouldReportAtStartup()); ASSERT_TRUE(ShouldContinueReporting()); } TEST_F(MetricsReporterTest, CompilerFilter) { SetupReporter("1", /*session_id=*/ 1099, /*reporting_mods=*/ 100); ASSERT_TRUE(MaybeStartBackgroundThread(/*add_metrics=*/ true)); AppInfo app_info; app_info.RegisterOdexStatus( "code_location", "verify", "install", "odex_status"); app_info.RegisterAppInfo( "package_name", std::vector({"code_location"}), "", "", AppInfo::CodeType::kPrimaryApk); NotifyAppInfoUpdated(&app_info); WaitForReport(/*report_count=*/ 1, /*sleep_period_ms=*/ 500); VerifyReports( /*size=*/ 1, /*with_metrics*/ true, CompilerFilterReporting::kVerify, CompilationReason::kInstall); } // Test class for period spec parsing class ReportingPeriodSpecTest : public testing::Test { public: void VerifyFalse(const std::string& spec_str) { Verify(spec_str, false, false, false, {}); } void VerifyTrue( const std::string& spec_str, bool startup_first, bool continuous, const std::vector& periods) { Verify(spec_str, true, startup_first, continuous, periods); } void Verify( const std::string& spec_str, bool valid, bool startup_first, bool continuous, const std::vector& periods) { std::string error_msg; std::optional spec = ReportingPeriodSpec::Parse(spec_str, &error_msg); ASSERT_EQ(valid, spec.has_value()) << spec_str; if (valid) { ASSERT_EQ(spec->spec, spec_str) << spec_str; ASSERT_EQ(spec->report_startup_first, startup_first) << spec_str; ASSERT_EQ(spec->continuous_reporting, continuous) << spec_str; ASSERT_EQ(spec->periods_seconds, periods) << spec_str; } } }; TEST_F(ReportingPeriodSpecTest, ParseTestsInvalid) { VerifyFalse(""); VerifyFalse("*"); VerifyFalse("S,*"); VerifyFalse("foo"); VerifyFalse("-1"); VerifyFalse("1,S"); VerifyFalse("*,1"); VerifyFalse("1,2,3,-1,3"); VerifyFalse("1,*,2"); VerifyFalse("1,S,2"); } TEST_F(ReportingPeriodSpecTest, ParseTestsValid) { VerifyTrue("S", true, false, {}); VerifyTrue("S,1", true, false, {1}); VerifyTrue("S,1,2,3,4", true, false, {1, 2, 3, 4}); VerifyTrue("S,1,*", true, true, {1}); VerifyTrue("S,1,2,3,4,*", true, true, {1, 2, 3, 4}); VerifyTrue("1", false, false, {1}); VerifyTrue("1,2,3,4", false, false, {1, 2, 3, 4}); VerifyTrue("1,*", false, true, {1}); VerifyTrue("1,2,3,4,*", false, true, {1, 2, 3, 4}); } } // namespace metrics } // namespace art #pragma clang diagnostic pop // -Wconversion