| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "odr_metrics.h" |
| |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <fstream> |
| #include <iosfwd> |
| #include <optional> |
| #include <ostream> |
| #include <string> |
| |
| #include <android-base/logging.h> |
| #include <base/os.h> |
| #include <base/string_view_cpp20.h> |
| #include <odr_fs_utils.h> |
| #include <odr_metrics_record.h> |
| |
| namespace art { |
| namespace odrefresh { |
| |
| OdrMetrics::OdrMetrics(const std::string& cache_directory, const std::string& metrics_file) |
| : cache_directory_(cache_directory), metrics_file_(metrics_file) { |
| DCHECK(StartsWith(metrics_file_, "/")); |
| |
| // Remove existing metrics file if it exists. |
| if (OS::FileExists(metrics_file.c_str())) { |
| if (unlink(metrics_file.c_str()) != 0) { |
| PLOG(ERROR) << "Failed to remove metrics file '" << metrics_file << "'"; |
| } |
| } |
| |
| // Create apexdata dalvik-cache directory if it does not exist. It is required before |
| // calling GetFreeSpaceMiB(). |
| if (!EnsureDirectoryExists(cache_directory)) { |
| // This should never fail except for no space on device or configuration issues (e.g. SELinux). |
| LOG(WARNING) << "Cache directory '" << cache_directory << "' could not be created."; |
| } |
| cache_space_free_start_mib_ = GetFreeSpaceMiB(cache_directory); |
| } |
| |
| OdrMetrics::~OdrMetrics() { |
| CaptureSpaceFreeEnd(); |
| |
| // Log metrics only if this is explicitly enabled (typically when compilation was done or an error |
| // occurred). |
| if (enabled_) { |
| WriteToFile(metrics_file_, this); |
| } |
| } |
| |
| void OdrMetrics::CaptureSpaceFreeEnd() { |
| cache_space_free_end_mib_ = GetFreeSpaceMiB(cache_directory_); |
| } |
| |
| void OdrMetrics::SetCompilationTime(int32_t millis) { |
| switch (stage_) { |
| case Stage::kPrimaryBootClasspath: |
| primary_bcp_compilation_millis_ = millis; |
| break; |
| case Stage::kSecondaryBootClasspath: |
| secondary_bcp_compilation_millis_ = millis; |
| break; |
| case Stage::kSystemServerClasspath: |
| system_server_compilation_millis_ = millis; |
| break; |
| case Stage::kCheck: |
| case Stage::kComplete: |
| case Stage::kPreparation: |
| case Stage::kUnknown: |
| LOG(FATAL) << "Unexpected stage " << stage_ << " when setting compilation time"; |
| } |
| } |
| |
| void OdrMetrics::SetDex2OatResult(const ExecResult& dex2oat_result) { |
| switch (stage_) { |
| case Stage::kPrimaryBootClasspath: |
| primary_bcp_dex2oat_result_ = dex2oat_result; |
| break; |
| case Stage::kSecondaryBootClasspath: |
| secondary_bcp_dex2oat_result_ = dex2oat_result; |
| break; |
| case Stage::kSystemServerClasspath: |
| system_server_dex2oat_result_ = dex2oat_result; |
| break; |
| case Stage::kCheck: |
| case Stage::kComplete: |
| case Stage::kPreparation: |
| case Stage::kUnknown: |
| LOG(FATAL) << "Unexpected stage " << stage_ << " when setting dex2oat result"; |
| } |
| } |
| |
| int32_t OdrMetrics::GetFreeSpaceMiB(const std::string& path) { |
| static constexpr uint32_t kBytesPerMiB = 1024 * 1024; |
| static constexpr uint64_t kNominalMaximumCacheBytes = 1024 * kBytesPerMiB; |
| |
| // Assume nominal cache space is 1GiB (much larger than expected, ~100MB). |
| uint64_t used_space_bytes; |
| if (!GetUsedSpace(path, &used_space_bytes)) { |
| used_space_bytes = 0; |
| } |
| uint64_t nominal_free_space_bytes = kNominalMaximumCacheBytes - used_space_bytes; |
| |
| // Get free space on partition containing `path`. |
| uint64_t free_space_bytes; |
| if (!GetFreeSpace(path, &free_space_bytes)) { |
| free_space_bytes = kNominalMaximumCacheBytes; |
| } |
| |
| // Pick the smallest free space, ie space on partition or nominal space in cache. |
| // There are two things of interest for metrics: |
| // (i) identifying failed compilations due to low space. |
| // (ii) understanding what the storage requirements are for the spectrum of boot classpaths and |
| // system_server classpaths. |
| uint64_t free_space_mib = std::min(free_space_bytes, nominal_free_space_bytes) / kBytesPerMiB; |
| return static_cast<int32_t>(free_space_mib); |
| } |
| |
| OdrMetricsRecord OdrMetrics::ToRecord() const { |
| return { |
| .odrefresh_metrics_version = kOdrefreshMetricsVersion, |
| .art_apex_version = art_apex_version_, |
| .trigger = static_cast<int32_t>(trigger_), |
| .stage_reached = static_cast<int32_t>(stage_), |
| .status = static_cast<int32_t>(status_), |
| .cache_space_free_start_mib = cache_space_free_start_mib_, |
| .cache_space_free_end_mib = cache_space_free_end_mib_, |
| .primary_bcp_compilation_millis = primary_bcp_compilation_millis_, |
| .secondary_bcp_compilation_millis = secondary_bcp_compilation_millis_, |
| .system_server_compilation_millis = system_server_compilation_millis_, |
| .primary_bcp_dex2oat_result = ConvertExecResult(primary_bcp_dex2oat_result_), |
| .secondary_bcp_dex2oat_result = ConvertExecResult(secondary_bcp_dex2oat_result_), |
| .system_server_dex2oat_result = ConvertExecResult(system_server_dex2oat_result_), |
| }; |
| } |
| |
| OdrMetricsRecord::Dex2OatExecResult OdrMetrics::ConvertExecResult( |
| const std::optional<ExecResult>& result) { |
| if (result.has_value()) { |
| return OdrMetricsRecord::Dex2OatExecResult(result.value()); |
| } else { |
| return {}; |
| } |
| } |
| |
| void OdrMetrics::WriteToFile(const std::string& path, const OdrMetrics* metrics) { |
| OdrMetricsRecord record = metrics->ToRecord(); |
| |
| const android::base::Result<void>& result = record.WriteToFile(path); |
| if (!result.ok()) { |
| LOG(ERROR) << "Failed to report metrics to file: " << path |
| << ", error: " << result.error().message(); |
| } |
| } |
| |
| } // namespace odrefresh |
| } // namespace art |