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 <base/string_view_cpp20.h>
32 #include <odr_fs_utils.h>
33 #include <odr_metrics_record.h>
34 
35 namespace art {
36 namespace odrefresh {
37 
OdrMetrics(const std::string & cache_directory,const std::string & metrics_file)38 OdrMetrics::OdrMetrics(const std::string& cache_directory, const std::string& metrics_file)
39     : cache_directory_(cache_directory), metrics_file_(metrics_file), status_(Status::kOK) {
40   DCHECK(StartsWith(metrics_file_, "/"));
41 
42   // Remove existing metrics file if it exists.
43   if (OS::FileExists(metrics_file.c_str())) {
44     if (unlink(metrics_file.c_str()) != 0) {
45       PLOG(ERROR) << "Failed to remove metrics file '" << metrics_file << "'";
46     }
47   }
48 
49   // Create apexdata dalvik-cache directory if it does not exist. It is required before
50   // calling GetFreeSpaceMiB().
51   if (!EnsureDirectoryExists(cache_directory)) {
52     // This should never fail except for no space on device or configuration issues (e.g. SELinux).
53     LOG(WARNING) << "Cache directory '" << cache_directory << "' could not be created.";
54   }
55   cache_space_free_start_mib_ = GetFreeSpaceMiB(cache_directory);
56 }
57 
~OdrMetrics()58 OdrMetrics::~OdrMetrics() {
59   cache_space_free_end_mib_ = GetFreeSpaceMiB(cache_directory_);
60 
61   // Log metrics only if odrefresh detected a reason to compile.
62   if (trigger_.has_value()) {
63     WriteToFile(metrics_file_, this);
64   }
65 }
66 
SetCompilationTime(int32_t seconds)67 void OdrMetrics::SetCompilationTime(int32_t seconds) {
68   switch (stage_) {
69     case Stage::kPrimaryBootClasspath:
70       primary_bcp_compilation_seconds_ = seconds;
71       break;
72     case Stage::kSecondaryBootClasspath:
73       secondary_bcp_compilation_seconds_ = seconds;
74       break;
75     case Stage::kSystemServerClasspath:
76       system_server_compilation_seconds_ = seconds;
77       break;
78     case Stage::kCheck:
79     case Stage::kComplete:
80     case Stage::kPreparation:
81     case Stage::kUnknown:
82       break;
83   }
84 }
85 
SetStage(Stage stage)86 void OdrMetrics::SetStage(Stage stage) {
87   if (status_ == Status::kOK) {
88     stage_ = stage;
89   }
90 }
91 
GetFreeSpaceMiB(const std::string & path)92 int32_t OdrMetrics::GetFreeSpaceMiB(const std::string& path) {
93   static constexpr uint32_t kBytesPerMiB = 1024 * 1024;
94   static constexpr uint64_t kNominalMaximumCacheBytes = 1024 * kBytesPerMiB;
95 
96   // Assume nominal cache space is 1GiB (much larger than expected, ~100MB).
97   uint64_t used_space_bytes;
98   if (!GetUsedSpace(path, &used_space_bytes)) {
99     used_space_bytes = 0;
100   }
101   uint64_t nominal_free_space_bytes = kNominalMaximumCacheBytes - used_space_bytes;
102 
103   // Get free space on partition containing `path`.
104   uint64_t free_space_bytes;
105   if (!GetFreeSpace(path, &free_space_bytes)) {
106     free_space_bytes = kNominalMaximumCacheBytes;
107   }
108 
109   // Pick the smallest free space, ie space on partition or nominal space in cache.
110   // There are two things of interest for metrics:
111   //  (i) identifying failed compilations due to low space.
112   // (ii) understanding what the storage requirements are for the spectrum of boot classpaths and
113   //      system_server classpaths.
114   uint64_t free_space_mib = std::min(free_space_bytes, nominal_free_space_bytes) / kBytesPerMiB;
115   return static_cast<int32_t>(free_space_mib);
116 }
117 
ToRecord(OdrMetricsRecord * record) const118 bool OdrMetrics::ToRecord(/*out*/OdrMetricsRecord* record) const {
119   if (!trigger_.has_value()) {
120     return false;
121   }
122   record->art_apex_version = art_apex_version_;
123   record->trigger = static_cast<uint32_t>(trigger_.value());
124   record->stage_reached = static_cast<uint32_t>(stage_);
125   record->status = static_cast<uint32_t>(status_);
126   record->primary_bcp_compilation_seconds = primary_bcp_compilation_seconds_;
127   record->secondary_bcp_compilation_seconds = secondary_bcp_compilation_seconds_;
128   record->system_server_compilation_seconds = system_server_compilation_seconds_;
129   record->cache_space_free_start_mib = cache_space_free_start_mib_;
130   record->cache_space_free_end_mib = cache_space_free_end_mib_;
131   return true;
132 }
133 
WriteToFile(const std::string & path,const OdrMetrics * metrics)134 void OdrMetrics::WriteToFile(const std::string& path, const OdrMetrics* metrics) {
135   OdrMetricsRecord record;
136   if (!metrics->ToRecord(&record)) {
137     LOG(ERROR) << "Attempting to report metrics without a compilation trigger.";
138     return;
139   }
140 
141   // Preserve order from frameworks/proto_logging/stats/atoms.proto in metrics file written.
142   std::ofstream ofs(path);
143   ofs << record;
144 }
145 
146 }  // namespace odrefresh
147 }  // namespace art
148