1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <odr_compilation_log.h>
18
19 #include <errno.h>
20
21 #include <fstream>
22 #include <ios>
23 #include <iosfwd>
24 #include <istream>
25 #include <ostream>
26 #include <streambuf>
27 #include <string>
28 #include <vector>
29
30 #include "android-base/logging.h"
31 #include "base/os.h"
32
33 #include "odrefresh/odrefresh.h"
34 #include "odr_metrics.h"
35
36 namespace art {
37 namespace odrefresh {
38
operator >>(std::istream & is,OdrCompilationLogEntry & entry)39 std::istream& operator>>(std::istream& is, OdrCompilationLogEntry& entry) {
40 // Block I/O related exceptions
41 auto saved_exceptions = is.exceptions();
42 is.exceptions(std::ios_base::iostate {});
43
44 // Write log entry. NB update OdrCompilationLog::kLogVersion if changing the format here.
45 is >> entry.apex_version >> std::ws;
46 is >> entry.last_update_millis >> std::ws;
47 is >> entry.trigger >> std::ws;
48 is >> entry.when >> std::ws;
49 is >> entry.exit_code >> std::ws;
50
51 // Restore I/O related exceptions
52 is.exceptions(saved_exceptions);
53 return is;
54 }
55
operator <<(std::ostream & os,const OdrCompilationLogEntry & entry)56 std::ostream& operator<<(std::ostream& os, const OdrCompilationLogEntry& entry) {
57 static const char kSpace = ' ';
58
59 // Block I/O related exceptions
60 auto saved_exceptions = os.exceptions();
61 os.exceptions(std::ios_base::iostate {});
62
63 os << entry.apex_version << kSpace;
64 os << entry.last_update_millis << kSpace;
65 os << entry.trigger << kSpace;
66 os << entry.when << kSpace;
67 os << entry.exit_code << std::endl;
68
69 // Restore I/O related exceptions
70 os.exceptions(saved_exceptions);
71 return os;
72 }
73
operator ==(const OdrCompilationLogEntry & lhs,const OdrCompilationLogEntry & rhs)74 bool operator==(const OdrCompilationLogEntry& lhs, const OdrCompilationLogEntry& rhs) {
75 return lhs.apex_version == rhs.apex_version && lhs.last_update_millis == rhs.last_update_millis &&
76 lhs.trigger == rhs.trigger && lhs.when == rhs.when && lhs.exit_code == rhs.exit_code;
77 }
78
operator !=(const OdrCompilationLogEntry & lhs,const OdrCompilationLogEntry & rhs)79 bool operator!=(const OdrCompilationLogEntry& lhs, const OdrCompilationLogEntry& rhs) {
80 return !(lhs == rhs);
81 }
82
OdrCompilationLog(const char * compilation_log_path)83 OdrCompilationLog::OdrCompilationLog(const char* compilation_log_path)
84 : log_path_(compilation_log_path) {
85 if (log_path_ != nullptr && OS::FileExists(log_path_)) {
86 if (!Read()) {
87 PLOG(ERROR) << "Failed to read compilation log: " << log_path_;
88 }
89 }
90 }
91
~OdrCompilationLog()92 OdrCompilationLog::~OdrCompilationLog() {
93 if (log_path_ != nullptr && !Write()) {
94 PLOG(ERROR) << "Failed to write compilation log: " << log_path_;
95 }
96 }
97
Read()98 bool OdrCompilationLog::Read() {
99 std::ifstream ifs(log_path_);
100 if (!ifs.good()) {
101 return false;
102 }
103
104 std::string log_version;
105 ifs >> log_version >> std::ws;
106 if (log_version != kLogVersion) {
107 return false;
108 }
109
110 while (!ifs.eof()) {
111 OdrCompilationLogEntry entry;
112 ifs >> entry;
113 if (ifs.fail()) {
114 entries_.clear();
115 return false;
116 }
117 entries_.push_back(entry);
118 }
119
120 return true;
121 }
122
Write() const123 bool OdrCompilationLog::Write() const {
124 std::ofstream ofs(log_path_, std::ofstream::trunc);
125 if (!ofs.good()) {
126 return false;
127 }
128
129 ofs << kLogVersion << std::endl;
130 for (const auto& entry : entries_) {
131 ofs << entry;
132 if (ofs.fail()) {
133 return false;
134 }
135 }
136
137 return true;
138 }
139
Truncate()140 void OdrCompilationLog::Truncate() {
141 if (entries_.size() < kMaxLoggedEntries) {
142 return;
143 }
144
145 size_t excess = entries_.size() - kMaxLoggedEntries;
146 entries_.erase(entries_.begin(), entries_.begin() + excess);
147 }
148
NumberOfEntries() const149 size_t OdrCompilationLog::NumberOfEntries() const {
150 return entries_.size();
151 }
152
Peek(size_t index) const153 const OdrCompilationLogEntry* OdrCompilationLog::Peek(size_t index) const {
154 if (index >= entries_.size()) {
155 return nullptr;
156 }
157 return &entries_[index];
158 }
159
Log(int64_t apex_version,int64_t last_update_millis,OdrMetrics::Trigger trigger,ExitCode compilation_result)160 void OdrCompilationLog::Log(int64_t apex_version,
161 int64_t last_update_millis,
162 OdrMetrics::Trigger trigger,
163 ExitCode compilation_result) {
164 time_t now;
165 time(&now);
166 Log(apex_version, last_update_millis, trigger, now, compilation_result);
167 }
168
Log(int64_t apex_version,int64_t last_update_millis,OdrMetrics::Trigger trigger,time_t when,ExitCode compilation_result)169 void OdrCompilationLog::Log(int64_t apex_version,
170 int64_t last_update_millis,
171 OdrMetrics::Trigger trigger,
172 time_t when,
173 ExitCode compilation_result) {
174 entries_.push_back(OdrCompilationLogEntry{apex_version,
175 last_update_millis,
176 static_cast<int32_t>(trigger),
177 when,
178 static_cast<int32_t>(compilation_result)});
179 Truncate();
180 }
181
ShouldAttemptCompile(OdrMetrics::Trigger trigger,time_t now) const182 bool OdrCompilationLog::ShouldAttemptCompile(OdrMetrics::Trigger trigger, time_t now) const {
183 if (entries_.size() == 0) {
184 // We have no history, try to compile.
185 return true;
186 }
187
188 // The backoff time is for avoiding too many failed attempts. It should not be applied if the last
189 // compilation was successful.
190 if (entries_.back().exit_code == ExitCode::kCompilationSuccess) {
191 return true;
192 }
193
194 if (trigger == OdrMetrics::Trigger::kApexVersionMismatch ||
195 trigger == OdrMetrics::Trigger::kDexFilesChanged) {
196 // Things have changed since the last run.
197 return true;
198 }
199
200 // Compute the backoff time based on the number of consecutive failures.
201 //
202 // Wait 12 hrs * pow(2, consecutive_failures) since the last compilation attempt.
203 static const int kSecondsPerDay = 86'400;
204 time_t backoff = kSecondsPerDay / 2;
205 for (auto it = entries_.crbegin(); it != entries_.crend(); ++it, backoff *= 2) {
206 if (it->exit_code == ExitCode::kCompilationSuccess) {
207 break;
208 }
209 }
210
211 if (now == 0) {
212 time(&now);
213 }
214
215 const time_t last_attempt = entries_.back().when;
216 const time_t threshold = last_attempt + backoff;
217 return now >= threshold;
218 }
219
220 } // namespace odrefresh
221 } // namespace art
222