blob: a61f8be75b06ee011126c194776c3324b59ad80c [file] [log] [blame]
/*
* 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 {
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>()) {}
ArtMetrics* GetMetrics() override { return art_metrics_.get(); }
std::unique_ptr<ArtMetrics> 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<DatumId, uint64_t> 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<uint32_t>& 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<Report>& GetReports() {
return reports_;
}
const SessionData& GetSessionData() {
return session_data_;
}
private:
SessionData session_data_;
std::vector<Report> reports_;
std::unique_ptr<Report> 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<MockMetricsReporter> 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<std::string>({"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<uint32_t>& 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<uint32_t>& periods) {
std::string error_msg;
std::optional<ReportingPeriodSpec> 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