Revert^2 "odrefresh: compilation backoff" am: f761f58879
Original change: https://android-review.googlesource.com/c/platform/art/+/1729804
Change-Id: I663fd5a297d06b65d379d41eceb2a790d1f6b480
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
index b5a5eb3..e42539d 100644
--- a/odrefresh/Android.bp
+++ b/odrefresh/Android.bp
@@ -29,6 +29,7 @@
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 @@
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 5676398..9d7c9fc 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 0000000..55432f4
--- /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 0000000..6f13c97
--- /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 0000000..c5c9555
--- /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 8b8d5ff..5ff9df2 100644
--- a/odrefresh/odr_metrics.h
+++ b/odrefresh/odr_metrics.h
@@ -74,11 +74,22 @@
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 e0720ca..85380a4 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 @@
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 1ac89fd..a8374d1 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 @@
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 @@
assumeTrue("Updating APEX is not supported", mInstallUtils.isApexUpdateSupported());
installPackage(TEST_APP_APK);
mInstallUtils.installApexes(APEX_FILENAME);
+ removeCompilationLogToAvoidBackoff();
reboot();
}
@@ -65,6 +74,7 @@
public void cleanup() throws Exception {
ApexInfo apex = mInstallUtils.getApexInfo(mInstallUtils.getTestFile(APEX_FILENAME));
getDevice().uninstallPackage(apex.name);
+ removeCompilationLogToAvoidBackoff();
reboot();
}
@@ -132,9 +142,6 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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());