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