Snap for 9049251 from a5497acb1220935d647b9c1b4f0fb763c8a4fbd3 to mainline-media-swcodec-release

Change-Id: I94aae0e527595a21b26ae78842c602a00a13aa31
diff --git a/libartbase/Android.bp b/libartbase/Android.bp
index f937523..80c63c6 100644
--- a/libartbase/Android.bp
+++ b/libartbase/Android.bp
@@ -70,6 +70,7 @@
                 // ZipArchive support, the order matters here to get all symbols.
                 "libziparchive",
             ],
+            whole_static_libs: ["libtinyxml2"],
             shared_libs: [
                 "libz",
                 "liblog",
@@ -88,6 +89,7 @@
             static: {
                 cflags: ["-DART_STATIC_LIBARTBASE"],
             },
+            whole_static_libs: ["libtinyxml2"],
             shared_libs: [
                 "libziparchive",
                 "libz",
@@ -112,6 +114,7 @@
                 // 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 @@
     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 d1e1ca6..fc568b2 100644
--- a/libartbase/base/flags.h
+++ b/libartbase/base/flags.h
@@ -304,6 +304,12 @@
   // 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 266534c..ebc44a9 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 @@
   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 @@
   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 @@
 };
 
 // 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 f09987b..025f5eb 100644
--- a/libartbase/base/metrics/metrics_common.cc
+++ b/libartbase/base/metrics/metrics_common.cc
@@ -76,7 +76,7 @@
 }
 
 void ArtMetrics::DumpForSigQuit(std::ostream& os) const {
-  StringBackend backend;
+  StringBackend backend(std::make_unique<TextFormatter>());
   ReportAllMetrics(&backend);
   os << backend.GetAndResetBuffer();
 }
@@ -88,13 +88,12 @@
 #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::BeginReport(uint64_t timestamp_since_start_ms) {
-  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)
-        << "\n";
-    os_ << "    compiler_filter: " << CompilerFilterReportingName(session_data_->compiler_filter)
-        << "\n";
-  }
-  os_ << "  Metrics:\n";
+  formatter_->FormatBeginReport(timestamp_since_start_ms, session_data_);
 }
 
-void StringBackend::EndReport() { os_ << "*** Done dumping ART internal metrics ***\n"; }
+void StringBackend::EndReport() {
+  formatter_->FormatEndReport();
+}
 
 void StringBackend::ReportCounter(DatumId counter_type, uint64_t value) {
-  os_ << "    " << DatumName(counter_type) << ": count = " << value << "\n";
+  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) {
-  os_ << "    " << DatumName(histogram_type) << ": range = " << minimum_value_ << "..." << maximum_value_;
-  if (buckets.size() > 0) {
+  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)
+        << "\n";
+    os_ << "    compiler_filter: " << CompilerFilterReportingName(session_data->compiler_filter)
+        << "\n";
+  }
+  os_ << "  Metrics:\n";
+}
+
+void TextFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) {
+  os_ << "    " << DatumName(counter_type) << ": count = " << value << "\n";
+}
+
+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 @@
   }
 }
 
-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 @@
   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 ed6f88d..2820290 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 @@
 // 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 @@
   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/odrefresh/Android.bp b/odrefresh/Android.bp
index 8fee83a..55a1fde 100644
--- a/odrefresh/Android.bp
+++ b/odrefresh/Android.bp
@@ -47,7 +47,6 @@
     ],
     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 4bddb17..33368b2 100644
--- a/odrefresh/odr_metrics.cc
+++ b/odrefresh/odr_metrics.cc
@@ -119,6 +119,7 @@
   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_);
@@ -132,15 +133,17 @@
 }
 
 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_record.cc b/odrefresh/odr_metrics_record.cc
index fc135d3..a5b4707 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);
+  }
 }
 
-std::ostream& operator<<(std::ostream& os, const OdrMetricsRecord& record) {
-  static const char kSpace = ' ';
+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);
+  }
 
-  // Block I/O related exceptions
-  auto saved_exceptions = os.exceptions();
-  os.exceptions(std::ios_base::iostate {});
+  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
+
+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();
+  }
+
+  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"));
+  primary_bcp_compilation_seconds = OR_RETURN(
+      ReadInt32(metrics, "primary_bcp_compilation_seconds"));
+  secondary_bcp_compilation_seconds = OR_RETURN(
+      ReadInt32(metrics, "secondary_bcp_compilation_seconds"));
+  system_server_compilation_seconds = OR_RETURN(
+      ReadInt32(metrics, "system_server_compilation_seconds"));
+  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"));
+  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;
+  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, "primary_bcp_compilation_seconds", primary_bcp_compilation_seconds);
+  AddMetric(metrics, "secondary_bcp_compilation_seconds", secondary_bcp_compilation_seconds);
+  AddMetric(metrics, "system_server_compilation_seconds", system_server_compilation_seconds);
+  AddMetric(metrics, "cache_space_free_start_mib", cache_space_free_start_mib);
+  AddMetric(metrics, "cache_space_free_end_mib", cache_space_free_end_mib);
 
-  // Restore I/O related exceptions
-  os.exceptions(saved_exceptions);
-  return os;
+  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 9dd51a6..1bd7faa 100644
--- a/odrefresh/odr_metrics_record.h
+++ b/odrefresh/odr_metrics_record.h
@@ -20,16 +20,23 @@
 #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 = 1;
 
 // 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;
@@ -39,24 +46,16 @@
   int32_t system_server_compilation_seconds;
   int32_t cache_space_free_start_mib;
   int32_t cache_space_free_end_mib;
+
+  // 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;
 };
 
-// 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);
-
 }  // namespace odrefresh
 }  // namespace art
 
diff --git a/odrefresh/odr_metrics_record_test.cc b/odrefresh/odr_metrics_record_test.cc
index dd739d6..d2cc59f 100644
--- a/odrefresh/odr_metrics_record_test.cc
+++ b/odrefresh/odr_metrics_record_test.cc
@@ -20,6 +20,7 @@
 
 #include <fstream>
 
+#include "android-base/result-gmock.h"
 #include "base/common_art_test.h"
 
 namespace art {
@@ -27,36 +28,30 @@
 
 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 {
-    .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
-  };
+  const OdrMetricsRecord expected{.odrefresh_metrics_version = 0x1,
+                                  .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};
 
   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);
@@ -71,42 +66,81 @@
 
 TEST_F(OdrMetricsRecordTest, EmptyInput) {
   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);
-  OdrMetricsRecord record;
-  ifs >> record;
-
-  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::ifstream ifs(file_path);
-  ifs.close();
-
-  OdrMetricsRecord record;
-  ifs >> record;
-
-  ASSERT_TRUE(ifs.fail());
-  ASSERT_TRUE(!ifs);
-}
-
-TEST_F(OdrMetricsRecordTest, ClosedOutput) {
-  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 << "<not_odrefresh_metrics></not_odrefresh_metrics>";
   ofs.close();
 
-  OdrMetricsRecord record {};
-  ofs << record;
+  OdrMetricsRecord record{};
+  ASSERT_THAT(
+      record.ReadFromFile(file_path),
+      HasError(WithMessage("odrefresh_metrics element not found in " + file_path)));
+}
 
-  ASSERT_TRUE(ofs.fail());
-  ASSERT_TRUE(!ofs.good());
+TEST_F(OdrMetricsRecordTest, ExpectedElementNotFound) {
+  ScratchDir dir(/*keep_files=*/false);
+  std::string file_path = dir.GetPath() + "/metrics-record.xml";
+
+  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, UnexpectedOdrefreshMetricsVersion) {
+  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>0</odrefresh_metrics_version>";
+  ofs << "</odrefresh_metrics>";
+  ofs.close();
+
+  OdrMetricsRecord record{};
+  ASSERT_THAT(record.ReadFromFile(file_path),
+              HasError(WithMessage("odrefresh_metrics_version 0 is different than expected (1)")));
+}
+
+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 << "<primary_bcp_compilation_seconds>825373492</primary_bcp_compilation_seconds>";
+  ofs << "<secondary_bcp_compilation_seconds>1094861636</secondary_bcp_compilation_seconds>";
+  ofs << "<system_server_compilation_seconds>1364349780</system_server_compilation_seconds>";
+  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 << "</odrefresh_metrics>";
+  ofs.close();
+
+  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 4519f00..4f73aef 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 @@
     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);
   }
@@ -207,11 +208,8 @@
     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 7db348e..831d1d1 100644
--- a/odrefresh/odr_statslog_android.cc
+++ b/odrefresh/odr_statslog_android.cc
@@ -103,16 +103,11 @@
 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;
   }
 
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 9e0e78e..98ddbb7 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -415,7 +415,6 @@
             ],
             static_libs: [
                 "libstatslog_art",
-                "libtinyxml2",
             ],
             generated_sources: [
                 "apex-info-list-tinyxml",
diff --git a/runtime/metrics/reporter.cc b/runtime/metrics/reporter.cc
index a44066e..28ca997 100644
--- a/runtime/metrics/reporter.cc
+++ b/runtime/metrics/reporter.cc
@@ -126,10 +126,17 @@
 
   // 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 @@
       .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 daeaf1f..af9e0ca 100644
--- a/runtime/metrics/reporter.h
+++ b/runtime/metrics/reporter.h
@@ -78,6 +78,9 @@
   // 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 78ba26d..26b795b 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -4282,6 +4282,9 @@
 
 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 @@
     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).