summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-09-12 16:55:30 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-09-12 16:55:30 +0000
commit351d9e2999d1872a086cfea023262271b8477b1e (patch)
treef4fc2b4b650b89dcc015fe6ca8a00bbebfd6b7ff
parent9517f745d98887af209d461b1f3ee55ff4101431 (diff)
parent20b0129442ad9f4d713d0e036def0155decac003 (diff)
downloadart-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.bp5
-rw-r--r--libartbase/base/flags.h6
-rw-r--r--libartbase/base/metrics/metrics.h84
-rw-r--r--libartbase/base/metrics/metrics_common.cc148
-rw-r--r--libartbase/base/metrics/metrics_test.cc263
-rw-r--r--libnativeloader/native_loader_test.cpp15
-rw-r--r--odrefresh/Android.bp1
-rw-r--r--odrefresh/odr_metrics.cc25
-rw-r--r--odrefresh/odr_metrics.h12
-rw-r--r--odrefresh/odr_metrics_record.cc129
-rw-r--r--odrefresh/odr_metrics_record.h39
-rw-r--r--odrefresh/odr_metrics_record_test.cc124
-rw-r--r--odrefresh/odr_metrics_test.cc34
-rw-r--r--odrefresh/odr_statslog_android.cc39
-rw-r--r--runtime/Android.bp1
-rw-r--r--runtime/metrics/reporter.cc12
-rw-r--r--runtime/metrics/reporter.h3
-rw-r--r--runtime/thread.cc6
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).