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(int64_t apex_version,int64_t last_update_millis,OdrMetrics::Trigger trigger,time_t now) const182 bool OdrCompilationLog::ShouldAttemptCompile(int64_t apex_version,
183                                              int64_t last_update_millis,
184                                              OdrMetrics::Trigger trigger,
185                                              time_t now) const {
186   if (entries_.size() == 0) {
187     // We have no history, try to compile.
188     return true;
189   }
190 
191   if (apex_version != entries_.back().apex_version) {
192     // There is a new ART APEX, we should compile right away.
193     return true;
194   }
195 
196     if (last_update_millis != entries_.back().last_update_millis) {
197     // There is a samegrade ART APEX update, we should compile right away.
198     return true;
199   }
200 
201   if (trigger == OdrMetrics::Trigger::kDexFilesChanged) {
202     // The DEX files in the classpaths have changed, possibly an OTA has updated them.
203     return true;
204   }
205 
206   // Compute the backoff time based on the number of consecutive failures.
207   //
208   // Wait 12 hrs * pow(2, consecutive_failures) since the last compilation attempt.
209   static const int kSecondsPerDay = 86'400;
210   time_t backoff = kSecondsPerDay / 2;
211   for (auto it = entries_.crbegin(); it != entries_.crend(); ++it, backoff *= 2) {
212     if (it->exit_code == ExitCode::kCompilationSuccess) {
213       break;
214     }
215   }
216 
217   if (now == 0) {
218     time(&now);
219   }
220 
221   const time_t last_attempt = entries_.back().when;
222   const time_t threshold = last_attempt + backoff;
223   return now >= threshold;
224 }
225 
226 }  // namespace odrefresh
227 }  // namespace art
228