summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Orion Hodson <oth@google.com> 2021-06-09 10:50:57 +0100
committer Orion Hodson <oth@google.com> 2021-06-10 08:00:22 +0000
commitf761f5887918375b842e4cc62ed9cbe7521c8444 (patch)
tree5cc23ca0310d55ac2f9a1dcd8959351f1376745b
parent890f2eb950930b9054bca6a77fd19f0e6c1a9c9b (diff)
Revert^2 "odrefresh: compilation backoff"
Adds backoff logic to limit attempts odrefresh tries to compile. It will always recompile if the APEX is updated or the input JARs change, but if compilation fails for any reason then odrefresh backs off exponentially in days. Relands commit 6859ffca5ffd15128459293046590488008221ff. The odsign_e2e tests required updating to remove the compilation log whose purpose is to backoff compilation attempts in the wild. Bug: 187494247 Test: atest art_odrefresh_tests Test: atest odsign_e2e_tests Change-Id: Id41ee875cf1ca376f8e2ae05a43d0f6f74a9995f
-rw-r--r--odrefresh/Android.bp3
-rw-r--r--odrefresh/TODO.md5
-rw-r--r--odrefresh/odr_compilation_log.cc206
-rw-r--r--odrefresh/odr_compilation_log.h93
-rw-r--r--odrefresh/odr_compilation_log_test.cc399
-rw-r--r--odrefresh/odr_metrics.h11
-rw-r--r--odrefresh/odrefresh.cc13
-rw-r--r--test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java36
8 files changed, 752 insertions, 14 deletions
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
index b5a5eb3997..e42539dde4 100644
--- a/odrefresh/Android.bp
+++ b/odrefresh/Android.bp
@@ -29,6 +29,7 @@ cc_defaults {
defaults: ["art_defaults"],
srcs: [
"odrefresh.cc",
+ "odr_compilation_log.cc",
"odr_fs_utils.cc",
"odr_metrics.cc",
"odr_metrics_record.cc",
@@ -161,6 +162,8 @@ art_cc_test {
header_libs: ["odrefresh_headers"],
srcs: [
"odr_artifacts_test.cc",
+ "odr_compilation_log.cc",
+ "odr_compilation_log_test.cc",
"odr_fs_utils.cc",
"odr_fs_utils_test.cc",
"odr_metrics.cc",
diff --git a/odrefresh/TODO.md b/odrefresh/TODO.md
index 567639806f..9d7c9fc3aa 100644
--- a/odrefresh/TODO.md
+++ b/odrefresh/TODO.md
@@ -2,7 +2,7 @@
## TODO (STOPSHIP until done)
-1. Implement back off on trying compilation when previous attempt(s) failed.
+1. denylist for AOT artifacts.
## DONE
@@ -21,5 +21,6 @@
- Unexpected error (a setup or clean-up action failed).
6. Metrics recording for subprocess timeouts.
7. Free space calculation and only attempting compilation if sufficient space.
+8. Implement back off on trying compilation when previous attempt(s) failed.
-</strike> \ No newline at end of file
+</strike>
diff --git a/odrefresh/odr_compilation_log.cc b/odrefresh/odr_compilation_log.cc
new file mode 100644
index 0000000000..55432f4357
--- /dev/null
+++ b/odrefresh/odr_compilation_log.cc
@@ -0,0 +1,206 @@
+/*
+ * 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_compilation_log.h>
+
+#include <errno.h>
+
+#include <fstream>
+#include <ios>
+#include <iosfwd>
+#include <istream>
+#include <ostream>
+#include <streambuf>
+#include <string>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "base/os.h"
+
+#include "odrefresh/odrefresh.h"
+#include "odr_metrics.h"
+
+namespace art {
+namespace odrefresh {
+
+std::istream& operator>>(std::istream& is, OdrCompilationLogEntry& entry) {
+ // Block I/O related exceptions
+ auto saved_exceptions = is.exceptions();
+ is.exceptions(std::ios_base::iostate {});
+
+ is >> entry.apex_version >> std::ws;
+ is >> entry.trigger >> std::ws;
+ is >> entry.when >> std::ws;
+ is >> entry.exit_code >> std::ws;
+
+ // Restore I/O related exceptions
+ is.exceptions(saved_exceptions);
+ return is;
+}
+
+std::ostream& operator<<(std::ostream& os, const OdrCompilationLogEntry& entry) {
+ static const char kSpace = ' ';
+
+ // Block I/O related exceptions
+ auto saved_exceptions = os.exceptions();
+ os.exceptions(std::ios_base::iostate {});
+
+ os << entry.apex_version << kSpace;
+ os << entry.trigger << kSpace;
+ os << entry.when << kSpace;
+ os << entry.exit_code << std::endl;
+
+ // Restore I/O related exceptions
+ os.exceptions(saved_exceptions);
+ return os;
+}
+
+bool operator==(const OdrCompilationLogEntry& lhs, const OdrCompilationLogEntry& rhs) {
+ return lhs.apex_version == rhs.apex_version && lhs.trigger == rhs.trigger &&
+ lhs.when == rhs.when && lhs.exit_code == rhs.exit_code;
+}
+
+bool operator!=(const OdrCompilationLogEntry& lhs, const OdrCompilationLogEntry& rhs) {
+ return !(lhs == rhs);
+}
+
+OdrCompilationLog::OdrCompilationLog(const char* compilation_log_path)
+ : log_path_(compilation_log_path) {
+ if (log_path_ != nullptr && OS::FileExists(log_path_)) {
+ if (!Read()) {
+ PLOG(ERROR) << "Failed to read compilation log: " << log_path_;
+ }
+ }
+}
+
+OdrCompilationLog::~OdrCompilationLog() {
+ if (log_path_ != nullptr && !Write()) {
+ PLOG(ERROR) << "Failed to write compilation log: " << log_path_;
+ }
+}
+
+bool OdrCompilationLog::Read() {
+ std::ifstream ifs(log_path_);
+ if (!ifs.good()) {
+ return false;
+ }
+
+ while (!ifs.eof()) {
+ OdrCompilationLogEntry entry;
+ ifs >> entry;
+ if (ifs.fail()) {
+ entries_.clear();
+ return false;
+ }
+ entries_.push_back(entry);
+ }
+
+ return true;
+}
+
+bool OdrCompilationLog::Write() const {
+ std::ofstream ofs(log_path_, std::ofstream::trunc);
+ if (!ofs.good()) {
+ return false;
+ }
+
+ for (const auto& entry : entries_) {
+ ofs << entry;
+ if (ofs.fail()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void OdrCompilationLog::Truncate() {
+ if (entries_.size() < kMaxLoggedEntries) {
+ return;
+ }
+
+ size_t excess = entries_.size() - kMaxLoggedEntries;
+ entries_.erase(entries_.begin(), entries_.begin() + excess);
+}
+
+size_t OdrCompilationLog::NumberOfEntries() const {
+ return entries_.size();
+}
+
+const OdrCompilationLogEntry* OdrCompilationLog::Peek(size_t index) const {
+ if (index >= entries_.size()) {
+ return nullptr;
+ }
+ return &entries_[index];
+}
+
+void OdrCompilationLog::Log(int64_t apex_version,
+ OdrMetrics::Trigger trigger,
+ ExitCode compilation_result) {
+ time_t now;
+ time(&now);
+ Log(apex_version, trigger, now, compilation_result);
+}
+
+void OdrCompilationLog::Log(int64_t apex_version,
+ OdrMetrics::Trigger trigger,
+ time_t when,
+ ExitCode compilation_result) {
+ entries_.push_back(OdrCompilationLogEntry{
+ apex_version, static_cast<int32_t>(trigger), when, static_cast<int32_t>(compilation_result)});
+ Truncate();
+}
+
+bool OdrCompilationLog::ShouldAttemptCompile(int64_t apex_version,
+ OdrMetrics::Trigger trigger,
+ time_t now) const {
+ if (entries_.size() == 0) {
+ // We have no history, try to compile.
+ return true;
+ }
+
+ if (apex_version != entries_.back().apex_version) {
+ // There is a new ART APEX, we should use compile right away.
+ return true;
+ }
+
+ if (trigger == OdrMetrics::Trigger::kDexFilesChanged) {
+ // The DEX files in the classpaths have changed, possibly an OTA has updated them.
+ return true;
+ }
+
+ // Compute the backoff time based on the number of consecutive failures.
+ //
+ // Wait 12 hrs * pow(2, consecutive_failures) since the last compilation attempt.
+ static const int kSecondsPerDay = 86'400;
+ time_t backoff = kSecondsPerDay / 2;
+ for (auto it = entries_.crbegin(); it != entries_.crend(); ++it, backoff *= 2) {
+ if (it->exit_code == ExitCode::kCompilationSuccess) {
+ break;
+ }
+ }
+
+ if (now == 0) {
+ time(&now);
+ }
+
+ const time_t last_attempt = entries_.back().when;
+ const time_t threshold = last_attempt + backoff;
+ return now >= threshold;
+}
+
+} // namespace odrefresh
+} // namespace art
diff --git a/odrefresh/odr_compilation_log.h b/odrefresh/odr_compilation_log.h
new file mode 100644
index 0000000000..6f13c97db1
--- /dev/null
+++ b/odrefresh/odr_compilation_log.h
@@ -0,0 +1,93 @@
+/*
+ * 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_ODR_COMPILATION_LOG_H_
+#define ART_ODREFRESH_ODR_COMPILATION_LOG_H_
+
+#include <time.h>
+
+#include <cstdint>
+#include <iosfwd>
+#include <vector>
+
+#include <odrefresh/odrefresh.h>
+#include <odr_metrics.h>
+
+namespace art {
+namespace odrefresh {
+
+// OdrCompilationLogEntry represents the result of a compilation attempt by odrefresh.
+struct OdrCompilationLogEntry {
+ int64_t apex_version;
+ int32_t trigger;
+ time_t when;
+ int32_t exit_code;
+};
+
+// Read an `OdrCompilationLogEntry` from an input stream.
+std::istream& operator>>(std::istream& is, OdrCompilationLogEntry& entry);
+
+// Write an `OdrCompilationLogEntry` to an output stream.
+std::ostream& operator<<(std::ostream& os, const OdrCompilationLogEntry& entry);
+
+// Equality test for two `OdrCompilationLogEntry` instances.
+bool operator==(const OdrCompilationLogEntry& lhs, const OdrCompilationLogEntry& rhs);
+bool operator!=(const OdrCompilationLogEntry& lhs, const OdrCompilationLogEntry& rhs);
+
+class OdrCompilationLog {
+ public:
+ // The compilation log location is in the same directory as used for the metricss.log. This
+ // directory is only used by odrefresh whereas the ART apexdata directory is also used by odsign
+ // and others which may lead to the deletion (or rollback) of the log file.
+ static constexpr const char* kCompilationLogFile = "/data/misc/odrefresh/compilation-log.txt";
+ static constexpr const size_t kMaxLoggedEntries = 4;
+
+ explicit OdrCompilationLog(const char* compilation_log_path = kCompilationLogFile);
+ ~OdrCompilationLog();
+
+ // Applies policy to compilation log to determine whether to recompile.
+ bool ShouldAttemptCompile(int64_t apex_version,
+ OdrMetrics::Trigger trigger,
+ time_t now = 0) const;
+
+ // Returns the number of entries in the log. The log never exceeds `kMaxLoggedEntries`.
+ size_t NumberOfEntries() const;
+
+ // Returns the entry at position `index` or nullptr if `index` is out of bounds.
+ const OdrCompilationLogEntry* Peek(size_t index) const;
+
+ void Log(int64_t apex_version, OdrMetrics::Trigger trigger, ExitCode compilation_result);
+
+ void Log(int64_t apex_version,
+ OdrMetrics::Trigger trigger,
+ time_t when,
+ ExitCode compilation_result);
+
+ // Truncates the in memory log to have `kMaxLoggedEntries` records.
+ void Truncate();
+
+ private:
+ bool Read();
+ bool Write() const;
+
+ std::vector<OdrCompilationLogEntry> entries_;
+ const char* log_path_;
+};
+
+} // namespace odrefresh
+} // namespace art
+
+#endif // ART_ODREFRESH_ODR_COMPILATION_LOG_H_
diff --git a/odrefresh/odr_compilation_log_test.cc b/odrefresh/odr_compilation_log_test.cc
new file mode 100644
index 0000000000..c5c95559c4
--- /dev/null
+++ b/odrefresh/odr_compilation_log_test.cc
@@ -0,0 +1,399 @@
+/*
+ * 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_compilation_log.h>
+
+#include <time.h>
+
+#include <cstdint>
+#include <ctime>
+#include <iosfwd>
+#include <istream>
+#include <limits>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "base/common_art_test.h"
+
+#include "odrefresh/odrefresh.h"
+#include "odr_metrics.h"
+
+namespace art {
+namespace odrefresh {
+
+const time_t kSecondsPerDay = 86'400;
+
+class OdrCompilationLogTest : public CommonArtTest {};
+
+TEST(OdrCompilationLogEntry, Equality) {
+ OdrCompilationLogEntry a{1, 2, 3, 4};
+
+ ASSERT_EQ(a, (OdrCompilationLogEntry{1, 2, 3, 4}));
+ ASSERT_NE(a, (OdrCompilationLogEntry{9, 2, 3, 4}));
+ ASSERT_NE(a, (OdrCompilationLogEntry{1, 9, 3, 4}));
+ ASSERT_NE(a, (OdrCompilationLogEntry{1, 2, 9, 4}));
+ ASSERT_NE(a, (OdrCompilationLogEntry{2, 2, 3, 9}));
+}
+
+TEST(OdrCompilationLogEntry, InputOutput) {
+ const OdrCompilationLogEntry entries[] = {
+ {1, 2, 3, 4},
+ {std::numeric_limits<int64_t>::min(),
+ std::numeric_limits<int32_t>::min(),
+ std::numeric_limits<time_t>::min(),
+ std::numeric_limits<int32_t>::min()},
+ {std::numeric_limits<int64_t>::max(),
+ std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<time_t>::max(),
+ std::numeric_limits<int32_t>::max()},
+ {0, 0, 0, 0},
+ {0x7fedcba9'87654321, 0x12345678, 0x2346789, 0x76543210}
+ };
+ for (const auto& entry : entries) {
+ std::stringstream ss;
+ ss << entry;
+ OdrCompilationLogEntry actual;
+ ss >> actual;
+ ASSERT_EQ(entry, actual);
+ }
+}
+
+TEST(OdrCompilationLogEntry, TruncatedInput) {
+ std::stringstream ss;
+ ss << "1 2";
+
+ OdrCompilationLogEntry entry;
+ ss >> entry;
+
+ ASSERT_TRUE(ss.fail());
+ ASSERT_FALSE(ss.bad());
+}
+
+TEST(OdrCompilationLogEntry, ReadMultiple) {
+ std::stringstream ss;
+ ss << "1 2 3 4\n5 6 7 8\n";
+
+ OdrCompilationLogEntry entry0, entry1;
+ ss >> entry0 >> entry1;
+ ASSERT_EQ(entry0, (OdrCompilationLogEntry{1, 2, 3, 4}));
+ ASSERT_EQ(entry1, (OdrCompilationLogEntry{5, 6, 7, 8}));
+
+ ASSERT_FALSE(ss.fail());
+ ASSERT_FALSE(ss.bad());
+}
+
+TEST(OdrCompilationLog, ShouldAttemptCompile) {
+ OdrCompilationLog ocl(/*compilation_log_path=*/nullptr);
+
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(1, OdrMetrics::Trigger::kMissingArtifacts, 0));
+
+ ocl.Log(
+ /*apex_version=*/1, OdrMetrics::Trigger::kApexVersionMismatch, ExitCode::kCompilationSuccess);
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(2, OdrMetrics::Trigger::kApexVersionMismatch));
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(1, OdrMetrics::Trigger::kApexVersionMismatch));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(1, OdrMetrics::Trigger::kDexFilesChanged));
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(1, OdrMetrics::Trigger::kUnknown));
+}
+
+TEST(OdrCompilationLog, BackOffNoHistory) {
+ time_t start_time;
+ time(&start_time);
+
+ OdrCompilationLog ocl(/*compilation_log_path=*/nullptr);
+
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1, OdrMetrics::Trigger::kApexVersionMismatch, start_time));
+
+ // Start log
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationFailed);
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1, OdrMetrics::Trigger::kApexVersionMismatch, start_time));
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay / 2));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay));
+
+ // Add one more log entry
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationFailed);
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 2 * kSecondsPerDay));
+
+ // One more.
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationFailed);
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 3 * kSecondsPerDay));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 4 * kSecondsPerDay));
+
+ // And one for the road.
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationFailed);
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 7 * kSecondsPerDay));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 8 * kSecondsPerDay));
+}
+
+TEST(OdrCompilationLog, BackOffHappyHistory) {
+ time_t start_time;
+ time(&start_time);
+
+ OdrCompilationLog ocl(/*compilation_log_path=*/nullptr);
+
+ // Start log with a successful entry.
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationSuccess);
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1, OdrMetrics::Trigger::kApexVersionMismatch, start_time));
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay / 4));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay / 2));
+
+ // Add a log entry for a failed compilation.
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationFailed);
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay / 2));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay));
+}
+
+TEST_F(OdrCompilationLogTest, LogNumberOfEntriesAndPeek) {
+ OdrCompilationLog ocl(/*compilation_log_path=*/nullptr);
+
+ std::vector<OdrCompilationLogEntry> entries = {
+ { 0, 1, 2, 3 },
+ { 1, 2, 3, 4 },
+ { 2, 3, 4, 5 },
+ { 3, 4, 5, 6 },
+ { 4, 5, 6, 7 },
+ { 5, 6, 7, 8 },
+ { 6, 7, 8, 9 }
+ };
+
+ for (size_t i = 0; i < entries.size(); ++i) {
+ OdrCompilationLogEntry& e = entries[i];
+ ocl.Log(e.apex_version,
+ static_cast<OdrMetrics::Trigger>(e.trigger),
+ e.when,
+ static_cast<ExitCode>(e.exit_code));
+ if (i < OdrCompilationLog::kMaxLoggedEntries) {
+ ASSERT_EQ(i + 1, ocl.NumberOfEntries());
+ } else {
+ ASSERT_EQ(OdrCompilationLog::kMaxLoggedEntries, ocl.NumberOfEntries());
+ }
+
+ for (size_t j = 0; j < ocl.NumberOfEntries(); ++j) {
+ const OdrCompilationLogEntry* logged = ocl.Peek(j);
+ ASSERT_TRUE(logged != nullptr);
+ const OdrCompilationLogEntry& expected = entries[i + 1 - ocl.NumberOfEntries() + j];
+ ASSERT_EQ(expected, *logged);
+ }
+ }
+}
+
+TEST_F(OdrCompilationLogTest, LogReadWrite) {
+ std::vector<OdrCompilationLogEntry> entries = {
+ { 0, 1, 2, 3 },
+ { 1, 2, 3, 4 },
+ { 2, 3, 4, 5 },
+ { 3, 4, 5, 6 },
+ { 4, 5, 6, 7 },
+ { 5, 6, 7, 8 },
+ { 6, 7, 8, 9 }
+ };
+
+ ScratchFile scratch_file;
+ scratch_file.Close();
+
+ for (size_t i = 0; i < entries.size(); ++i) {
+ {
+ OdrCompilationLog ocl(scratch_file.GetFilename().c_str());
+ OdrCompilationLogEntry& e = entries[i];
+ ocl.Log(e.apex_version,
+ static_cast<OdrMetrics::Trigger>(e.trigger),
+ e.when,
+ static_cast<ExitCode>(e.exit_code));
+ }
+
+ {
+ OdrCompilationLog ocl(scratch_file.GetFilename().c_str());
+ if (i < OdrCompilationLog::kMaxLoggedEntries) {
+ ASSERT_EQ(i + 1, ocl.NumberOfEntries());
+ } else {
+ ASSERT_EQ(OdrCompilationLog::kMaxLoggedEntries, ocl.NumberOfEntries());
+ }
+
+ for (size_t j = 0; j < ocl.NumberOfEntries(); ++j) {
+ const OdrCompilationLogEntry* logged = ocl.Peek(j);
+ ASSERT_TRUE(logged != nullptr);
+ const OdrCompilationLogEntry& expected = entries[i + 1 - ocl.NumberOfEntries() + j];
+ ASSERT_EQ(expected, *logged);
+ }
+ }
+ }
+}
+
+TEST_F(OdrCompilationLogTest, BackoffBasedOnLog) {
+ time_t start_time;
+ time(&start_time);
+
+ ScratchFile scratch_file;
+ scratch_file.Close();
+
+ const char* log_path = scratch_file.GetFilename().c_str();
+ {
+ OdrCompilationLog ocl(log_path);
+
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1, OdrMetrics::Trigger::kApexVersionMismatch, start_time));
+ }
+
+ {
+ OdrCompilationLog ocl(log_path);
+
+ // Start log
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationFailed);
+ }
+
+ {
+ OdrCompilationLog ocl(log_path);
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1, OdrMetrics::Trigger::kApexVersionMismatch, start_time));
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay / 2));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay));
+ }
+
+ {
+ // Add one more log entry
+ OdrCompilationLog ocl(log_path);
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationFailed);
+ }
+
+ {
+ OdrCompilationLog ocl(log_path);
+
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + kSecondsPerDay));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 2 * kSecondsPerDay));
+ }
+
+ {
+ // One more log entry.
+ OdrCompilationLog ocl(log_path);
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationFailed);
+ }
+
+ {
+ OdrCompilationLog ocl(log_path);
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 3 * kSecondsPerDay));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 4 * kSecondsPerDay));
+ }
+
+ {
+ // And one for the road.
+ OdrCompilationLog ocl(log_path);
+ ocl.Log(/*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time,
+ ExitCode::kCompilationFailed);
+ }
+
+ {
+ OdrCompilationLog ocl(log_path);
+ ASSERT_FALSE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 7 * kSecondsPerDay));
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(
+ /*apex_version=*/1,
+ OdrMetrics::Trigger::kApexVersionMismatch,
+ start_time + 8 * kSecondsPerDay));
+ }
+}
+
+} // namespace odrefresh
+} // namespace art
diff --git a/odrefresh/odr_metrics.h b/odrefresh/odr_metrics.h
index 8b8d5ff013..5ff9df2fcf 100644
--- a/odrefresh/odr_metrics.h
+++ b/odrefresh/odr_metrics.h
@@ -74,11 +74,22 @@ class OdrMetrics final {
const std::string& metrics_file = kOdrefreshMetricsFile);
~OdrMetrics();
+ // Gets the ART APEX that metrics are being collected on behalf of.
+ int64_t GetApexVersion() const {
+ return art_apex_version_;
+ }
+
// Sets the ART APEX that metrics are being collected on behalf of.
void SetArtApexVersion(int64_t version) {
art_apex_version_ = version;
}
+ // Gets the trigger for metrics collection. The trigger is the reason why odrefresh considers
+ // compilation necessary.
+ Trigger GetTrigger() const {
+ return trigger_.has_value() ? trigger_.value() : Trigger::kUnknown;
+ }
+
// Sets the trigger for metrics collection. The trigger is the reason why odrefresh considers
// compilation necessary. Only call this method if compilation is necessary as the presence
// of a trigger means we will try to record and upload metrics.
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index e0720ca3ad..85380a47eb 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -69,6 +69,7 @@
#include "palette/palette_types.h"
#include "odr_artifacts.h"
+#include "odr_compilation_log.h"
#include "odr_config.h"
#include "odr_fs_utils.h"
#include "odr_metrics.h"
@@ -1471,10 +1472,16 @@ class OnDeviceRefresh final {
return odr.CheckArtifactsAreUpToDate(metrics);
} else if (action == "--compile") {
const ExitCode exit_code = odr.CheckArtifactsAreUpToDate(metrics);
- if (exit_code == ExitCode::kCompilationRequired) {
- return odr.Compile(metrics, /*force_compile=*/false);
+ if (exit_code != ExitCode::kCompilationRequired) {
+ return exit_code;
}
- return exit_code;
+ OdrCompilationLog compilation_log;
+ if (!compilation_log.ShouldAttemptCompile(metrics.GetApexVersion(), metrics.GetTrigger())) {
+ return ExitCode::kOkay;
+ }
+ ExitCode compile_result = odr.Compile(metrics, /*force_compile=*/false);
+ compilation_log.Log(metrics.GetApexVersion(), metrics.GetTrigger(), compile_result);
+ return compile_result;
} else if (action == "--force-compile") {
return odr.Compile(metrics, /*force_compile=*/true);
} else if (action == "--verify") {
diff --git a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
index 1ac89fddbb..a8374d127e 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
@@ -43,9 +43,17 @@ import java.util.Set;
public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
private static final String APEX_FILENAME = "test_com.android.art.apex";
+
private static final String ART_APEX_DALVIK_CACHE_DIRNAME =
"/data/misc/apexdata/com.android.art/dalvik-cache";
+ private static final String ODREFRESH_COMPILATION_LOG =
+ "/data/misc/odrefresh/compilation-log.txt";
+
+ private final String[] APP_ARTIFACT_EXTENSIONS = new String[] {".art", ".odex", ".vdex"};
+
+ private final String[] BCP_ARTIFACT_EXTENSIONS = new String[] {".art", ".oat", ".vdex"};
+
private static final String TEST_APP_PACKAGE_NAME = "com.android.tests.odsign";
private static final String TEST_APP_APK = "odsign_e2e_test_app.apk";
@@ -58,6 +66,7 @@ public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
assumeTrue("Updating APEX is not supported", mInstallUtils.isApexUpdateSupported());
installPackage(TEST_APP_APK);
mInstallUtils.installApexes(APEX_FILENAME);
+ removeCompilationLogToAvoidBackoff();
reboot();
}
@@ -65,6 +74,7 @@ public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
public void cleanup() throws Exception {
ApexInfo apex = mInstallUtils.getApexInfo(mInstallUtils.getTestFile(APEX_FILENAME));
getDevice().uninstallPackage(apex.name);
+ removeCompilationLogToAvoidBackoff();
reboot();
}
@@ -132,9 +142,6 @@ public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
final String isa = getSystemServerIsa(mappedArtifacts.iterator().next());
final String isaCacheDirectory = String.format("%s/%s", ART_APEX_DALVIK_CACHE_DIRNAME, isa);
- // Extension types for artifacts that this test looks for.
- final String[] extensions = new String[] {".art", ".odex", ".vdex"};
-
// Check the non-APEX components in the system_server classpath have mapped artifacts.
for (String element : classpathElements) {
// Skip system_server classpath elements from APEXes as these are not currently
@@ -143,7 +150,7 @@ public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
continue;
}
String escapedPath = element.substring(1).replace('/', '@');
- for (String extension : extensions) {
+ for (String extension : APP_ARTIFACT_EXTENSIONS) {
final String fullArtifactPath =
String.format("%s/%s@classes%s", isaCacheDirectory, escapedPath, extension);
assertTrue(
@@ -162,7 +169,8 @@ public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
// Check the mapped artifact has a .art, .odex or .vdex extension.
final boolean knownArtifactKind =
- Arrays.stream(extensions).anyMatch(e -> mappedArtifact.endsWith(e));
+ Arrays.stream(APP_ARTIFACT_EXTENSIONS)
+ .anyMatch(e -> mappedArtifact.endsWith(e));
assertTrue("Unknown artifact kind: " + mappedArtifact, knownArtifactKind);
}
}
@@ -173,10 +181,7 @@ public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
assertTrue("Expect 3 boot-framework artifacts", mappedArtifacts.size() == 3);
- // Extension types for artifacts that this test looks for.
- final String[] extensions = new String[] {".art", ".oat", ".vdex"};
-
- for (String extension : extensions) {
+ for (String extension : BCP_ARTIFACT_EXTENSIONS) {
final String artifact = bootExtensionName + extension;
final boolean found = mappedArtifacts.stream().anyMatch(a -> a.endsWith(artifact));
assertTrue(artifact + " not found", found);
@@ -207,6 +212,9 @@ public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
final boolean adbEnabled = getDevice().enableAdbRoot();
assertTrue("ADB root failed and required to get process maps", adbEnabled);
+ // Check there is a compilation log, we expect compilation to have occurred.
+ assertTrue("Compilation log not found", haveCompilationLog());
+
// Check both zygote and system_server processes to see that they have loaded the
// artifacts compiled and signed by odrefresh and odsign. We check both here rather than
// having a separate test because the device reboots between each @Test method and
@@ -215,6 +223,16 @@ public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
verifySystemServerLoadedArtifacts();
}
+ private boolean haveCompilationLog() throws Exception {
+ CommandResult result =
+ getDevice().executeShellV2Command("stat " + ODREFRESH_COMPILATION_LOG);
+ return result.getExitCode() == 0;
+ }
+
+ private void removeCompilationLogToAvoidBackoff() throws Exception {
+ getDevice().executeShellCommand("rm -f " + ODREFRESH_COMPILATION_LOG);
+ }
+
private void reboot() throws Exception {
getDevice().reboot();
boolean success = getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT.toMillis());