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 "reporter.h"
18 
19 #include <algorithm>
20 
21 #include <android-base/parseint.h>
22 
23 #include "base/flags.h"
24 #include "oat_file_manager.h"
25 #include "runtime.h"
26 #include "runtime_options.h"
27 #include "statsd.h"
28 #include "thread-current-inl.h"
29 
30 #pragma clang diagnostic push
31 #pragma clang diagnostic error "-Wconversion"
32 
33 namespace art {
34 namespace metrics {
35 
Create(const ReportingConfig & config,Runtime * runtime)36 std::unique_ptr<MetricsReporter> MetricsReporter::Create(
37     const ReportingConfig& config, Runtime* runtime) {
38   // We can't use std::make_unique here because the MetricsReporter constructor is private.
39   return std::unique_ptr<MetricsReporter>{new MetricsReporter{std::move(config), runtime}};
40 }
41 
MetricsReporter(const ReportingConfig & config,Runtime * runtime)42 MetricsReporter::MetricsReporter(const ReportingConfig& config, Runtime* runtime)
43     : config_{config},
44       runtime_{runtime},
45       startup_reported_{false},
46       report_interval_index_{0} {}
47 
~MetricsReporter()48 MetricsReporter::~MetricsReporter() { MaybeStopBackgroundThread(); }
49 
ReloadConfig(const ReportingConfig & config)50 void MetricsReporter::ReloadConfig(const ReportingConfig& config) {
51   DCHECK(!thread_.has_value()) << "The config cannot be reloaded after the background "
52                                   "reporting thread is started.";
53   config_ = config;
54 }
55 
IsMetricsReportingEnabled(const SessionData & session_data) const56 bool MetricsReporter::IsMetricsReportingEnabled(const SessionData& session_data) const {
57   return session_data.session_id % config_.reporting_num_mods < config_.reporting_mods;
58 }
59 
MaybeStartBackgroundThread(SessionData session_data)60 bool MetricsReporter::MaybeStartBackgroundThread(SessionData session_data) {
61   CHECK(!thread_.has_value());
62 
63   session_data_ = session_data;
64   LOG_STREAM(DEBUG) << "Received session metadata: " << session_data_.session_id;
65 
66   if (!IsMetricsReportingEnabled(session_data_)) {
67     return false;
68   }
69 
70   thread_.emplace(&MetricsReporter::BackgroundThreadRun, this);
71   return true;
72 }
73 
MaybeStopBackgroundThread()74 void MetricsReporter::MaybeStopBackgroundThread() {
75   if (thread_.has_value()) {
76     messages_.SendMessage(ShutdownRequestedMessage{});
77     thread_->join();
78     thread_.reset();
79   }
80 }
81 
NotifyStartupCompleted()82 void MetricsReporter::NotifyStartupCompleted() {
83   if (ShouldReportAtStartup() && thread_.has_value()) {
84     messages_.SendMessage(StartupCompletedMessage{});
85   }
86 }
87 
NotifyAppInfoUpdated(AppInfo * app_info)88 void MetricsReporter::NotifyAppInfoUpdated(AppInfo* app_info) {
89   std::string compilation_reason;
90   std::string compiler_filter;
91 
92   app_info->GetPrimaryApkOptimizationStatus(
93       &compiler_filter, &compilation_reason);
94 
95   SetCompilationInfo(
96       CompilationReasonFromName(compilation_reason),
97       CompilerFilterReportingFromName(compiler_filter));
98 }
99 
RequestMetricsReport(bool synchronous)100 void MetricsReporter::RequestMetricsReport(bool synchronous) {
101   if (thread_.has_value()) {
102     messages_.SendMessage(RequestMetricsReportMessage{synchronous});
103     if (synchronous) {
104       thread_to_host_messages_.ReceiveMessage();
105     }
106   }
107 }
108 
SetCompilationInfo(CompilationReason compilation_reason,CompilerFilterReporting compiler_filter)109 void MetricsReporter::SetCompilationInfo(CompilationReason compilation_reason,
110                                          CompilerFilterReporting compiler_filter) {
111   if (thread_.has_value()) {
112     messages_.SendMessage(CompilationInfoMessage{compilation_reason, compiler_filter});
113   }
114 }
115 
BackgroundThreadRun()116 void MetricsReporter::BackgroundThreadRun() {
117   LOG_STREAM(DEBUG) << "Metrics reporting thread started";
118 
119   // AttachCurrentThread is needed so we can safely use the ART concurrency primitives within the
120   // messages_ MessageQueue.
121   const bool attached = runtime_->AttachCurrentThread(kBackgroundThreadName,
122                                                       /*as_daemon=*/true,
123                                                       runtime_->GetSystemThreadGroup(),
124                                                       /*create_peer=*/true);
125   bool running = true;
126 
127   // Configure the backends
128   if (config_.dump_to_logcat) {
129     backends_.emplace_back(new LogBackend(LogSeverity::INFO));
130   }
131   if (config_.dump_to_file.has_value()) {
132     backends_.emplace_back(new FileBackend(config_.dump_to_file.value()));
133   }
134   if (config_.dump_to_statsd) {
135     auto backend = CreateStatsdBackend();
136     if (backend != nullptr) {
137       backends_.emplace_back(std::move(backend));
138     }
139   }
140 
141   MaybeResetTimeout();
142 
143   while (running) {
144     messages_.SwitchReceive(
145         [&]([[maybe_unused]] ShutdownRequestedMessage message) {
146           LOG_STREAM(DEBUG) << "Shutdown request received " << session_data_.session_id;
147           running = false;
148 
149           ReportMetrics();
150         },
151         [&](RequestMetricsReportMessage message) {
152           LOG_STREAM(DEBUG) << "Explicit report request received " << session_data_.session_id;
153           ReportMetrics();
154           if (message.synchronous) {
155             thread_to_host_messages_.SendMessage(ReportCompletedMessage{});
156           }
157         },
158         [&]([[maybe_unused]] TimeoutExpiredMessage message) {
159           LOG_STREAM(DEBUG) << "Timer expired, reporting metrics " << session_data_.session_id;
160 
161           ReportMetrics();
162           MaybeResetTimeout();
163         },
164         [&]([[maybe_unused]] StartupCompletedMessage message) {
165           LOG_STREAM(DEBUG) << "App startup completed, reporting metrics "
166               << session_data_.session_id;
167           ReportMetrics();
168           startup_reported_ = true;
169           MaybeResetTimeout();
170         },
171         [&](CompilationInfoMessage message) {
172           LOG_STREAM(DEBUG) << "Compilation info received " << session_data_.session_id;
173           session_data_.compilation_reason = message.compilation_reason;
174           session_data_.compiler_filter = message.compiler_filter;
175 
176           UpdateSessionInBackends();
177         });
178   }
179 
180   if (attached) {
181     runtime_->DetachCurrentThread();
182   }
183   LOG_STREAM(DEBUG) << "Metrics reporting thread terminating " << session_data_.session_id;
184 }
185 
MaybeResetTimeout()186 void MetricsReporter::MaybeResetTimeout() {
187   if (ShouldContinueReporting()) {
188     messages_.SetTimeout(SecondsToMs(GetNextPeriodSeconds()));
189   }
190 }
191 
GetMetrics()192 const ArtMetrics* MetricsReporter::GetMetrics() {
193   return runtime_->GetMetrics();
194 }
195 
ReportMetrics()196 void MetricsReporter::ReportMetrics() {
197   const ArtMetrics* metrics = GetMetrics();
198 
199   if (!session_started_) {
200     for (auto& backend : backends_) {
201       backend->BeginOrUpdateSession(session_data_);
202     }
203     session_started_ = true;
204   }
205 
206   for (auto& backend : backends_) {
207     metrics->ReportAllMetrics(backend.get());
208   }
209 }
210 
UpdateSessionInBackends()211 void MetricsReporter::UpdateSessionInBackends() {
212   if (session_started_) {
213     for (auto& backend : backends_) {
214       backend->BeginOrUpdateSession(session_data_);
215     }
216   }
217 }
218 
ShouldReportAtStartup() const219 bool MetricsReporter::ShouldReportAtStartup() const {
220   return IsMetricsReportingEnabled(session_data_) &&
221       config_.period_spec.has_value() &&
222       config_.period_spec->report_startup_first;
223 }
224 
ShouldContinueReporting() const225 bool MetricsReporter::ShouldContinueReporting() const {
226   bool result =
227       // Only if the reporting is enabled
228       IsMetricsReportingEnabled(session_data_) &&
229       // and if we have period spec
230       config_.period_spec.has_value() &&
231       // and the periods are non empty
232       !config_.period_spec->periods_seconds.empty() &&
233       // and we already reported startup or not required to report startup
234       (startup_reported_ || !config_.period_spec->report_startup_first) &&
235       // and we still have unreported intervals or we are asked to report continuously.
236       (config_.period_spec->continuous_reporting ||
237               (report_interval_index_ < config_.period_spec->periods_seconds.size()));
238   return result;
239 }
240 
GetNextPeriodSeconds()241 uint32_t MetricsReporter::GetNextPeriodSeconds() {
242   DCHECK(ShouldContinueReporting());
243 
244   // The index is either the current report_interval_index or the last index
245   // if we are in continuous mode and reached the end.
246   uint32_t index = std::min(
247       report_interval_index_,
248       static_cast<uint32_t>(config_.period_spec->periods_seconds.size() - 1));
249 
250   uint32_t result = config_.period_spec->periods_seconds[index];
251 
252   // Advance the index if we didn't get to the end.
253   if (report_interval_index_ < config_.period_spec->periods_seconds.size()) {
254     report_interval_index_++;
255   }
256   return result;
257 }
258 
FromFlags(bool is_system_server)259 ReportingConfig ReportingConfig::FromFlags(bool is_system_server) {
260   std::optional<std::string> spec_str = is_system_server
261       ? gFlags.MetricsReportingSpecSystemServer.GetValueOptional()
262       : gFlags.MetricsReportingSpec.GetValueOptional();
263 
264   std::optional<ReportingPeriodSpec> period_spec = std::nullopt;
265 
266   if (spec_str.has_value()) {
267     std::string error;
268     period_spec = ReportingPeriodSpec::Parse(spec_str.value(), &error);
269     if (!period_spec.has_value()) {
270       LOG(ERROR) << "Failed to create metrics reporting spec from: " << spec_str.value()
271           << " with error: " << error;
272     }
273   }
274 
275   uint32_t reporting_num_mods = is_system_server
276       ? gFlags.MetricsReportingNumModsServer()
277       : gFlags.MetricsReportingNumMods();
278   uint32_t reporting_mods = is_system_server
279       ? gFlags.MetricsReportingModsServer()
280       : gFlags.MetricsReportingMods();
281 
282   if (reporting_mods > reporting_num_mods || reporting_num_mods == 0) {
283     LOG(ERROR) << "Invalid metrics reporting mods: " << reporting_mods
284         << " num modes=" << reporting_num_mods
285         << ". The reporting is disabled";
286     reporting_mods = 0;
287     reporting_num_mods = 100;
288   }
289 
290   return {
291       .dump_to_logcat = gFlags.MetricsWriteToLogcat(),
292       .dump_to_file = gFlags.MetricsWriteToFile.GetValueOptional(),
293       .dump_to_statsd = gFlags.MetricsWriteToStatsd(),
294       .period_spec = period_spec,
295       .reporting_num_mods = reporting_num_mods,
296       .reporting_mods = reporting_mods,
297   };
298 }
299 
Parse(const std::string & spec_str,std::string * error_msg)300 std::optional<ReportingPeriodSpec> ReportingPeriodSpec::Parse(
301     const std::string& spec_str, std::string* error_msg) {
302   *error_msg = "";
303   if (spec_str.empty()) {
304     *error_msg = "Invalid empty spec.";
305     return std::nullopt;
306   }
307 
308   // Split the string. Each element is separated by comma.
309   std::vector<std::string> elems;
310   Split(spec_str, ',', &elems);
311 
312   // Check the startup marker (front) and the continuous one (back).
313   std::optional<ReportingPeriodSpec> spec = std::make_optional(ReportingPeriodSpec());
314   spec->spec = spec_str;
315   spec->report_startup_first = elems.front() == "S";
316   spec->continuous_reporting = elems.back() == "*";
317 
318   // Compute the indices for the period values.
319   size_t start_interval_idx = spec->report_startup_first ? 1 : 0;
320   size_t end_interval_idx = spec->continuous_reporting ? (elems.size() - 1) : elems.size();
321 
322   // '*' needs a numeric interval before in order to be valid.
323   if (spec->continuous_reporting &&
324       end_interval_idx == start_interval_idx) {
325     *error_msg = "Invalid period value in spec: " + spec_str;
326     return std::nullopt;
327   }
328 
329   // Parse the periods.
330   for (size_t i = start_interval_idx; i < end_interval_idx; i++) {
331     uint32_t period;
332     if (!android::base::ParseUint(elems[i], &period)) {
333         *error_msg = "Invalid period value in spec: " + spec_str;
334         return std::nullopt;
335     }
336     spec->periods_seconds.push_back(period);
337   }
338 
339   return spec;
340 }
341 
342 }  // namespace metrics
343 }  // namespace art
344 
345 #pragma clang diagnostic pop  // -Wconversion
346