diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-09-12 16:55:30 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-09-12 16:55:30 +0000 |
commit | 351d9e2999d1872a086cfea023262271b8477b1e (patch) | |
tree | f4fc2b4b650b89dcc015fe6ca8a00bbebfd6b7ff | |
parent | 9517f745d98887af209d461b1f3ee55ff4101431 (diff) | |
parent | 20b0129442ad9f4d713d0e036def0155decac003 (diff) | |
download | art-android13-mainline-appsearch-release.tar.gz |
Snap for 9052600 from 20b0129442ad9f4d713d0e036def0155decac003 to mainline-appsearch-releaseaml_ase_331311020aml_ase_331112000android13-mainline-appsearch-release
Change-Id: I57ac181866ea4fdd60084d6a2bc62999bfb6715c
-rw-r--r-- | libartbase/Android.bp | 5 | ||||
-rw-r--r-- | libartbase/base/flags.h | 6 | ||||
-rw-r--r-- | libartbase/base/metrics/metrics.h | 84 | ||||
-rw-r--r-- | libartbase/base/metrics/metrics_common.cc | 148 | ||||
-rw-r--r-- | libartbase/base/metrics/metrics_test.cc | 263 | ||||
-rw-r--r-- | libnativeloader/native_loader_test.cpp | 15 | ||||
-rw-r--r-- | odrefresh/Android.bp | 1 | ||||
-rw-r--r-- | odrefresh/odr_metrics.cc | 25 | ||||
-rw-r--r-- | odrefresh/odr_metrics.h | 12 | ||||
-rw-r--r-- | odrefresh/odr_metrics_record.cc | 129 | ||||
-rw-r--r-- | odrefresh/odr_metrics_record.h | 39 | ||||
-rw-r--r-- | odrefresh/odr_metrics_record_test.cc | 124 | ||||
-rw-r--r-- | odrefresh/odr_metrics_test.cc | 34 | ||||
-rw-r--r-- | odrefresh/odr_statslog_android.cc | 39 | ||||
-rw-r--r-- | runtime/Android.bp | 1 | ||||
-rw-r--r-- | runtime/metrics/reporter.cc | 12 | ||||
-rw-r--r-- | runtime/metrics/reporter.h | 3 | ||||
-rw-r--r-- | runtime/thread.cc | 6 |
18 files changed, 745 insertions, 201 deletions
diff --git a/libartbase/Android.bp b/libartbase/Android.bp index f9375239e4..80c63c6cfb 100644 --- a/libartbase/Android.bp +++ b/libartbase/Android.bp @@ -70,6 +70,7 @@ cc_defaults { // ZipArchive support, the order matters here to get all symbols. "libziparchive", ], + whole_static_libs: ["libtinyxml2"], shared_libs: [ "libz", "liblog", @@ -88,6 +89,7 @@ cc_defaults { static: { cflags: ["-DART_STATIC_LIBARTBASE"], }, + whole_static_libs: ["libtinyxml2"], shared_libs: [ "libziparchive", "libz", @@ -112,6 +114,7 @@ cc_defaults { // For common macros. "libbase", ], + whole_static_libs: ["libtinyxml2"], export_static_lib_headers: ["libbase"], // ART's macros.h depends on libbase's macros.h. cflags: ["-Wno-thread-safety"], @@ -391,6 +394,8 @@ cc_library_headers { shared_libs: ["libbase"], export_shared_lib_headers: ["libbase"], + whole_static_libs: ["libtinyxml2"], + apex_available: [ "com.android.art", "com.android.art.debug", diff --git a/libartbase/base/flags.h b/libartbase/base/flags.h index d1e1ca6243..fc568b2be2 100644 --- a/libartbase/base/flags.h +++ b/libartbase/base/flags.h @@ -304,6 +304,12 @@ struct Flags { // Note that the actual write is still controlled by // MetricsReportingMods and MetricsReportingNumMods. Flag<std::string> MetricsWriteToFile{"metrics.write-to-file", "", FlagType::kCmdlineOnly}; + + // The output format for metrics. This is only used + // when writing metrics to a file; metrics written + // to logcat will be in human-readable text format. + // Supported values are "text" and "xml". + Flag<std::string> MetricsFormat{"metrics.format", "text", FlagType::kCmdlineOnly}; }; // This is the actual instance of all the flags. diff --git a/libartbase/base/metrics/metrics.h b/libartbase/base/metrics/metrics.h index 266534cb0b..ebc44a9394 100644 --- a/libartbase/base/metrics/metrics.h +++ b/libartbase/base/metrics/metrics.h @@ -30,6 +30,7 @@ #include "android-base/logging.h" #include "base/bit_utils.h" #include "base/time_utils.h" +#include "tinyxml2.h" #pragma clang diagnostic push #pragma clang diagnostic error "-Wconversion" @@ -441,12 +442,80 @@ class MetricsAccumulator final : MetricsBase<T> { friend class ArtMetrics; }; -// A backend that writes metrics in a human-readable format to a string. +// Base class for formatting metrics into different formats +// (human-readable text, JSON, etc.) +class MetricsFormatter { + public: + virtual ~MetricsFormatter() = default; + + virtual void FormatBeginReport(uint64_t timestamp_since_start_ms, + const std::optional<SessionData>& session_data) = 0; + virtual void FormatEndReport() = 0; + virtual void FormatReportCounter(DatumId counter_type, uint64_t value) = 0; + virtual void FormatReportHistogram(DatumId histogram_type, + int64_t low_value, + int64_t high_value, + const std::vector<uint32_t>& buckets) = 0; + virtual std::string GetAndResetBuffer() = 0; + + protected: + const std::string version = "1.0"; +}; + +// Formatter outputting metrics in human-readable text format +class TextFormatter : public MetricsFormatter { + public: + TextFormatter() = default; + + void FormatBeginReport(uint64_t timestamp_millis, + const std::optional<SessionData>& session_data) override; + + void FormatReportCounter(DatumId counter_type, uint64_t value) override; + + void FormatReportHistogram(DatumId histogram_type, + int64_t low_value, + int64_t high_value, + const std::vector<uint32_t>& buckets) override; + + void FormatEndReport() override; + + std::string GetAndResetBuffer() override; + + private: + std::ostringstream os_; +}; + +// Formatter outputting metrics in XML format +class XmlFormatter : public MetricsFormatter { + public: + XmlFormatter() = default; + + void FormatBeginReport(uint64_t timestamp_millis, + const std::optional<SessionData>& session_data) override; + + void FormatReportCounter(DatumId counter_type, uint64_t value) override; + + void FormatReportHistogram(DatumId histogram_type, + int64_t low_value, + int64_t high_value, + const std::vector<uint32_t>& buckets) override; + + void FormatEndReport() override; + + std::string GetAndResetBuffer() override; + + private: + tinyxml2::XMLDocument document_; +}; + +// A backend that writes metrics to a string. +// The format of the metrics' output is delegated +// to the MetricsFormatter class. // // This is used as a base for LogBackend and FileBackend. class StringBackend : public MetricsBackend { public: - StringBackend(); + explicit StringBackend(std::unique_ptr<MetricsFormatter> formatter); void BeginOrUpdateSession(const SessionData& session_data) override; @@ -464,14 +533,15 @@ class StringBackend : public MetricsBackend { std::string GetAndResetBuffer(); private: - std::ostringstream os_; + std::unique_ptr<MetricsFormatter> formatter_; std::optional<SessionData> session_data_; }; // A backend that writes metrics in human-readable format to the log (i.e. logcat). class LogBackend : public StringBackend { public: - explicit LogBackend(android::base::LogSeverity level); + explicit LogBackend(std::unique_ptr<MetricsFormatter> formatter, + android::base::LogSeverity level); void BeginReport(uint64_t timestamp_millis) override; void EndReport() override; @@ -481,12 +551,10 @@ class LogBackend : public StringBackend { }; // A backend that writes metrics to a file. -// -// These are currently written in the same human-readable format used by StringBackend and -// LogBackend, but we will probably want a more machine-readable format in the future. class FileBackend : public StringBackend { public: - explicit FileBackend(const std::string& filename); + explicit FileBackend(std::unique_ptr<MetricsFormatter> formatter, + const std::string& filename); void BeginReport(uint64_t timestamp_millis) override; void EndReport() override; diff --git a/libartbase/base/metrics/metrics_common.cc b/libartbase/base/metrics/metrics_common.cc index f09987ba8e..025f5eb79e 100644 --- a/libartbase/base/metrics/metrics_common.cc +++ b/libartbase/base/metrics/metrics_common.cc @@ -76,7 +76,7 @@ void ArtMetrics::ReportAllMetrics(MetricsBackend* backend) const { } void ArtMetrics::DumpForSigQuit(std::ostream& os) const { - StringBackend backend; + StringBackend backend(std::make_unique<TextFormatter>()); ReportAllMetrics(&backend); os << backend.GetAndResetBuffer(); } @@ -88,13 +88,12 @@ void ArtMetrics::Reset() { #undef ART_METRIC } -StringBackend::StringBackend() {} +StringBackend::StringBackend(std::unique_ptr<MetricsFormatter> formatter) + : formatter_(std::move(formatter)) +{} std::string StringBackend::GetAndResetBuffer() { - std::string result = os_.str(); - os_.clear(); - os_.str(""); - return result; + return formatter_->GetAndResetBuffer(); } void StringBackend::BeginOrUpdateSession(const SessionData& session_data) { @@ -102,32 +101,51 @@ void StringBackend::BeginOrUpdateSession(const SessionData& session_data) { } void StringBackend::BeginReport(uint64_t timestamp_since_start_ms) { + formatter_->FormatBeginReport(timestamp_since_start_ms, session_data_); +} + +void StringBackend::EndReport() { + formatter_->FormatEndReport(); +} + +void StringBackend::ReportCounter(DatumId counter_type, uint64_t value) { + formatter_->FormatReportCounter(counter_type, value); +} + +void StringBackend::ReportHistogram(DatumId histogram_type, + int64_t minimum_value_, + int64_t maximum_value_, + const std::vector<uint32_t>& buckets) { + formatter_->FormatReportHistogram(histogram_type, minimum_value_, maximum_value_, buckets); +} + +void TextFormatter::FormatBeginReport(uint64_t timestamp_since_start_ms, + const std::optional<SessionData>& session_data) { os_ << "\n*** ART internal metrics ***\n"; os_ << " Metadata:\n"; os_ << " timestamp_since_start_ms: " << timestamp_since_start_ms << "\n"; - if (session_data_.has_value()) { - os_ << " session_id: " << session_data_->session_id << "\n"; - os_ << " uid: " << session_data_->uid << "\n"; - os_ << " compilation_reason: " << CompilationReasonName(session_data_->compilation_reason) + if (session_data.has_value()) { + os_ << " session_id: " << session_data->session_id << "\n"; + os_ << " uid: " << session_data->uid << "\n"; + os_ << " compilation_reason: " << CompilationReasonName(session_data->compilation_reason) << "\n"; - os_ << " compiler_filter: " << CompilerFilterReportingName(session_data_->compiler_filter) + os_ << " compiler_filter: " << CompilerFilterReportingName(session_data->compiler_filter) << "\n"; } os_ << " Metrics:\n"; } -void StringBackend::EndReport() { os_ << "*** Done dumping ART internal metrics ***\n"; } - -void StringBackend::ReportCounter(DatumId counter_type, uint64_t value) { +void TextFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) { os_ << " " << DatumName(counter_type) << ": count = " << value << "\n"; } -void StringBackend::ReportHistogram(DatumId histogram_type, - int64_t minimum_value_, - int64_t maximum_value_, - const std::vector<uint32_t>& buckets) { - os_ << " " << DatumName(histogram_type) << ": range = " << minimum_value_ << "..." << maximum_value_; - if (buckets.size() > 0) { +void TextFormatter::FormatReportHistogram(DatumId histogram_type, + int64_t minimum_value_, + int64_t maximum_value_, + const std::vector<uint32_t>& buckets) { + os_ << " " << DatumName(histogram_type) << ": range = " + << minimum_value_ << "..." << maximum_value_; + if (!buckets.empty()) { os_ << ", buckets: "; bool first = true; for (const auto& count : buckets) { @@ -143,22 +161,100 @@ void StringBackend::ReportHistogram(DatumId histogram_type, } } -LogBackend::LogBackend(android::base::LogSeverity level) : level_{level} {} +void TextFormatter::FormatEndReport() { + os_ << "*** Done dumping ART internal metrics ***\n"; +} + +std::string TextFormatter::GetAndResetBuffer() { + std::string result = os_.str(); + os_.clear(); + os_.str(""); + return result; +} + +void XmlFormatter::FormatBeginReport(uint64_t timestamp_millis, + const std::optional<SessionData>& session_data) { + tinyxml2::XMLElement* art_runtime_metrics = document_.NewElement("art_runtime_metrics"); + document_.InsertEndChild(art_runtime_metrics); + + art_runtime_metrics->InsertNewChildElement("version")->SetText(version.data()); + + tinyxml2::XMLElement* metadata = art_runtime_metrics->InsertNewChildElement("metadata"); + metadata->InsertNewChildElement("timestamp_since_start_ms")->SetText(timestamp_millis); + + if (session_data.has_value()) { + metadata->InsertNewChildElement("session_id")->SetText(session_data->session_id); + metadata->InsertNewChildElement("uid")->SetText(session_data->uid); + metadata + ->InsertNewChildElement("compilation_reason") + ->SetText(CompilationReasonName(session_data->compilation_reason)); + metadata + ->InsertNewChildElement("compiler_filter") + ->SetText(CompilerFilterReportingName(session_data->compiler_filter)); + } + + art_runtime_metrics->InsertNewChildElement("metrics"); +} + +void XmlFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) { + tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics"); + + tinyxml2::XMLElement* counter = metrics->InsertNewChildElement(DatumName(counter_type).data()); + counter->InsertNewChildElement("counter_type")->SetText("count"); + counter->InsertNewChildElement("value")->SetText(value); +} + +void XmlFormatter::FormatReportHistogram(DatumId histogram_type, + int64_t low_value, + int64_t high_value, + const std::vector<uint32_t>& buckets) { + tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics"); + + tinyxml2::XMLElement* histogram = + metrics->InsertNewChildElement(DatumName(histogram_type).data()); + histogram->InsertNewChildElement("counter_type")->SetText("histogram"); + histogram->InsertNewChildElement("minimum_value")->SetText(low_value); + histogram->InsertNewChildElement("maximum_value")->SetText(high_value); + + tinyxml2::XMLElement* buckets_element = histogram->InsertNewChildElement("buckets"); + for (const auto& count : buckets) { + buckets_element->InsertNewChildElement("bucket")->SetText(count); + } +} + +void XmlFormatter::FormatEndReport() {} + +std::string XmlFormatter::GetAndResetBuffer() { + tinyxml2::XMLPrinter printer(/*file=*/nullptr, /*compact=*/true); + document_.Print(&printer); + std::string result = printer.CStr(); + document_.Clear(); + + return result; +} + +LogBackend::LogBackend(std::unique_ptr<MetricsFormatter> formatter, + android::base::LogSeverity level) + : StringBackend{std::move(formatter)}, level_{level} +{} void LogBackend::BeginReport(uint64_t timestamp_since_start_ms) { - GetAndResetBuffer(); + StringBackend::GetAndResetBuffer(); StringBackend::BeginReport(timestamp_since_start_ms); } void LogBackend::EndReport() { StringBackend::EndReport(); - LOG_STREAM(level_) << GetAndResetBuffer(); + LOG_STREAM(level_) << StringBackend::GetAndResetBuffer(); } -FileBackend::FileBackend(const std::string& filename) : filename_{filename} {} +FileBackend::FileBackend(std::unique_ptr<MetricsFormatter> formatter, + const std::string& filename) + : StringBackend{std::move(formatter)}, filename_{filename} +{} void FileBackend::BeginReport(uint64_t timestamp_since_start_ms) { - GetAndResetBuffer(); + StringBackend::GetAndResetBuffer(); StringBackend::BeginReport(timestamp_since_start_ms); } @@ -170,7 +266,7 @@ void FileBackend::EndReport() { if (file.get() == nullptr) { LOG(WARNING) << "Could open metrics file '" << filename_ << "': " << error_message; } else { - if (!android::base::WriteStringToFd(GetAndResetBuffer(), file.get()->Fd())) { + if (!android::base::WriteStringToFd(StringBackend::GetAndResetBuffer(), file.get()->Fd())) { PLOG(WARNING) << "Error writing metrics to file"; } } diff --git a/libartbase/base/metrics/metrics_test.cc b/libartbase/base/metrics/metrics_test.cc index ed6f88d709..282029012b 100644 --- a/libartbase/base/metrics/metrics_test.cc +++ b/libartbase/base/metrics/metrics_test.cc @@ -16,6 +16,7 @@ #include "metrics.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "metrics_test.h" @@ -248,7 +249,7 @@ TEST_F(MetricsTest, HistogramTimer) { // Makes sure all defined metrics are included when dumping through StreamBackend. TEST_F(MetricsTest, StreamBackendDumpAllMetrics) { ArtMetrics metrics; - StringBackend backend; + StringBackend backend(std::make_unique<TextFormatter>()); metrics.ReportAllMetrics(&backend); @@ -305,6 +306,266 @@ TEST_F(MetricsTest, ResetMetrics) { metrics.ReportAllMetrics(&zero_backend); } +TEST(TextFormatterTest, ReportMetrics_WithBuckets) { + TextFormatter text_formatter; + SessionData session_data { + .session_id = 1000, + .uid = 50, + .compilation_reason = CompilationReason::kInstall, + .compiler_filter = CompilerFilterReporting::kSpeed, + }; + + text_formatter.FormatBeginReport(200, session_data); + text_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u); + text_formatter.FormatReportHistogram(DatumId::kFullGcCollectionTime, + 50, + 200, + {2, 4, 7, 1}); + text_formatter.FormatEndReport(); + + const std::string result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 200\n" + " session_id: 1000\n" + " uid: 50\n" + " compilation_reason: install\n" + " compiler_filter: speed\n" + " Metrics:\n" + " FullGcCount: count = 1\n" + " FullGcCollectionTime: range = 50...200, buckets: 2,4,7,1\n" + "*** Done dumping ART internal metrics ***\n"); +} + +TEST(TextFormatterTest, ReportMetrics_NoBuckets) { + TextFormatter text_formatter; + SessionData session_data { + .session_id = 500, + .uid = 15, + .compilation_reason = CompilationReason::kCmdLine, + .compiler_filter = CompilerFilterReporting::kExtract, + }; + + text_formatter.FormatBeginReport(400, session_data); + text_formatter.FormatReportHistogram(DatumId::kFullGcCollectionTime, 10, 20, {}); + text_formatter.FormatEndReport(); + + std::string result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 400\n" + " session_id: 500\n" + " uid: 15\n" + " compilation_reason: cmdline\n" + " compiler_filter: extract\n" + " Metrics:\n" + " FullGcCollectionTime: range = 10...20, no buckets\n" + "*** Done dumping ART internal metrics ***\n"); +} + +TEST(TextFormatterTest, BeginReport_NoSessionData) { + TextFormatter text_formatter; + std::optional<SessionData> empty_session_data; + + text_formatter.FormatBeginReport(100, empty_session_data); + text_formatter.FormatEndReport(); + + std::string result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 100\n" + " Metrics:\n" + "*** Done dumping ART internal metrics ***\n"); +} + +TEST(TextFormatterTest, GetAndResetBuffer_ActuallyResetsBuffer) { + TextFormatter text_formatter; + std::optional<SessionData> empty_session_data; + + text_formatter.FormatBeginReport(200, empty_session_data); + text_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u); + text_formatter.FormatEndReport(); + + std::string result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 200\n" + " Metrics:\n" + " FullGcCount: count = 1\n" + "*** Done dumping ART internal metrics ***\n"); + + text_formatter.FormatBeginReport(300, empty_session_data); + text_formatter.FormatReportCounter(DatumId::kFullGcCount, 5u); + text_formatter.FormatEndReport(); + + result = text_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "\n*** ART internal metrics ***\n" + " Metadata:\n" + " timestamp_since_start_ms: 300\n" + " Metrics:\n" + " FullGcCount: count = 5\n" + "*** Done dumping ART internal metrics ***\n"); +} + +TEST(XmlFormatterTest, ReportMetrics_WithBuckets) { + XmlFormatter xml_formatter; + SessionData session_data { + .session_id = 123, + .uid = 456, + .compilation_reason = CompilationReason::kFirstBoot, + .compiler_filter = CompilerFilterReporting::kSpace, + }; + + xml_formatter.FormatBeginReport(250, session_data); + xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 3u); + xml_formatter.FormatReportHistogram(DatumId::kYoungGcCollectionTime, + 300, + 600, + {1, 5, 3}); + xml_formatter.FormatEndReport(); + + const std::string result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>250</timestamp_since_start_ms>" + "<session_id>123</session_id>" + "<uid>456</uid>" + "<compilation_reason>first-boot</compilation_reason>" + "<compiler_filter>space</compiler_filter>" + "</metadata>" + "<metrics>" + "<YoungGcCount>" + "<counter_type>count</counter_type>" + "<value>3</value>" + "</YoungGcCount>" + "<YoungGcCollectionTime>" + "<counter_type>histogram</counter_type>" + "<minimum_value>300</minimum_value>" + "<maximum_value>600</maximum_value>" + "<buckets>" + "<bucket>1</bucket>" + "<bucket>5</bucket>" + "<bucket>3</bucket>" + "</buckets>" + "</YoungGcCollectionTime>" + "</metrics>" + "</art_runtime_metrics>"); +} + +TEST(XmlFormatterTest, ReportMetrics_NoBuckets) { + XmlFormatter xml_formatter; + SessionData session_data { + .session_id = 234, + .uid = 345, + .compilation_reason = CompilationReason::kFirstBoot, + .compiler_filter = CompilerFilterReporting::kSpace, + }; + + xml_formatter.FormatBeginReport(160, session_data); + xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 4u); + xml_formatter.FormatReportHistogram(DatumId::kYoungGcCollectionTime, 20, 40, {}); + xml_formatter.FormatEndReport(); + + const std::string result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>160</timestamp_since_start_ms>" + "<session_id>234</session_id>" + "<uid>345</uid>" + "<compilation_reason>first-boot</compilation_reason>" + "<compiler_filter>space</compiler_filter>" + "</metadata>" + "<metrics>" + "<YoungGcCount>" + "<counter_type>count</counter_type>" + "<value>4</value>" + "</YoungGcCount>" + "<YoungGcCollectionTime>" + "<counter_type>histogram</counter_type>" + "<minimum_value>20</minimum_value>" + "<maximum_value>40</maximum_value>" + "<buckets/>" + "</YoungGcCollectionTime>" + "</metrics>" + "</art_runtime_metrics>"); +} + +TEST(XmlFormatterTest, BeginReport_NoSessionData) { + XmlFormatter xml_formatter; + std::optional<SessionData> empty_session_data; + + xml_formatter.FormatBeginReport(100, empty_session_data); + xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 3u); + xml_formatter.FormatEndReport(); + + std::string result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>100</timestamp_since_start_ms>" + "</metadata>" + "<metrics>" + "<YoungGcCount>" + "<counter_type>count</counter_type>" + "<value>3</value>" + "</YoungGcCount>" + "</metrics>" + "</art_runtime_metrics>"); +} + +TEST(XmlFormatterTest, GetAndResetBuffer_ActuallyResetsBuffer) { + XmlFormatter xml_formatter; + std::optional<SessionData> empty_session_data; + + xml_formatter.FormatBeginReport(200, empty_session_data); + xml_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u); + xml_formatter.FormatEndReport(); + + std::string result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>200</timestamp_since_start_ms>" + "</metadata>" + "<metrics>" + "<FullGcCount>" + "<counter_type>count</counter_type>" + "<value>1</value>" + "</FullGcCount>" + "</metrics>" + "</art_runtime_metrics>"); + + xml_formatter.FormatBeginReport(300, empty_session_data); + xml_formatter.FormatReportCounter(DatumId::kFullGcCount, 5u); + xml_formatter.FormatEndReport(); + + result = xml_formatter.GetAndResetBuffer(); + ASSERT_EQ(result, + "<art_runtime_metrics>" + "<version>1.0</version>" + "<metadata>" + "<timestamp_since_start_ms>300</timestamp_since_start_ms>" + "</metadata>" + "<metrics>" + "<FullGcCount>" + "<counter_type>count</counter_type>" + "<value>5</value>" + "</FullGcCount>" + "</metrics>" + "</art_runtime_metrics>"); +} + TEST(CompilerFilterReportingTest, FromName) { ASSERT_EQ(CompilerFilterReportingFromName("error"), CompilerFilterReporting::kError); diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp index 9648713e70..080b4d6e46 100644 --- a/libnativeloader/native_loader_test.cpp +++ b/libnativeloader/native_loader_test.cpp @@ -167,13 +167,16 @@ INSTANTIATE_TEST_SUITE_P(NativeLoaderTests, NativeLoaderTest, testing::Bool()); ///////////////////////////////////////////////////////////////// -std::string default_public_and_extended_libraries() { - std::string public_libs = default_public_libraries(); - std::string ext_libs = extended_public_libraries(); +std::string append_extended_libraries(const std::string& libs) { + const std::string& ext_libs = extended_public_libraries(); if (!ext_libs.empty()) { - public_libs = public_libs + ":" + ext_libs; + return libs + ":" + ext_libs; } - return public_libs; + return libs; +} + +std::string default_public_and_extended_libraries() { + return append_extended_libraries(default_public_libraries()); } class NativeLoaderTest_Create : public NativeLoaderTest { @@ -380,7 +383,7 @@ TEST_P(NativeLoaderTest_Create, UnbundledProductApp) { expected_permitted_path = expected_permitted_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR; expected_shared_libs_to_platform_ns = - default_public_libraries() + ":" + llndk_libraries_product(); + append_extended_libraries(default_public_libraries() + ":" + llndk_libraries_product()); expected_link_with_vndk_product_ns = true; } SetExpectations(); diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp index 8fee83a38a..55a1fdea2c 100644 --- a/odrefresh/Android.bp +++ b/odrefresh/Android.bp @@ -47,7 +47,6 @@ cc_defaults { ], static_libs: [ "libc++fs", - "libtinyxml2", ], tidy: true, tidy_disabled_srcs: [":art-apex-cache-info"], diff --git a/odrefresh/odr_metrics.cc b/odrefresh/odr_metrics.cc index 4bddb17bd8..a2ce51f65b 100644 --- a/odrefresh/odr_metrics.cc +++ b/odrefresh/odr_metrics.cc @@ -64,16 +64,16 @@ OdrMetrics::~OdrMetrics() { } } -void OdrMetrics::SetCompilationTime(int32_t seconds) { +void OdrMetrics::SetCompilationTime(int32_t millis) { switch (stage_) { case Stage::kPrimaryBootClasspath: - primary_bcp_compilation_seconds_ = seconds; + primary_bcp_compilation_millis_ = millis; break; case Stage::kSecondaryBootClasspath: - secondary_bcp_compilation_seconds_ = seconds; + secondary_bcp_compilation_millis_ = millis; break; case Stage::kSystemServerClasspath: - system_server_compilation_seconds_ = seconds; + system_server_compilation_millis_ = millis; break; case Stage::kCheck: case Stage::kComplete: @@ -119,28 +119,31 @@ bool OdrMetrics::ToRecord(/*out*/OdrMetricsRecord* record) const { if (!trigger_.has_value()) { return false; } + record->odrefresh_metrics_version = kOdrefreshMetricsVersion; record->art_apex_version = art_apex_version_; record->trigger = static_cast<uint32_t>(trigger_.value()); record->stage_reached = static_cast<uint32_t>(stage_); record->status = static_cast<uint32_t>(status_); - record->primary_bcp_compilation_seconds = primary_bcp_compilation_seconds_; - record->secondary_bcp_compilation_seconds = secondary_bcp_compilation_seconds_; - record->system_server_compilation_seconds = system_server_compilation_seconds_; record->cache_space_free_start_mib = cache_space_free_start_mib_; record->cache_space_free_end_mib = cache_space_free_end_mib_; + record->primary_bcp_compilation_millis = primary_bcp_compilation_millis_; + record->secondary_bcp_compilation_millis = secondary_bcp_compilation_millis_; + record->system_server_compilation_millis = system_server_compilation_millis_; return true; } void OdrMetrics::WriteToFile(const std::string& path, const OdrMetrics* metrics) { - OdrMetricsRecord record; + OdrMetricsRecord record{}; if (!metrics->ToRecord(&record)) { LOG(ERROR) << "Attempting to report metrics without a compilation trigger."; return; } - // Preserve order from frameworks/proto_logging/stats/atoms.proto in metrics file written. - std::ofstream ofs(path); - ofs << record; + const android::base::Result<void>& result = record.WriteToFile(path); + if (!result.ok()) { + LOG(ERROR) << "Failed to report metrics to file: " << path + << ", error: " << result.error().message(); + } } } // namespace odrefresh diff --git a/odrefresh/odr_metrics.h b/odrefresh/odr_metrics.h index cd80beffc5..7ebc148684 100644 --- a/odrefresh/odr_metrics.h +++ b/odrefresh/odr_metrics.h @@ -126,7 +126,7 @@ class OdrMetrics final { static int32_t GetFreeSpaceMiB(const std::string& path); static void WriteToFile(const std::string& path, const OdrMetrics* metrics); - void SetCompilationTime(int32_t seconds); + void SetCompilationTime(int32_t millis); const std::string cache_directory_; const std::string metrics_file_; @@ -137,11 +137,11 @@ class OdrMetrics final { Stage stage_ = Stage::kUnknown; Status status_ = Status::kUnknown; - int32_t primary_bcp_compilation_seconds_ = 0; - int32_t secondary_bcp_compilation_seconds_ = 0; - int32_t system_server_compilation_seconds_ = 0; int32_t cache_space_free_start_mib_ = 0; int32_t cache_space_free_end_mib_ = 0; + int32_t primary_bcp_compilation_millis_ = 0; + int32_t secondary_bcp_compilation_millis_ = 0; + int32_t system_server_compilation_millis_ = 0; friend class ScopedOdrCompilationTimer; }; @@ -155,8 +155,8 @@ class ScopedOdrCompilationTimer final { ~ScopedOdrCompilationTimer() { auto elapsed_time = std::chrono::steady_clock::now() - start_; - auto elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed_time); - metrics_.SetCompilationTime(static_cast<int32_t>(elapsed_seconds.count())); + auto elapsed_millis = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time); + metrics_.SetCompilationTime(static_cast<int32_t>(elapsed_millis.count())); } private: diff --git a/odrefresh/odr_metrics_record.cc b/odrefresh/odr_metrics_record.cc index fc135d3399..b8c2f80fea 100644 --- a/odrefresh/odr_metrics_record.cc +++ b/odrefresh/odr_metrics_record.cc @@ -14,59 +14,110 @@ * limitations under the License. */ +#include "android-base/logging.h" #include "odr_metrics_record.h" +#include "tinyxml2.h" #include <iosfwd> -#include <istream> -#include <ostream> -#include <streambuf> #include <string> namespace art { namespace odrefresh { -std::istream& operator>>(std::istream& is, OdrMetricsRecord& record) { - // Block I/O related exceptions - auto saved_exceptions = is.exceptions(); - is.exceptions(std::ios_base::iostate {}); +namespace { +android::base::Result<int64_t> ReadInt64(tinyxml2::XMLElement* parent, const char* name) { + tinyxml2::XMLElement* element = parent->FirstChildElement(name); + if (element == nullptr) { + return Errorf("Expected Odrefresh metric {} not found", name); + } - // The order here matches the field order of MetricsRecord. - is >> record.art_apex_version >> std::ws; - is >> record.trigger >> std::ws; - is >> record.stage_reached >> std::ws; - is >> record.status >> std::ws; - is >> record.primary_bcp_compilation_seconds >> std::ws; - is >> record.secondary_bcp_compilation_seconds >> std::ws; - is >> record.system_server_compilation_seconds >> std::ws; - is >> record.cache_space_free_start_mib >> std::ws; - is >> record.cache_space_free_end_mib >> std::ws; - - // Restore I/O related exceptions - is.exceptions(saved_exceptions); - return is; + int64_t metric; + tinyxml2::XMLError result = element->QueryInt64Text(&metric); + if (result == tinyxml2::XML_SUCCESS) { + return metric; + } else { + return Errorf("Odrefresh metric {} is not an int64", name); + } +} + +android::base::Result<int32_t> ReadInt32(tinyxml2::XMLElement* parent, const char* name) { + tinyxml2::XMLElement* element = parent->FirstChildElement(name); + if (element == nullptr) { + return Errorf("Expected Odrefresh metric {} not found", name); + } + + int32_t metric; + tinyxml2::XMLError result = element->QueryIntText(&metric); + if (result == tinyxml2::XML_SUCCESS) { + return metric; + } else { + return Errorf("Odrefresh metric {} is not an int32", name); + } +} + +template <typename T> +void AddMetric(tinyxml2::XMLElement* parent, const char* name, const T& value) { + parent->InsertNewChildElement(name)->SetText(value); } +} // namespace -std::ostream& operator<<(std::ostream& os, const OdrMetricsRecord& record) { - static const char kSpace = ' '; +android::base::Result<void> OdrMetricsRecord::ReadFromFile(const std::string& filename) { + tinyxml2::XMLDocument xml_document; + tinyxml2::XMLError result = xml_document.LoadFile(filename.data()); + if (result != tinyxml2::XML_SUCCESS) { + return android::base::Error() << xml_document.ErrorStr(); + } - // Block I/O related exceptions - auto saved_exceptions = os.exceptions(); - os.exceptions(std::ios_base::iostate {}); + tinyxml2::XMLElement* metrics = xml_document.FirstChildElement("odrefresh_metrics"); + if (metrics == nullptr) { + return Errorf("odrefresh_metrics element not found in {}", filename); + } + + odrefresh_metrics_version = OR_RETURN(ReadInt32(metrics, "odrefresh_metrics_version")); + if (odrefresh_metrics_version != kOdrefreshMetricsVersion) { + return Errorf("odrefresh_metrics_version {} is different than expected ({})", + odrefresh_metrics_version, + kOdrefreshMetricsVersion); + } + + art_apex_version = OR_RETURN(ReadInt64(metrics, "art_apex_version")); + trigger = OR_RETURN(ReadInt32(metrics, "trigger")); + stage_reached = OR_RETURN(ReadInt32(metrics, "stage_reached")); + status = OR_RETURN(ReadInt32(metrics, "status")); + cache_space_free_start_mib = OR_RETURN(ReadInt32(metrics, "cache_space_free_start_mib")); + cache_space_free_end_mib = OR_RETURN(ReadInt32(metrics, "cache_space_free_end_mib")); + primary_bcp_compilation_millis = OR_RETURN( + ReadInt32(metrics, "primary_bcp_compilation_millis")); + secondary_bcp_compilation_millis = OR_RETURN( + ReadInt32(metrics, "secondary_bcp_compilation_millis")); + system_server_compilation_millis = OR_RETURN( + ReadInt32(metrics, "system_server_compilation_millis")); + return {}; +} + +android::base::Result<void> OdrMetricsRecord::WriteToFile(const std::string& filename) const { + tinyxml2::XMLDocument xml_document; + tinyxml2::XMLElement* metrics = xml_document.NewElement("odrefresh_metrics"); + xml_document.InsertEndChild(metrics); // The order here matches the field order of MetricsRecord. - os << record.art_apex_version << kSpace; - os << record.trigger << kSpace; - os << record.stage_reached << kSpace; - os << record.status << kSpace; - os << record.primary_bcp_compilation_seconds << kSpace; - os << record.secondary_bcp_compilation_seconds << kSpace; - os << record.system_server_compilation_seconds << kSpace; - os << record.cache_space_free_start_mib << kSpace; - os << record.cache_space_free_end_mib << std::endl; - - // Restore I/O related exceptions - os.exceptions(saved_exceptions); - return os; + AddMetric(metrics, "odrefresh_metrics_version", odrefresh_metrics_version); + AddMetric(metrics, "art_apex_version", art_apex_version); + AddMetric(metrics, "trigger", trigger); + AddMetric(metrics, "stage_reached", stage_reached); + AddMetric(metrics, "status", status); + AddMetric(metrics, "cache_space_free_start_mib", cache_space_free_start_mib); + AddMetric(metrics, "cache_space_free_end_mib", cache_space_free_end_mib); + AddMetric(metrics, "primary_bcp_compilation_millis", primary_bcp_compilation_millis); + AddMetric(metrics, "secondary_bcp_compilation_millis", secondary_bcp_compilation_millis); + AddMetric(metrics, "system_server_compilation_millis", system_server_compilation_millis); + + tinyxml2::XMLError result = xml_document.SaveFile(filename.data(), /*compact=*/true); + if (result == tinyxml2::XML_SUCCESS) { + return {}; + } else { + return android::base::Error() << xml_document.ErrorStr(); + } } } // namespace odrefresh diff --git a/odrefresh/odr_metrics_record.h b/odrefresh/odr_metrics_record.h index 9dd51a63cc..fa953374e4 100644 --- a/odrefresh/odr_metrics_record.h +++ b/odrefresh/odr_metrics_record.h @@ -20,42 +20,41 @@ #include <cstdint> #include <iosfwd> // For forward-declaration of std::string. +#include "android-base/result.h" +#include "tinyxml2.h" + namespace art { namespace odrefresh { // Default location for storing metrics from odrefresh. -constexpr const char* kOdrefreshMetricsFile = "/data/misc/odrefresh/odrefresh-metrics.txt"; +constexpr const char* kOdrefreshMetricsFile = "/data/misc/odrefresh/odrefresh-metrics.xml"; + +// Initial OdrefreshMetrics version +static constexpr int32_t kOdrefreshMetricsVersion = 2; // MetricsRecord is a simpler container for Odrefresh metric values reported to statsd. The order // and types of fields here mirror definition of `OdrefreshReported` in // frameworks/proto_logging/stats/atoms.proto. struct OdrMetricsRecord { + int32_t odrefresh_metrics_version; int64_t art_apex_version; int32_t trigger; int32_t stage_reached; int32_t status; - int32_t primary_bcp_compilation_seconds; - int32_t secondary_bcp_compilation_seconds; - int32_t system_server_compilation_seconds; int32_t cache_space_free_start_mib; int32_t cache_space_free_end_mib; -}; + int32_t primary_bcp_compilation_millis; + int32_t secondary_bcp_compilation_millis; + int32_t system_server_compilation_millis; -// Read a `MetricsRecord` from an `istream`. -// -// This method blocks istream related exceptions, the caller should check `is.fail()` is false after -// calling. -// -// Returns `is`. -std::istream& operator>>(std::istream& is, OdrMetricsRecord& record); - -// Write a `MetricsRecord` to an `ostream`. -// -// This method blocks ostream related exceptions, the caller should check `os.fail()` is false after -// calling. -// -// Returns `os` -std::ostream& operator<<(std::ostream& os, const OdrMetricsRecord& record); + // Reads a `MetricsRecord` from an XML file. + // Returns an error if the XML document was not found or parsed correctly. + android::base::Result<void> ReadFromFile(const std::string& filename); + + // Writes a `MetricsRecord` to an XML file. + // Returns an error if the XML document was not saved correctly. + android::base::Result<void> WriteToFile(const std::string& filename) const; +}; } // namespace odrefresh } // namespace art diff --git a/odrefresh/odr_metrics_record_test.cc b/odrefresh/odr_metrics_record_test.cc index dd739d68f2..fbb5b99063 100644 --- a/odrefresh/odr_metrics_record_test.cc +++ b/odrefresh/odr_metrics_record_test.cc @@ -20,6 +20,8 @@ #include <fstream> +#include "android-base/result-gmock.h" +#include "android-base/stringprintf.h" #include "base/common_art_test.h" namespace art { @@ -27,86 +29,124 @@ namespace odrefresh { class OdrMetricsRecordTest : public CommonArtTest {}; +using android::base::testing::Ok; +using android::base::testing::HasError; +using android::base::testing::WithMessage; + TEST_F(OdrMetricsRecordTest, HappyPath) { - const OdrMetricsRecord expected { + const OdrMetricsRecord expected{ + .odrefresh_metrics_version = art::odrefresh::kOdrefreshMetricsVersion, .art_apex_version = 0x01233456'789abcde, .trigger = 0x01020304, .stage_reached = 0x11121314, .status = 0x21222324, - .primary_bcp_compilation_seconds = 0x31323334, - .secondary_bcp_compilation_seconds = 0x41424344, - .system_server_compilation_seconds = 0x51525354, .cache_space_free_start_mib = 0x61626364, - .cache_space_free_end_mib = 0x71727374 + .cache_space_free_end_mib = 0x71727374, + .primary_bcp_compilation_millis = 0x31323334, + .secondary_bcp_compilation_millis = 0x41424344, + .system_server_compilation_millis = 0x51525354 }; ScratchDir dir(/*keep_files=*/false); - std::string file_path = dir.GetPath() + "/metrics-record.txt"; - - { - std::ofstream ofs(file_path); - ofs << expected; - ASSERT_FALSE(ofs.fail()); - ofs.close(); - } + std::string file_path = dir.GetPath() + "/metrics-record.xml"; + ASSERT_THAT(expected.WriteToFile(file_path), Ok()); OdrMetricsRecord actual {}; - { - std::ifstream ifs(file_path); - ifs >> actual; - ASSERT_TRUE(ifs.eof()); - } + ASSERT_THAT(actual.ReadFromFile(file_path), Ok()); + ASSERT_EQ(expected.odrefresh_metrics_version, actual.odrefresh_metrics_version); ASSERT_EQ(expected.art_apex_version, actual.art_apex_version); ASSERT_EQ(expected.trigger, actual.trigger); ASSERT_EQ(expected.stage_reached, actual.stage_reached); ASSERT_EQ(expected.status, actual.status); - ASSERT_EQ(expected.primary_bcp_compilation_seconds, actual.primary_bcp_compilation_seconds); - ASSERT_EQ(expected.secondary_bcp_compilation_seconds, actual.secondary_bcp_compilation_seconds); - ASSERT_EQ(expected.system_server_compilation_seconds, actual.system_server_compilation_seconds); ASSERT_EQ(expected.cache_space_free_start_mib, actual.cache_space_free_start_mib); ASSERT_EQ(expected.cache_space_free_end_mib, actual.cache_space_free_end_mib); + ASSERT_EQ(expected.primary_bcp_compilation_millis, actual.primary_bcp_compilation_millis); + ASSERT_EQ(expected.secondary_bcp_compilation_millis, actual.secondary_bcp_compilation_millis); + ASSERT_EQ(expected.system_server_compilation_millis, actual.system_server_compilation_millis); ASSERT_EQ(0, memcmp(&expected, &actual, sizeof(expected))); } TEST_F(OdrMetricsRecordTest, EmptyInput) { ScratchDir dir(/*keep_files=*/false); - std::string file_path = dir.GetPath() + "/metrics-record.txt"; - - std::ifstream ifs(file_path); - OdrMetricsRecord record; - ifs >> record; + std::string file_path = dir.GetPath() + "/metrics-record.xml"; - ASSERT_TRUE(ifs.fail()); - ASSERT_TRUE(!ifs); + OdrMetricsRecord record{}; + ASSERT_THAT(record.ReadFromFile(file_path), testing::Not(Ok())); } -TEST_F(OdrMetricsRecordTest, ClosedInput) { +TEST_F(OdrMetricsRecordTest, UnexpectedInput) { ScratchDir dir(/*keep_files=*/false); - std::string file_path = dir.GetPath() + "/metrics-record.txt"; + std::string file_path = dir.GetPath() + "/metrics-record.xml"; - std::ifstream ifs(file_path); - ifs.close(); + std::ofstream ofs(file_path); + ofs << "<not_odrefresh_metrics></not_odrefresh_metrics>"; + ofs.close(); - OdrMetricsRecord record; - ifs >> record; + OdrMetricsRecord record{}; + ASSERT_THAT( + record.ReadFromFile(file_path), + HasError(WithMessage("odrefresh_metrics element not found in " + file_path))); +} + +TEST_F(OdrMetricsRecordTest, ExpectedElementNotFound) { + ScratchDir dir(/*keep_files=*/false); + std::string file_path = dir.GetPath() + "/metrics-record.xml"; - ASSERT_TRUE(ifs.fail()); - ASSERT_TRUE(!ifs); + std::ofstream ofs(file_path); + ofs << "<odrefresh_metrics>"; + ofs << "<not_valid_metric>25</not_valid_metric>"; + ofs << "</odrefresh_metrics>"; + ofs.close(); + + OdrMetricsRecord record{}; + ASSERT_THAT( + record.ReadFromFile(file_path), + HasError(WithMessage("Expected Odrefresh metric odrefresh_metrics_version not found"))); } -TEST_F(OdrMetricsRecordTest, ClosedOutput) { +TEST_F(OdrMetricsRecordTest, UnexpectedOdrefreshMetricsVersion) { ScratchDir dir(/*keep_files=*/false); - std::string file_path = dir.GetPath() + "/metrics-record.txt"; + std::string file_path = dir.GetPath() + "/metrics-record.xml"; std::ofstream ofs(file_path); + ofs << "<odrefresh_metrics>"; + ofs << "<odrefresh_metrics_version>0</odrefresh_metrics_version>"; + ofs << "</odrefresh_metrics>"; ofs.close(); - OdrMetricsRecord record {}; - ofs << record; + OdrMetricsRecord record{}; + std::string expected_error = android::base::StringPrintf( + "odrefresh_metrics_version 0 is different than expected (%d)", + kOdrefreshMetricsVersion); + ASSERT_THAT(record.ReadFromFile(file_path), + HasError(WithMessage(expected_error))); +} + +TEST_F(OdrMetricsRecordTest, UnexpectedType) { + ScratchDir dir(/*keep_files=*/false); + std::string file_path = dir.GetPath() + "/metrics-record.xml"; + + std::ofstream ofs(file_path); + ofs << "<odrefresh_metrics>"; + ofs << "<odrefresh_metrics_version>" << kOdrefreshMetricsVersion + << "</odrefresh_metrics_version>"; + ofs << "<art_apex_version>81966764218039518</art_apex_version>"; + ofs << "<trigger>16909060</trigger>"; + ofs << "<stage_reached>286397204</stage_reached>"; + ofs << "<status>abcd</status>"; // It should be an int32. + ofs << "<cache_space_free_start_mib>1633837924</cache_space_free_start_mib>"; + ofs << "<cache_space_free_end_mib>1903326068</cache_space_free_end_mib>"; + ofs << "<primary_bcp_compilation_millis>825373492</primary_bcp_compilation_millis>"; + ofs << "<secondary_bcp_compilation_millis>1094861636</secondary_bcp_compilation_millis>"; + ofs << "<system_server_compilation_millis>1364349780</system_server_compilation_millis>"; + ofs << "</odrefresh_metrics>"; + ofs.close(); - ASSERT_TRUE(ofs.fail()); - ASSERT_TRUE(!ofs.good()); + OdrMetricsRecord record{}; + ASSERT_THAT( + record.ReadFromFile(file_path), + HasError(WithMessage("Odrefresh metric status is not an int32"))); } } // namespace odrefresh diff --git a/odrefresh/odr_metrics_test.cc b/odrefresh/odr_metrics_test.cc index 4519f00363..f222caaf2e 100644 --- a/odrefresh/odr_metrics_test.cc +++ b/odrefresh/odr_metrics_test.cc @@ -24,6 +24,7 @@ #include <memory> #include <string> +#include "android-base/result-gmock.h" #include "base/common_art_test.h" namespace art { @@ -35,7 +36,7 @@ class OdrMetricsTest : public CommonArtTest { CommonArtTest::SetUp(); scratch_dir_ = std::make_unique<ScratchDir>(); - metrics_file_path_ = scratch_dir_->GetPath() + "/metrics.txt"; + metrics_file_path_ = scratch_dir_->GetPath() + "/metrics.xml"; cache_directory_ = scratch_dir_->GetPath() + "/dir"; mkdir(cache_directory_.c_str(), S_IRWXU); } @@ -158,10 +159,10 @@ TEST_F(OdrMetricsTest, TimeValuesAreRecorded) { EXPECT_TRUE(metrics.ToRecord(&record)); EXPECT_EQ(OdrMetrics::Stage::kPrimaryBootClasspath, enum_cast<OdrMetrics::Stage>(record.stage_reached)); - EXPECT_NE(0, record.primary_bcp_compilation_seconds); - EXPECT_GT(10, record.primary_bcp_compilation_seconds); - EXPECT_EQ(0, record.secondary_bcp_compilation_seconds); - EXPECT_EQ(0, record.system_server_compilation_seconds); + EXPECT_NE(0, record.primary_bcp_compilation_millis); + EXPECT_GT(10'000, record.primary_bcp_compilation_millis); + EXPECT_EQ(0, record.secondary_bcp_compilation_millis); + EXPECT_EQ(0, record.system_server_compilation_millis); // Secondary boot classpath compilation time. { @@ -172,10 +173,10 @@ TEST_F(OdrMetricsTest, TimeValuesAreRecorded) { EXPECT_TRUE(metrics.ToRecord(&record)); EXPECT_EQ(OdrMetrics::Stage::kSecondaryBootClasspath, enum_cast<OdrMetrics::Stage>(record.stage_reached)); - EXPECT_NE(0, record.primary_bcp_compilation_seconds); - EXPECT_NE(0, record.secondary_bcp_compilation_seconds); - EXPECT_GT(10, record.secondary_bcp_compilation_seconds); - EXPECT_EQ(0, record.system_server_compilation_seconds); + EXPECT_NE(0, record.primary_bcp_compilation_millis); + EXPECT_NE(0, record.secondary_bcp_compilation_millis); + EXPECT_GT(10'000, record.secondary_bcp_compilation_millis); + EXPECT_EQ(0, record.system_server_compilation_millis); // system_server classpath compilation time. { @@ -186,10 +187,10 @@ TEST_F(OdrMetricsTest, TimeValuesAreRecorded) { EXPECT_TRUE(metrics.ToRecord(&record)); EXPECT_EQ(OdrMetrics::Stage::kSystemServerClasspath, enum_cast<OdrMetrics::Stage>(record.stage_reached)); - EXPECT_NE(0, record.primary_bcp_compilation_seconds); - EXPECT_NE(0, record.secondary_bcp_compilation_seconds); - EXPECT_NE(0, record.system_server_compilation_seconds); - EXPECT_GT(10, record.system_server_compilation_seconds); + EXPECT_NE(0, record.primary_bcp_compilation_millis); + EXPECT_NE(0, record.secondary_bcp_compilation_millis); + EXPECT_NE(0, record.system_server_compilation_millis); + EXPECT_GT(10'000, record.system_server_compilation_millis); } TEST_F(OdrMetricsTest, CacheSpaceValuesAreUpdated) { @@ -207,11 +208,8 @@ TEST_F(OdrMetricsTest, CacheSpaceValuesAreUpdated) { EXPECT_EQ(0, snap.cache_space_free_end_mib); } - OdrMetricsRecord on_disk; - std::ifstream ifs(GetMetricsFilePath()); - EXPECT_TRUE(ifs); - ifs >> on_disk; - EXPECT_TRUE(ifs); + OdrMetricsRecord on_disk{}; + EXPECT_THAT(on_disk.ReadFromFile(GetMetricsFilePath()), android::base::testing::Ok()); EXPECT_EQ(snap.cache_space_free_start_mib, on_disk.cache_space_free_start_mib); EXPECT_NE(0, on_disk.cache_space_free_end_mib); } diff --git a/odrefresh/odr_statslog_android.cc b/odrefresh/odr_statslog_android.cc index 7db348e633..ee173d4b3d 100644 --- a/odrefresh/odr_statslog_android.cc +++ b/odrefresh/odr_statslog_android.cc @@ -103,16 +103,11 @@ int32_t TranslateTrigger(int32_t art_metrics_trigger) { bool ReadValues(const char* metrics_file, /*out*/ OdrMetricsRecord* record, /*out*/ std::string* error_msg) { - std::ifstream ifs(metrics_file); - if (!ifs) { - *error_msg = android::base::StringPrintf( - "metrics file '%s' could not be opened: %s", metrics_file, strerror(errno)); - return false; - } - - ifs >> *record; - if (!ifs) { - *error_msg = "file parsing error."; + const android::base::Result<void>& result = record->ReadFromFile(metrics_file); + if (!result.ok()) { + *error_msg = android::base::StringPrintf("Unable to open or parse metrics file %s (error: %s)", + metrics_file, + result.error().message().data()); return false; } @@ -151,16 +146,20 @@ bool UploadStatsIfAvailable(/*out*/std::string* error_msg) { // Write values to statsd. The order of values passed is the same as the order of the // fields in OdrMetricsRecord. - int bytes_written = art::metrics::statsd::stats_write(metrics::statsd::ODREFRESH_REPORTED, - record.art_apex_version, - record.trigger, - record.stage_reached, - record.status, - record.primary_bcp_compilation_seconds, - record.secondary_bcp_compilation_seconds, - record.system_server_compilation_seconds, - record.cache_space_free_start_mib, - record.cache_space_free_end_mib); + int bytes_written = art::metrics::statsd::stats_write( + metrics::statsd::ODREFRESH_REPORTED, + record.art_apex_version, + record.trigger, + record.stage_reached, + record.status, + record.primary_bcp_compilation_millis / 1000, + record.secondary_bcp_compilation_millis / 1000, + record.system_server_compilation_millis / 1000, + record.cache_space_free_start_mib, + record.cache_space_free_end_mib, + record.primary_bcp_compilation_millis, + record.secondary_bcp_compilation_millis, + record.system_server_compilation_millis); if (bytes_written <= 0) { *error_msg = android::base::StringPrintf("stats_write returned %d", bytes_written); return false; diff --git a/runtime/Android.bp b/runtime/Android.bp index 9e0e78ebdb..98ddbb7210 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -415,7 +415,6 @@ libart_cc_defaults { ], static_libs: [ "libstatslog_art", - "libtinyxml2", ], generated_sources: [ "apex-info-list-tinyxml", diff --git a/runtime/metrics/reporter.cc b/runtime/metrics/reporter.cc index a44066e487..28ca997cec 100644 --- a/runtime/metrics/reporter.cc +++ b/runtime/metrics/reporter.cc @@ -126,10 +126,17 @@ void MetricsReporter::BackgroundThreadRun() { // Configure the backends if (config_.dump_to_logcat) { - backends_.emplace_back(new LogBackend(LogSeverity::INFO)); + backends_.emplace_back(new LogBackend(std::make_unique<TextFormatter>(), LogSeverity::INFO)); } if (config_.dump_to_file.has_value()) { - backends_.emplace_back(new FileBackend(config_.dump_to_file.value())); + std::unique_ptr<MetricsFormatter> formatter; + if (config_.metrics_format == "xml") { + formatter = std::make_unique<XmlFormatter>(); + } else { + formatter = std::make_unique<TextFormatter>(); + } + + backends_.emplace_back(new FileBackend(std::move(formatter), config_.dump_to_file.value())); } if (config_.dump_to_statsd) { auto backend = CreateStatsdBackend(); @@ -291,6 +298,7 @@ ReportingConfig ReportingConfig::FromFlags(bool is_system_server) { .dump_to_logcat = gFlags.MetricsWriteToLogcat(), .dump_to_file = gFlags.MetricsWriteToFile.GetValueOptional(), .dump_to_statsd = gFlags.MetricsWriteToStatsd(), + .metrics_format = gFlags.MetricsFormat(), .period_spec = period_spec, .reporting_num_mods = reporting_num_mods, .reporting_mods = reporting_mods, diff --git a/runtime/metrics/reporter.h b/runtime/metrics/reporter.h index daeaf1fa18..af9e0ca151 100644 --- a/runtime/metrics/reporter.h +++ b/runtime/metrics/reporter.h @@ -78,6 +78,9 @@ struct ReportingConfig { // If set, provides a file name to enable metrics logging to a file. std::optional<std::string> dump_to_file; + // Provides the desired output format for metrics written to a file. + std::string metrics_format; + // The reporting period configuration. std::optional<ReportingPeriodSpec> period_spec; diff --git a/runtime/thread.cc b/runtime/thread.cc index 78ba26dec0..26b795b967 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -4282,6 +4282,9 @@ void Thread::VisitRoots(RootVisitor* visitor) { static void SweepCacheEntry(IsMarkedVisitor* visitor, const Instruction* inst, size_t* value) REQUIRES_SHARED(Locks::mutator_lock_) { + // WARNING: The interpreter will not modify the cache while this method is running in GC. + // However, ClearAllInterpreterCaches can still run if any dex file is closed. + // Therefore the cache entry can be nulled at any point through this method. if (inst == nullptr) { return; } @@ -4307,6 +4310,9 @@ static void SweepCacheEntry(IsMarkedVisitor* visitor, const Instruction* inst, s case Opcode::CONST_STRING: case Opcode::CONST_STRING_JUMBO: { mirror::Object* object = reinterpret_cast<mirror::Object*>(*value); + if (object == nullptr) { + return; + } mirror::Object* new_object = visitor->IsMarked(object); // We know the string is marked because it's a strongly-interned string that // is always alive (see b/117621117 for trying to make those strings weak). |