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_metrics.h"
18
19 #include <unistd.h>
20
21 #include <algorithm>
22 #include <cstdint>
23 #include <fstream>
24 #include <iosfwd>
25 #include <optional>
26 #include <ostream>
27 #include <string>
28
29 #include <android-base/logging.h>
30 #include <base/os.h>
31 #include <odr_fs_utils.h>
32 #include <odr_metrics_record.h>
33
34 namespace art {
35 namespace odrefresh {
36
OdrMetrics(const std::string & cache_directory,const std::string & metrics_file)37 OdrMetrics::OdrMetrics(const std::string& cache_directory, const std::string& metrics_file)
38 : cache_directory_(cache_directory), metrics_file_(metrics_file) {
39 DCHECK(metrics_file.starts_with("/"));
40
41 // Remove existing metrics file if it exists.
42 if (OS::FileExists(metrics_file.c_str())) {
43 if (unlink(metrics_file.c_str()) != 0) {
44 PLOG(ERROR) << "Failed to remove metrics file '" << metrics_file << "'";
45 }
46 }
47
48 // Create apexdata dalvik-cache directory if it does not exist. It is required before
49 // calling GetFreeSpaceMiB().
50 if (!EnsureDirectoryExists(cache_directory)) {
51 // This should never fail except for no space on device or configuration issues (e.g. SELinux).
52 LOG(WARNING) << "Cache directory '" << cache_directory << "' could not be created.";
53 }
54 cache_space_free_start_mib_ = GetFreeSpaceMiB(cache_directory);
55 }
56
~OdrMetrics()57 OdrMetrics::~OdrMetrics() {
58 CaptureSpaceFreeEnd();
59
60 // Log metrics only if this is explicitly enabled (typically when compilation was done or an error
61 // occurred).
62 if (enabled_) {
63 WriteToFile(metrics_file_, this);
64 }
65 }
66
CaptureSpaceFreeEnd()67 void OdrMetrics::CaptureSpaceFreeEnd() {
68 cache_space_free_end_mib_ = GetFreeSpaceMiB(cache_directory_);
69 }
70
SetDex2OatResult(Stage stage,int64_t compilation_time_ms,const std::optional<ExecResult> & dex2oat_result)71 void OdrMetrics::SetDex2OatResult(Stage stage,
72 int64_t compilation_time_ms,
73 const std::optional<ExecResult>& dex2oat_result) {
74 switch (stage) {
75 case Stage::kPrimaryBootClasspath:
76 primary_bcp_compilation_millis_ = compilation_time_ms;
77 primary_bcp_dex2oat_result_ = dex2oat_result;
78 break;
79 case Stage::kSecondaryBootClasspath:
80 secondary_bcp_compilation_millis_ = compilation_time_ms;
81 secondary_bcp_dex2oat_result_ = dex2oat_result;
82 break;
83 case Stage::kSystemServerClasspath:
84 system_server_compilation_millis_ = compilation_time_ms;
85 system_server_dex2oat_result_ = dex2oat_result;
86 break;
87 case Stage::kCheck:
88 case Stage::kComplete:
89 case Stage::kPreparation:
90 case Stage::kUnknown:
91 LOG(FATAL) << "Unexpected stage " << stage_ << " when setting dex2oat result";
92 }
93 }
94
SetBcpCompilationType(Stage stage,BcpCompilationType type)95 void OdrMetrics::SetBcpCompilationType(Stage stage, BcpCompilationType type) {
96 switch (stage) {
97 case Stage::kPrimaryBootClasspath:
98 primary_bcp_compilation_type_ = type;
99 break;
100 case Stage::kSecondaryBootClasspath:
101 secondary_bcp_compilation_type_ = type;
102 break;
103 case Stage::kSystemServerClasspath:
104 case Stage::kCheck:
105 case Stage::kComplete:
106 case Stage::kPreparation:
107 case Stage::kUnknown:
108 LOG(FATAL) << "Unexpected stage " << stage_ << " when setting BCP compilation type";
109 }
110 }
111
GetFreeSpaceMiB(const std::string & path)112 int32_t OdrMetrics::GetFreeSpaceMiB(const std::string& path) {
113 static constexpr uint32_t kBytesPerMiB = 1024 * 1024;
114 static constexpr uint64_t kNominalMaximumCacheBytes = 1024 * kBytesPerMiB;
115
116 // Assume nominal cache space is 1GiB (much larger than expected, ~100MB).
117 uint64_t used_space_bytes;
118 if (!GetUsedSpace(path, &used_space_bytes)) {
119 used_space_bytes = 0;
120 }
121 uint64_t nominal_free_space_bytes = kNominalMaximumCacheBytes - used_space_bytes;
122
123 // Get free space on partition containing `path`.
124 uint64_t free_space_bytes;
125 if (!GetFreeSpace(path, &free_space_bytes)) {
126 free_space_bytes = kNominalMaximumCacheBytes;
127 }
128
129 // Pick the smallest free space, ie space on partition or nominal space in cache.
130 // There are two things of interest for metrics:
131 // (i) identifying failed compilations due to low space.
132 // (ii) understanding what the storage requirements are for the spectrum of boot classpaths and
133 // system_server classpaths.
134 uint64_t free_space_mib = std::min(free_space_bytes, nominal_free_space_bytes) / kBytesPerMiB;
135 return static_cast<int32_t>(free_space_mib);
136 }
137
ToRecord() const138 OdrMetricsRecord OdrMetrics::ToRecord() const {
139 return {
140 .odrefresh_metrics_version = kOdrefreshMetricsVersion,
141 .art_apex_version = art_apex_version_,
142 .trigger = static_cast<int32_t>(trigger_),
143 .stage_reached = static_cast<int32_t>(stage_),
144 .status = static_cast<int32_t>(status_),
145 .cache_space_free_start_mib = cache_space_free_start_mib_,
146 .cache_space_free_end_mib = cache_space_free_end_mib_,
147 .primary_bcp_compilation_millis = primary_bcp_compilation_millis_,
148 .secondary_bcp_compilation_millis = secondary_bcp_compilation_millis_,
149 .system_server_compilation_millis = system_server_compilation_millis_,
150 .primary_bcp_dex2oat_result = ConvertExecResult(primary_bcp_dex2oat_result_),
151 .secondary_bcp_dex2oat_result = ConvertExecResult(secondary_bcp_dex2oat_result_),
152 .system_server_dex2oat_result = ConvertExecResult(system_server_dex2oat_result_),
153 .primary_bcp_compilation_type = static_cast<int32_t>(primary_bcp_compilation_type_),
154 .secondary_bcp_compilation_type = static_cast<int32_t>(secondary_bcp_compilation_type_),
155 };
156 }
157
ConvertExecResult(const std::optional<ExecResult> & result)158 OdrMetricsRecord::Dex2OatExecResult OdrMetrics::ConvertExecResult(
159 const std::optional<ExecResult>& result) {
160 if (result.has_value()) {
161 return OdrMetricsRecord::Dex2OatExecResult(result.value());
162 } else {
163 return {};
164 }
165 }
166
WriteToFile(const std::string & path,const OdrMetrics * metrics)167 void OdrMetrics::WriteToFile(const std::string& path, const OdrMetrics* metrics) {
168 OdrMetricsRecord record = metrics->ToRecord();
169
170 const android::base::Result<void>& result = record.WriteToFile(path);
171 if (!result.ok()) {
172 LOG(ERROR) << "Failed to report metrics to file: " << path
173 << ", error: " << result.error().message();
174 }
175 }
176
177 } // namespace odrefresh
178 } // namespace art
179