Skip to content

Commit

Permalink
odrefresh: add metrics support
Browse files Browse the repository at this point in the history
Adds metrics to stages of odrefresh.

Bug: 169925964
Test: atest art_odrefresh_tests
Test: atest --host art_odrefresh_tests
Change-Id: I768ce5f122b0c1b839f4cdf55aa6dafb68708eb2
  • Loading branch information
ohodson committed Apr 26, 2021
1 parent dda82d2 commit 3d877f0
Show file tree
Hide file tree
Showing 8 changed files with 866 additions and 24 deletions.
22 changes: 19 additions & 3 deletions odrefresh/Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ cc_defaults {
srcs: [
"odrefresh.cc",
"odr_fs_utils.cc",
"odr_metrics.cc",
"odr_metrics_record.cc",
],
local_include_dirs: ["include"],
header_libs: ["dexoptanalyzer_headers"],
generated_sources: [
"apex-info-list",
"art-apex-cache-info",
"art-odrefresh-operator-srcs",
],
shared_libs: [
"libartpalette",
Expand Down Expand Up @@ -81,6 +84,16 @@ cc_library_headers {
visibility: ["//visibility:public"],
}

gensrcs {
name: "art-odrefresh-operator-srcs",
cmd: "$(location generate_operator_out) art/odrefresh $(in) > $(out)",
tools: ["generate_operator_out"],
srcs: [
"odr_metrics.h",
],
output_extension: "operator_out.cc",
}

art_cc_binary {
name: "odrefresh",
defaults: ["odrefresh-defaults"],
Expand Down Expand Up @@ -126,16 +139,19 @@ art_cc_test {
defaults: [
"art_gtest_defaults",
],
generated_sources: ["art-odrefresh-operator-srcs"],
header_libs: ["odrefresh_headers"],
srcs: [
"odr_artifacts_test.cc",
"odr_fs_utils.cc",
"odr_fs_utils_test.cc",
"odr_metrics.cc",
"odr_metrics_test.cc",
"odr_metrics_record.cc",
"odr_metrics_record_test.cc",
"odrefresh_test.cc",
],
shared_libs: [
"libbase",
],
shared_libs: ["libbase"],
}

xsd_config {
Expand Down
147 changes: 147 additions & 0 deletions odrefresh/odr_metrics.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* 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), status_(Status::kOK) {
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() {
cache_space_free_end_mib_ = GetFreeSpaceMiB(cache_directory_);

// Log metrics only if odrefresh detected a reason to compile.
if (trigger_.has_value()) {
WriteToFile(metrics_file_, this);
}
}

void OdrMetrics::SetCompilationTime(int32_t seconds) {
switch (stage_) {
case Stage::kPrimaryBootClasspath:
primary_bcp_compilation_seconds_ = seconds;
break;
case Stage::kSecondaryBootClasspath:
secondary_bcp_compilation_seconds_ = seconds;
break;
case Stage::kSystemServerClasspath:
system_server_compilation_seconds_ = seconds;
break;
case Stage::kCheck:
case Stage::kComplete:
case Stage::kPreparation:
case Stage::kUnknown:
break;
}
}

void OdrMetrics::SetStage(Stage stage) {
if (status_ == Status::kOK) {
stage_ = stage;
}
}

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);
}

bool OdrMetrics::ToRecord(/*out*/OdrMetricsRecord* record) const {
if (!trigger_.has_value()) {
return false;
}
record->art_apex_version = art_apex_version_;
record->trigger = static_cast<uint32_t>(trigger_.value());
record->stage_reached = static_cast<uint32_t>(stage_);
record->status = static_cast<uint32_t>(status_);
record->primary_bcp_compilation_seconds = primary_bcp_compilation_seconds_;
record->secondary_bcp_compilation_seconds = secondary_bcp_compilation_seconds_;
record->system_server_compilation_seconds = system_server_compilation_seconds_;
record->cache_space_free_start_mib = cache_space_free_start_mib_;
record->cache_space_free_end_mib = cache_space_free_end_mib_;
return true;
}

void OdrMetrics::WriteToFile(const std::string& path, const OdrMetrics* metrics) {
OdrMetricsRecord record;
if (!metrics->ToRecord(&record)) {
LOG(ERROR) << "Attempting to report metrics without a compilation trigger.";
return;
}

// Preserve order from frameworks/proto_logging/stats/atoms.proto in metrics file written.
std::ofstream ofs(path);
ofs << record;
}

} // namespace odrefresh
} // namespace art
155 changes: 155 additions & 0 deletions odrefresh/odr_metrics.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* 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.
*/

#ifndef ART_ODREFRESH_ODR_METRICS_H_
#define ART_ODREFRESH_ODR_METRICS_H_

#include <chrono>
#include <cstdint>
#include <iosfwd>
#include <optional>
#include <string>

#include "base/macros.h"
#include "odr_metrics_record.h"

namespace art {
namespace odrefresh {

class OdrMetrics final {
public:
// Enumeration used to track the latest stage reached running odrefresh.
//
// These values mirror those in OdrefreshReported::Stage in frameworks/proto_logging/atoms.proto.
// NB There are gaps between the values in case an additional stages are introduced.
enum class Stage : uint8_t {
kUnknown = 0,
kCheck = 10,
kPreparation = 20,
kPrimaryBootClasspath = 30,
kSecondaryBootClasspath = 40,
kSystemServerClasspath = 50,
kComplete = 60,
};

// Enumeration describing the overall status, processing stops on the first error discovered.
//
// These values mirror those in OdrefreshReported::Status in frameworks/proto_logging/atoms.proto.
enum class Status : uint8_t {
kUnknown = 0,
kOK = 1,
kNoSpace = 2,
kIoError = 3,
kDex2OatError = 4,
kTimeLimitExceeded = 5,
kStagingFailed = 6,
kInstallFailed = 7,
};

// Enumeration describing the cause of compilation (if any) in odrefresh.
//
// These values mirror those in OdrefreshReported::Trigger in
// frameworks/proto_logging/atoms.proto.
enum class Trigger : uint8_t {
kUnknown = 0,
kApexVersionMismatch = 1,
kDexFilesChanged = 2,
kMissingArtifacts = 3,
};

explicit OdrMetrics(const std::string& cache_directory,
const std::string& metrics_file = kOdrefreshMetricsFile);
~OdrMetrics();

// Sets the ART APEX that metrics are being collected on behalf of.
void SetArtApexVersion(int64_t version) {
art_apex_version_ = version;
}

// Sets the trigger for metrics collection. The trigger is the reason why odrefresh considers
// compilation necessary. Only call this method if compilation is necessary as the presence
// of a trigger means we will try to record and upload metrics.
void SetTrigger(const Trigger trigger) {
trigger_ = trigger;
}

// Sets the execution status of the current odrefresh processing stage.
void SetStatus(const Status status) {
status_ = status;
}

// Sets the current odrefresh processing stage.
void SetStage(Stage stage);

// Record metrics into an OdrMetricsRecord.
// returns true on success, false if instance is not valid (because the trigger value is not set).
bool ToRecord(/*out*/OdrMetricsRecord* record) const;

private:
OdrMetrics(const OdrMetrics&) = delete;
OdrMetrics operator=(const OdrMetrics&) = delete;

static int32_t GetFreeSpaceMiB(const std::string& path);
static void WriteToFile(const std::string& path, const OdrMetrics* metrics);

void SetCompilationTime(int32_t seconds);

const std::string cache_directory_;
const std::string metrics_file_;

int64_t art_apex_version_ = 0;
std::optional<Trigger> trigger_ = {}; // metrics are only logged if compilation is triggered.
Stage stage_ = Stage::kUnknown;
Status status_ = Status::kUnknown;

int32_t primary_bcp_compilation_seconds_ = 0;
int32_t secondary_bcp_compilation_seconds_ = 0;
int32_t system_server_compilation_seconds_ = 0;
int32_t cache_space_free_start_mib_ = 0;
int32_t cache_space_free_end_mib_ = 0;

friend class ScopedOdrCompilationTimer;
};

// Timer used to measure compilation time (in seconds). Automatically associates the time recorded
// with the current stage of the metrics used.
class ScopedOdrCompilationTimer final {
public:
explicit ScopedOdrCompilationTimer(OdrMetrics& metrics) :
metrics_(metrics), start_(std::chrono::steady_clock::now()) {}

~ScopedOdrCompilationTimer() {
auto elapsed_time = std::chrono::steady_clock::now() - start_;
auto elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed_time);
metrics_.SetCompilationTime(static_cast<int32_t>(elapsed_seconds.count()));
}

private:
OdrMetrics& metrics_;
std::chrono::time_point<std::chrono::steady_clock> start_;

DISALLOW_ALLOCATION();
};

// Generated ostream operators.
std::ostream& operator<<(std::ostream& os, OdrMetrics::Status status);
std::ostream& operator<<(std::ostream& os, OdrMetrics::Stage stage);
std::ostream& operator<<(std::ostream& os, OdrMetrics::Trigger trigger);

} // namespace odrefresh
} // namespace art

#endif // ART_ODREFRESH_ODR_METRICS_H_
Loading

0 comments on commit 3d877f0

Please sign in to comment.