/* * 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 #include #include #include #include #include #include #include #include #include #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 {}); // Write log entry. NB update OdrCompilationLog::kLogVersion if changing the format here. is >> entry.apex_version >> std::ws; is >> entry.last_update_millis >> 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.last_update_millis << 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.last_update_millis == rhs.last_update_millis && 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; } std::string log_version; ifs >> log_version >> std::ws; if (log_version != kLogVersion) { 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; } ofs << kLogVersion << std::endl; 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, int64_t last_update_millis, OdrMetrics::Trigger trigger, ExitCode compilation_result) { time_t now; time(&now); Log(apex_version, last_update_millis, trigger, now, compilation_result); } void OdrCompilationLog::Log(int64_t apex_version, int64_t last_update_millis, OdrMetrics::Trigger trigger, time_t when, ExitCode compilation_result) { entries_.push_back(OdrCompilationLogEntry{apex_version, last_update_millis, static_cast(trigger), when, static_cast(compilation_result)}); Truncate(); } bool OdrCompilationLog::ShouldAttemptCompile(OdrMetrics::Trigger trigger, time_t now) const { if (entries_.size() == 0) { // We have no history, try to compile. return true; } // The backoff time is for avoiding too many failed attempts. It should not be applied if the last // compilation was successful. if (entries_.back().exit_code == ExitCode::kCompilationSuccess) { return true; } if (trigger == OdrMetrics::Trigger::kApexVersionMismatch || trigger == OdrMetrics::Trigger::kDexFilesChanged) { // Things have changed since the last run. 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