odrefresh: Add support for uploading stats

Adds support for loading odrefresh metrics saved to file and uploading
them when system_server starts.

Bug: 169925964
Test: manual module update with instrumentation showing statsd got data

(cherry picked from commit 55ca8ab5216e4c20638f34b44c6bc53866b991ce)
Merged-In: Iccc6ede1583235d09dbfd42996eb4ba118b700f8
Change-Id: Iad588f05622864b37d40333d081bd89fbf76051c
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
index 8a9acd3..e504064 100644
--- a/odrefresh/Android.bp
+++ b/odrefresh/Android.bp
@@ -134,6 +134,34 @@
     ],
 }
 
+cc_library_static {
+    name: "libodrstatslog",
+    defaults: ["art_defaults"],
+    host_supported: true,
+    export_include_dirs: ["include"],
+
+    local_include_dirs: ["include"],
+    shared_libs: ["libartbase"],
+    target: {
+        android: {
+            generated_headers: ["statslog_odrefresh.h"],
+            generated_sources: ["statslog_odrefresh.cpp"],
+            srcs: [
+                "odr_metrics_record.cc",
+                "odr_statslog_android.cc",
+            ],
+            shared_libs: ["libstatssocket"],
+        },
+        host: {
+            srcs: ["odr_statslog_host.cc"],
+        },
+    },
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
 art_cc_test {
     name: "art_odrefresh_tests",
     defaults: [
@@ -154,6 +182,24 @@
     shared_libs: ["libbase"],
 }
 
+genrule {
+    name: "statslog_odrefresh.h",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_odrefresh.h --module art --namespace art,metrics,statsd",
+    out: [
+        "statslog_odrefresh.h",
+    ],
+}
+
+genrule {
+    name: "statslog_odrefresh.cpp",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_odrefresh.cpp --module art --namespace art,metrics,statsd --importHeader statslog_odrefresh.h",
+    out: [
+        "statslog_odrefresh.cpp",
+    ],
+}
+
 xsd_config {
     name: "art-apex-cache-info",
     srcs: ["CacheInfo.xsd"],
diff --git a/odrefresh/include/odr_statslog/odr_statslog.h b/odrefresh/include/odr_statslog/odr_statslog.h
new file mode 100644
index 0000000..0a27475
--- /dev/null
+++ b/odrefresh/include/odr_statslog/odr_statslog.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_ODREFRESH_INCLUDE_ODR_STATSLOG_ODR_STATSLOG_H_
+#define ART_ODREFRESH_INCLUDE_ODR_STATSLOG_ODR_STATSLOG_H_
+
+#include <iosfwd>  // For forward-declaration of std::string.
+namespace art {
+namespace odrefresh {
+
+// Upload the metrics (if any) generated by odrefresh by passing the data `statsd` process.
+//
+// Metrics from odrefresh a persisted to the path specified by `OdrefreshMetricsFile`. This method
+// reads the saved metrics, passes them to statsd, then removes the file to avoid uploading them
+// in future.
+//
+// Returns true on success. On failure `error_msg` summarizes the failure and this method returns
+// false.
+bool UploadStatsIfAvailable(/*out*/std::string* error_msg);
+
+}  // namespace odrefresh
+}  // namespace art
+
+#endif  // ART_ODREFRESH_INCLUDE_ODR_STATSLOG_ODR_STATSLOG_H_
diff --git a/odrefresh/odr_statslog_android.cc b/odrefresh/odr_statslog_android.cc
new file mode 100644
index 0000000..7db348e
--- /dev/null
+++ b/odrefresh/odr_statslog_android.cc
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "odr_statslog/odr_statslog.h"
+
+#include <cstdint>
+#include <fstream>
+#include <istream>
+#include <string>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "odr_metrics.h"
+#include "odr_metrics_record.h"
+#include "statslog_odrefresh.h"
+
+namespace art {
+namespace odrefresh {
+
+using android::base::StringPrintf;
+
+namespace {
+
+// Convert bare value from art::metrics::Stage to value defined in atoms.proto.
+int32_t TranslateStage(int32_t art_metrics_stage) {
+  switch (static_cast<OdrMetrics::Stage>(art_metrics_stage)) {
+    case OdrMetrics::Stage::kUnknown:
+      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_UNKNOWN;
+    case OdrMetrics::Stage::kCheck:
+      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_CHECK;
+    case OdrMetrics::Stage::kPreparation:
+      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_PREPARATION;
+    case OdrMetrics::Stage::kPrimaryBootClasspath:
+      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_PRIMARY_BOOT_CLASSPATH;
+    case OdrMetrics::Stage::kSecondaryBootClasspath:
+      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_SECONDARY_BOOT_CLASSPATH;
+    case OdrMetrics::Stage::kSystemServerClasspath:
+      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_SYSTEM_SERVER_CLASSPATH;
+    case OdrMetrics::Stage::kComplete:
+      return metrics::statsd::ODREFRESH_REPORTED__STAGE_REACHED__STAGE_COMPLETE;
+  }
+
+  LOG(ERROR) << "Unknown stage value: " << art_metrics_stage;
+  return -1;
+}
+
+// Convert bare value from art::metrics::Status to value defined in atoms.proto.
+int32_t TranslateStatus(int32_t art_metrics_status) {
+  switch (static_cast<OdrMetrics::Status>(art_metrics_status)) {
+    case OdrMetrics::Status::kUnknown:
+      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_UNKNOWN;
+    case OdrMetrics::Status::kOK:
+      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_OK;
+    case OdrMetrics::Status::kNoSpace:
+      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_NO_SPACE;
+    case OdrMetrics::Status::kIoError:
+      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_IO_ERROR;
+    case OdrMetrics::Status::kDex2OatError:
+      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_DEX2OAT_ERROR;
+    case OdrMetrics::Status::kTimeLimitExceeded:
+      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_TIME_LIMIT_EXCEEDED;
+    case OdrMetrics::Status::kStagingFailed:
+      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_STAGING_FAILED;
+    case OdrMetrics::Status::kInstallFailed:
+      return metrics::statsd::ODREFRESH_REPORTED__STATUS__STATUS_INSTALL_FAILED;
+  }
+
+  LOG(ERROR) << "Unknown status value: " << art_metrics_status;
+  return -1;
+}
+
+// Convert bare value from art::metrics::Trigger to value defined in atoms.proto.
+int32_t TranslateTrigger(int32_t art_metrics_trigger) {
+  switch (static_cast<OdrMetrics::Trigger>(art_metrics_trigger)) {
+    case OdrMetrics::Trigger::kUnknown:
+      return metrics::statsd::ODREFRESH_REPORTED__TRIGGER__TRIGGER_UNKNOWN;
+    case OdrMetrics::Trigger::kApexVersionMismatch:
+      return metrics::statsd::ODREFRESH_REPORTED__TRIGGER__TRIGGER_APEX_VERSION_MISMATCH;
+    case OdrMetrics::Trigger::kDexFilesChanged:
+      return metrics::statsd::ODREFRESH_REPORTED__TRIGGER__TRIGGER_DEX_FILES_CHANGED;
+    case OdrMetrics::Trigger::kMissingArtifacts:
+      return metrics::statsd::ODREFRESH_REPORTED__TRIGGER__TRIGGER_MISSING_ARTIFACTS;
+  }
+
+  LOG(ERROR) << "Unknown trigger value: " << art_metrics_trigger;
+  return -1;
+}
+
+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.";
+    return false;
+  }
+
+  //
+  // Convert values defined as enums to their statsd values.
+  //
+
+  record->trigger = TranslateTrigger(record->trigger);
+  if (record->trigger < 0) {
+    *error_msg = "failed to parse trigger.";
+    return false;
+  }
+
+  record->stage_reached = TranslateStage(record->stage_reached);
+  if (record->stage_reached < 0) {
+    *error_msg = "failed to parse stage_reached.";
+    return false;
+  }
+
+  record->status = TranslateStatus(record->status);
+  if (record->status < 0) {
+    *error_msg = "failed to parse status.";
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+bool UploadStatsIfAvailable(/*out*/std::string* error_msg) {
+  OdrMetricsRecord record;
+  if (!ReadValues(kOdrefreshMetricsFile, &record, error_msg)) {
+    return false;
+  }
+
+  // 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);
+  if (bytes_written <= 0) {
+    *error_msg = android::base::StringPrintf("stats_write returned %d", bytes_written);
+    return false;
+  }
+
+  if (unlink(kOdrefreshMetricsFile) != 0) {
+    *error_msg = StringPrintf("failed to unlink '%s': %s", kOdrefreshMetricsFile, strerror(errno));
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace odrefresh
+}  // namespace art
diff --git a/odrefresh/odr_statslog_host.cc b/odrefresh/odr_statslog_host.cc
new file mode 100644
index 0000000..118563a
--- /dev/null
+++ b/odrefresh/odr_statslog_host.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "odr_statslog/odr_statslog.h"  // for declararation of UploadStatsIfAvailable().
+
+#include <iosfwd>                       // for forward declaration of std::string.
+namespace art {
+namespace odrefresh {
+
+bool UploadStatsIfAvailable(/*out*/std::string* /*error_msg*/) {
+  // No stats reported from host, report success.
+  return true;
+}
+
+}  // namespace odrefresh
+}  // namespace art
diff --git a/runtime/Android.bp b/runtime/Android.bp
index dd4be7c..f66faf4 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -440,6 +440,7 @@
         "libsigchain",
         "libunwindstack",
     ],
+    static_libs: ["libodrstatslog"],
     export_include_dirs: ["."],
     // ART's macros.h depends on libbase's macros.h.
     // Note: runtime_options.h depends on cmdline. But we don't really want to export this
@@ -458,6 +459,7 @@
         "liblzma", // libelffile dependency; must be repeated here since it's a static lib.
         "libnativebridge",
         "libnativeloader",
+        "libodrstatslog",
         "libsigchain_fake",
         "libunwindstack",
         "libz",
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index c950aaa..a4e5550 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -150,6 +150,7 @@
 #include "oat_file_manager.h"
 #include "oat_quick_method_header.h"
 #include "object_callbacks.h"
+#include "odr_statslog/odr_statslog.h"
 #include "parsed_options.h"
 #include "quick/quick_method_frame_info.h"
 #include "reflection.h"
@@ -1111,6 +1112,16 @@
       LOG(WARNING) << "Failed to load perfetto_javaheapprof: " << err;
     }
   }
+  if (Runtime::Current()->IsSystemServer()) {
+    std::string err;
+    ScopedTrace tr("odrefresh stats logging");
+    ScopedThreadSuspension sts(Thread::Current(), ThreadState::kNative);
+    // Report stats if available. This should be moved into ART Services when they are ready.
+    if (!odrefresh::UploadStatsIfAvailable(&err)) {
+      LOG(WARNING) << "Failed to upload odrefresh metrics: " << err;
+    }
+  }
+
   if (LIKELY(automatically_set_jni_ids_indirection_) && CanSetJniIdType()) {
     if (IsJavaDebuggable()) {
       SetJniIdType(JniIdType::kIndices);