283 lines
12 KiB
C++
283 lines
12 KiB
C++
/*
|
|
* 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 <cstddef>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include <benchmark/benchmark.h>
|
|
|
|
#include <unwindstack/Arch.h>
|
|
#include <unwindstack/Unwinder.h>
|
|
|
|
#include "Utils.h"
|
|
#include "utils/OfflineUnwindUtils.h"
|
|
|
|
// This collection of benchmarks exercises Unwinder::Unwind for offline unwinds.
|
|
//
|
|
// See `libunwindstack/utils/OfflineUnwindUtils.h` for more info on offline unwinds
|
|
// and b/192012600 for additional information regarding these benchmarks.
|
|
namespace unwindstack {
|
|
namespace {
|
|
|
|
static constexpr char kStartup[] = "startup_case";
|
|
static constexpr char kSteadyState[] = "steady_state_case";
|
|
|
|
class OfflineUnwindBenchmark : public benchmark::Fixture {
|
|
public:
|
|
void SetUp(benchmark::State& state) override {
|
|
// Ensure each benchmarks has a fresh ELF cache at the start.
|
|
unwind_case_ = state.range(0) ? kSteadyState : kStartup;
|
|
resolve_names_ = state.range(1);
|
|
Elf::SetCachingEnabled(false);
|
|
}
|
|
|
|
void TearDown(benchmark::State& state) override {
|
|
offline_utils_.ReturnToCurrentWorkingDirectory();
|
|
mem_tracker_.SetBenchmarkCounters(state);
|
|
}
|
|
|
|
void SingleUnwindBenchmark(benchmark::State& state, const UnwindSampleInfo& sample_info) {
|
|
std::string error_msg;
|
|
if (!offline_utils_.Init(sample_info, &error_msg)) {
|
|
state.SkipWithError(error_msg.c_str());
|
|
return;
|
|
}
|
|
BenchmarkOfflineUnwindMultipleSamples(state, std::vector<UnwindSampleInfo>{sample_info});
|
|
}
|
|
|
|
void ConsecutiveUnwindBenchmark(benchmark::State& state,
|
|
const std::vector<UnwindSampleInfo>& sample_infos) {
|
|
std::string error_msg;
|
|
if (!offline_utils_.Init(sample_infos, &error_msg)) {
|
|
state.SkipWithError(error_msg.c_str());
|
|
return;
|
|
}
|
|
BenchmarkOfflineUnwindMultipleSamples(state, sample_infos);
|
|
}
|
|
|
|
private:
|
|
void BenchmarkOfflineUnwindMultipleSamples(benchmark::State& state,
|
|
const std::vector<UnwindSampleInfo>& sample_infos) {
|
|
std::string error_msg;
|
|
auto offline_unwind_multiple_samples = [&](bool benchmarking_unwind) {
|
|
// The benchmark should only measure the time / memory usage for the creation of
|
|
// each Unwinder object and the corresponding unwind as close as possible.
|
|
if (benchmarking_unwind) state.PauseTiming();
|
|
|
|
std::unordered_map<std::string_view, std::unique_ptr<Regs>> regs_copies;
|
|
for (const auto& sample_info : sample_infos) {
|
|
const std::string& sample_name = sample_info.offline_files_dir;
|
|
|
|
// Need to init unwinder with new copy of regs each iteration because unwinding changes
|
|
// the attributes of the regs object.
|
|
regs_copies.emplace(sample_name,
|
|
std::unique_ptr<Regs>(offline_utils_.GetRegs(sample_name)->Clone()));
|
|
|
|
// The Maps object will still hold the parsed maps from the previous unwinds. So reset them
|
|
// unless we want to assume all Maps are cached.
|
|
if (!sample_info.create_maps) {
|
|
if (!offline_utils_.CreateMaps(&error_msg, sample_name)) {
|
|
state.SkipWithError(error_msg.c_str());
|
|
return;
|
|
}
|
|
|
|
// Since this maps object will be cached, need to make sure that
|
|
// all of the names are fully qualified paths. This allows the
|
|
// caching mechanism to properly cache elf files that are
|
|
// actually the same.
|
|
if (!offline_utils_.ChangeToSampleDirectory(&error_msg, sample_name)) {
|
|
state.SkipWithError(error_msg.c_str());
|
|
return;
|
|
}
|
|
for (auto& map_info : *offline_utils_.GetMaps(sample_name)) {
|
|
auto& name = map_info->name();
|
|
if (!name.empty()) {
|
|
std::filesystem::path path;
|
|
if (std::filesystem::is_symlink(name.c_str())) {
|
|
path = std::filesystem::read_symlink(name.c_str());
|
|
} else {
|
|
path = std::filesystem::current_path();
|
|
path /= name.c_str();
|
|
}
|
|
name = path.lexically_normal().c_str();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (benchmarking_unwind) mem_tracker_.StartTrackingAllocations();
|
|
for (const auto& sample_info : sample_infos) {
|
|
const std::string& sample_name = sample_info.offline_files_dir;
|
|
// Need to change to sample directory for Unwinder to properly init ELF objects.
|
|
// See more info at OfflineUnwindUtils::ChangeToSampleDirectory.
|
|
if (!offline_utils_.ChangeToSampleDirectory(&error_msg, sample_name)) {
|
|
state.SkipWithError(error_msg.c_str());
|
|
return;
|
|
}
|
|
if (benchmarking_unwind) state.ResumeTiming();
|
|
|
|
Unwinder unwinder(128, offline_utils_.GetMaps(sample_name),
|
|
regs_copies.at(sample_name).get(),
|
|
offline_utils_.GetProcessMemory(sample_name));
|
|
if (sample_info.memory_flag == ProcessMemoryFlag::kIncludeJitMemory) {
|
|
unwinder.SetJitDebug(offline_utils_.GetJitDebug(sample_name));
|
|
}
|
|
unwinder.SetResolveNames(resolve_names_);
|
|
unwinder.Unwind();
|
|
|
|
if (benchmarking_unwind) state.PauseTiming();
|
|
size_t expected_num_frames;
|
|
if (!offline_utils_.GetExpectedNumFrames(&expected_num_frames, &error_msg, sample_name)) {
|
|
state.SkipWithError(error_msg.c_str());
|
|
return;
|
|
}
|
|
if (unwinder.NumFrames() != expected_num_frames) {
|
|
std::stringstream err_stream;
|
|
err_stream << "Failed to unwind sample " << sample_name << " properly.Expected "
|
|
<< expected_num_frames << " frames, but unwinder contained "
|
|
<< unwinder.NumFrames() << " frames. Unwind:\n"
|
|
<< DumpFrames(unwinder);
|
|
state.SkipWithError(err_stream.str().c_str());
|
|
return;
|
|
}
|
|
}
|
|
if (benchmarking_unwind) mem_tracker_.StopTrackingAllocations();
|
|
};
|
|
|
|
if (unwind_case_ == kSteadyState) {
|
|
WarmUpUnwindCaches(offline_unwind_multiple_samples);
|
|
}
|
|
|
|
for (auto _ : state) {
|
|
offline_unwind_multiple_samples(/*benchmarking_unwind=*/true);
|
|
}
|
|
}
|
|
|
|
// This functions main purpose is to enable ELF caching for the steady state unwind case
|
|
// and then perform one unwind to warm up the cache for subsequent unwinds.
|
|
//
|
|
// Another reason for pulling this functionality out of the main benchmarking function is
|
|
// to add an additional call stack frame in between the cache warm-up unwinds and
|
|
// BenchmarkOfflineUnwindMultipleSamples so that it is easy to filter this set of unwinds out
|
|
// when profiling.
|
|
void WarmUpUnwindCaches(const std::function<void(bool)>& offline_unwind_multiple_samples) {
|
|
Elf::SetCachingEnabled(true);
|
|
offline_unwind_multiple_samples(/*benchmarking_unwind=*/false);
|
|
}
|
|
|
|
std::string unwind_case_;
|
|
bool resolve_names_;
|
|
MemoryTracker mem_tracker_;
|
|
OfflineUnwindUtils offline_utils_;
|
|
};
|
|
|
|
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64)(benchmark::State& state) {
|
|
SingleUnwindBenchmark(
|
|
state, {.offline_files_dir = "straddle_arm64/", .arch = ARCH_ARM64, .create_maps = false});
|
|
}
|
|
BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64)
|
|
->ArgNames({"is_steady_state_case", "resolve_names"})
|
|
->Ranges({{false, true}, {false, true}});
|
|
|
|
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64_cached_maps)
|
|
(benchmark::State& state) {
|
|
SingleUnwindBenchmark(state, {.offline_files_dir = "straddle_arm64/", .arch = ARCH_ARM64});
|
|
}
|
|
BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64_cached_maps)
|
|
->ArgNames({"is_steady_state_case", "resolve_names"})
|
|
->Ranges({{false, true}, {false, true}});
|
|
|
|
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_jit_debug_arm)(benchmark::State& state) {
|
|
SingleUnwindBenchmark(state, {.offline_files_dir = "jit_debug_arm/",
|
|
.arch = ARCH_ARM,
|
|
.memory_flag = ProcessMemoryFlag::kIncludeJitMemory,
|
|
.create_maps = false});
|
|
}
|
|
BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_jit_debug_arm)
|
|
->ArgNames({"is_steady_state_case", "resolve_names"})
|
|
->Ranges({{false, true}, {false, true}});
|
|
|
|
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_profiler_like_multi_process)
|
|
(benchmark::State& state) {
|
|
ConsecutiveUnwindBenchmark(
|
|
state,
|
|
std::vector<UnwindSampleInfo>{
|
|
{.offline_files_dir = "bluetooth_arm64/pc_1/", .arch = ARCH_ARM64, .create_maps = false},
|
|
{.offline_files_dir = "jit_debug_arm/",
|
|
.arch = ARCH_ARM,
|
|
.memory_flag = ProcessMemoryFlag::kIncludeJitMemory,
|
|
.create_maps = false},
|
|
{.offline_files_dir = "photos_reset_arm64/", .arch = ARCH_ARM64, .create_maps = false},
|
|
{.offline_files_dir = "youtube_compiled_arm64/",
|
|
.arch = ARCH_ARM64,
|
|
.create_maps = false},
|
|
{.offline_files_dir = "yt_music_arm64/", .arch = ARCH_ARM64, .create_maps = false},
|
|
{.offline_files_dir = "maps_compiled_arm64/28656_oat_odex_jar/",
|
|
.arch = ARCH_ARM64,
|
|
.create_maps = false}});
|
|
}
|
|
BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_profiler_like_multi_process)
|
|
->ArgNames({"is_steady_state_case", "resolve_names"})
|
|
->Ranges({{false, true}, {false, true}});
|
|
|
|
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_profiler_like_single_process_multi_thread)
|
|
(benchmark::State& state) {
|
|
ConsecutiveUnwindBenchmark(
|
|
state,
|
|
std::vector<UnwindSampleInfo>{{.offline_files_dir = "maps_compiled_arm64/28656_oat_odex_jar/",
|
|
.arch = ARCH_ARM64,
|
|
.create_maps = false},
|
|
{.offline_files_dir = "maps_compiled_arm64/28613_main-thread/",
|
|
.arch = ARCH_ARM64,
|
|
.create_maps = false},
|
|
{.offline_files_dir = "maps_compiled_arm64/28644/",
|
|
.arch = ARCH_ARM64,
|
|
.create_maps = false},
|
|
{.offline_files_dir = "maps_compiled_arm64/28648/",
|
|
.arch = ARCH_ARM64,
|
|
.create_maps = false},
|
|
{.offline_files_dir = "maps_compiled_arm64/28667/",
|
|
.arch = ARCH_ARM64,
|
|
.create_maps = false}});
|
|
}
|
|
BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_profiler_like_single_process_multi_thread)
|
|
->ArgNames({"is_steady_state_case", "resolve_names"})
|
|
->Ranges({{false, true}, {false, true}});
|
|
|
|
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_profiler_like_single_thread_diverse_pcs)
|
|
(benchmark::State& state) {
|
|
ConsecutiveUnwindBenchmark(
|
|
state,
|
|
std::vector<UnwindSampleInfo>{
|
|
{.offline_files_dir = "bluetooth_arm64/pc_1/", .arch = ARCH_ARM64, .create_maps = false},
|
|
{.offline_files_dir = "bluetooth_arm64/pc_2/", .arch = ARCH_ARM64, .create_maps = false},
|
|
{.offline_files_dir = "bluetooth_arm64/pc_3/", .arch = ARCH_ARM64, .create_maps = false},
|
|
{.offline_files_dir = "bluetooth_arm64/pc_4/",
|
|
.arch = ARCH_ARM64,
|
|
.create_maps = false}});
|
|
}
|
|
BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_profiler_like_single_thread_diverse_pcs)
|
|
->ArgNames({"is_steady_state_case", "resolve_names"})
|
|
->Ranges({{false, true}, {false, true}});
|
|
|
|
} // namespace
|
|
} // namespace unwindstack
|