Allow multiple output formats for runtime metrics

* Add base abstract class MetricsFormatter
* Extract current formatting into TextFormatter
* Add XmlFormatter for XML output format
* Add command line flag for xml format

Test: test.py (gTests)
Test: atest art_standalone_libartbase_tests
Test: art/build/apex/runtests.sh
Bug: 175048705

Size of APEX files:
* before the changes:
  * com.android.art.apex: 61,001,728 bytes (59 MB)
  * com.android.art.capex: 22,757,376 bytes (22 MB)

* after the changes:
  * com.android.art.apex: 60,928,000 bytes (-73,728 bytes)
  * com.android.art.capex: 22,728,704 bytes (-28,672 bytes)

Change-Id: Ie80abaf67ffc721d7da59b8115f655630b21cae4
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 701054e..4a1664a 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"
@@ -433,12 +434,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;
 
@@ -456,14 +525,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;
@@ -473,12 +543,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/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;