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 <cstddef>
18 #include <cstdint>
19 #include <filesystem>
20 #include <memory>
21 #include <sstream>
22 #include <unordered_map>
23 #include <vector>
24
25 #include <benchmark/benchmark.h>
26
27 #include <unwindstack/Arch.h>
28 #include <unwindstack/Unwinder.h>
29
30 #include "Utils.h"
31 #include "utils/OfflineUnwindUtils.h"
32
33 // This collection of benchmarks exercises Unwinder::Unwind for offline unwinds.
34 //
35 // See `libunwindstack/utils/OfflineUnwindUtils.h` for more info on offline unwinds
36 // and b/192012600 for additional information regarding these benchmarks.
37 namespace unwindstack {
38 namespace {
39
40 static constexpr char kStartup[] = "startup_case";
41 static constexpr char kSteadyState[] = "steady_state_case";
42
43 class OfflineUnwindBenchmark : public benchmark::Fixture {
44 public:
SetUp(benchmark::State & state)45 void SetUp(benchmark::State& state) override {
46 // Ensure each benchmarks has a fresh ELF cache at the start.
47 unwind_case_ = state.range(0) ? kSteadyState : kStartup;
48 resolve_names_ = state.range(1);
49 Elf::SetCachingEnabled(false);
50 }
51
TearDown(const benchmark::State &)52 void TearDown(const benchmark::State&) override {
53 offline_utils_.ReturnToCurrentWorkingDirectory();
54 }
55
SingleUnwindBenchmark(benchmark::State & state,const UnwindSampleInfo & sample_info)56 void SingleUnwindBenchmark(benchmark::State& state, const UnwindSampleInfo& sample_info) {
57 std::string error_msg;
58 if (!offline_utils_.Init(sample_info, &error_msg)) {
59 state.SkipWithError(error_msg.c_str());
60 return;
61 }
62 BenchmarkOfflineUnwindMultipleSamples(state, std::vector<UnwindSampleInfo>{sample_info});
63 }
64
ConsecutiveUnwindBenchmark(benchmark::State & state,const std::vector<UnwindSampleInfo> & sample_infos)65 void ConsecutiveUnwindBenchmark(benchmark::State& state,
66 const std::vector<UnwindSampleInfo>& sample_infos) {
67 std::string error_msg;
68 if (!offline_utils_.Init(sample_infos, &error_msg)) {
69 state.SkipWithError(error_msg.c_str());
70 return;
71 }
72 BenchmarkOfflineUnwindMultipleSamples(state, sample_infos);
73 }
74
75 private:
BenchmarkOfflineUnwindMultipleSamples(benchmark::State & state,const std::vector<UnwindSampleInfo> & sample_infos)76 void BenchmarkOfflineUnwindMultipleSamples(benchmark::State& state,
77 const std::vector<UnwindSampleInfo>& sample_infos) {
78 std::string error_msg;
79 MemoryTracker mem_tracker;
80 auto offline_unwind_multiple_samples = [&](bool benchmarking_unwind) {
81 // The benchmark should only measure the time / memory usage for the creation of
82 // each Unwinder object and the corresponding unwind as close as possible.
83 if (benchmarking_unwind) state.PauseTiming();
84
85 std::unordered_map<std::string_view, std::unique_ptr<Regs>> regs_copies;
86 for (const auto& sample_info : sample_infos) {
87 const std::string& sample_name = sample_info.offline_files_dir;
88
89 // Need to init unwinder with new copy of regs each iteration because unwinding changes
90 // the attributes of the regs object.
91 regs_copies.emplace(sample_name,
92 std::unique_ptr<Regs>(offline_utils_.GetRegs(sample_name)->Clone()));
93
94 // The Maps object will still hold the parsed maps from the previous unwinds. So reset them
95 // unless we want to assume all Maps are cached.
96 if (!sample_info.create_maps) {
97 if (!offline_utils_.CreateMaps(&error_msg, sample_name)) {
98 state.SkipWithError(error_msg.c_str());
99 return;
100 }
101
102 // Since this maps object will be cached, need to make sure that
103 // all of the names are fully qualified paths. This allows the
104 // caching mechanism to properly cache elf files that are
105 // actually the same.
106 if (!offline_utils_.ChangeToSampleDirectory(&error_msg, sample_name)) {
107 state.SkipWithError(error_msg.c_str());
108 return;
109 }
110 for (auto& map_info : *offline_utils_.GetMaps(sample_name)) {
111 auto& name = map_info->name();
112 if (!name.empty()) {
113 std::filesystem::path path;
114 if (std::filesystem::is_symlink(name.c_str())) {
115 path = std::filesystem::read_symlink(name.c_str());
116 } else {
117 path = std::filesystem::current_path();
118 path /= name.c_str();
119 }
120 name = path.lexically_normal().c_str();
121 }
122 }
123 }
124 }
125
126 if (benchmarking_unwind) mem_tracker.StartTrackingAllocations();
127 for (const auto& sample_info : sample_infos) {
128 const std::string& sample_name = sample_info.offline_files_dir;
129 // Need to change to sample directory for Unwinder to properly init ELF objects.
130 // See more info at OfflineUnwindUtils::ChangeToSampleDirectory.
131 if (!offline_utils_.ChangeToSampleDirectory(&error_msg, sample_name)) {
132 state.SkipWithError(error_msg.c_str());
133 return;
134 }
135 if (benchmarking_unwind) state.ResumeTiming();
136
137 Unwinder unwinder(128, offline_utils_.GetMaps(sample_name),
138 regs_copies.at(sample_name).get(),
139 offline_utils_.GetProcessMemory(sample_name));
140 if (sample_info.memory_flag == ProcessMemoryFlag::kIncludeJitMemory) {
141 unwinder.SetJitDebug(offline_utils_.GetJitDebug(sample_name));
142 }
143 unwinder.SetResolveNames(resolve_names_);
144 unwinder.Unwind();
145
146 if (benchmarking_unwind) state.PauseTiming();
147 size_t expected_num_frames;
148 if (!offline_utils_.GetExpectedNumFrames(&expected_num_frames, &error_msg, sample_name)) {
149 state.SkipWithError(error_msg.c_str());
150 return;
151 }
152 if (unwinder.NumFrames() != expected_num_frames) {
153 std::stringstream err_stream;
154 err_stream << "Failed to unwind sample " << sample_name << " properly.Expected "
155 << expected_num_frames << " frames, but unwinder contained "
156 << unwinder.NumFrames() << " frames. Unwind:\n"
157 << DumpFrames(unwinder);
158 state.SkipWithError(err_stream.str().c_str());
159 return;
160 }
161 }
162 if (benchmarking_unwind) {
163 mem_tracker.StopTrackingAllocations();
164 state.ResumeTiming();
165 }
166 };
167
168 if (unwind_case_ == kSteadyState) {
169 WarmUpUnwindCaches(offline_unwind_multiple_samples);
170 }
171
172 for (const auto& _ : state) {
173 offline_unwind_multiple_samples(/*benchmarking_unwind=*/true);
174 }
175 mem_tracker.SetBenchmarkCounters(state);
176 }
177
178 // This functions main purpose is to enable ELF caching for the steady state unwind case
179 // and then perform one unwind to warm up the cache for subsequent unwinds.
180 //
181 // Another reason for pulling this functionality out of the main benchmarking function is
182 // to add an additional call stack frame in between the cache warm-up unwinds and
183 // BenchmarkOfflineUnwindMultipleSamples so that it is easy to filter this set of unwinds out
184 // when profiling.
WarmUpUnwindCaches(const std::function<void (bool)> & offline_unwind_multiple_samples)185 void WarmUpUnwindCaches(const std::function<void(bool)>& offline_unwind_multiple_samples) {
186 Elf::SetCachingEnabled(true);
187 offline_unwind_multiple_samples(/*benchmarking_unwind=*/false);
188 }
189
190 std::string unwind_case_;
191 bool resolve_names_;
192 OfflineUnwindUtils offline_utils_;
193 };
194
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark,BM_offline_straddle_arm64)195 BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64)(benchmark::State& state) {
196 SingleUnwindBenchmark(
197 state, {.offline_files_dir = "straddle_arm64/", .arch = ARCH_ARM64, .create_maps = false});
198 }
199 BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64)
200 ->ArgNames({"is_steady_state_case", "resolve_names"})
201 ->Ranges({{false, true}, {false, true}});
202
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark,BM_offline_straddle_arm64_cached_maps)203 BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64_cached_maps)
204 (benchmark::State& state) {
205 SingleUnwindBenchmark(state, {.offline_files_dir = "straddle_arm64/", .arch = ARCH_ARM64});
206 }
207 BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_straddle_arm64_cached_maps)
208 ->ArgNames({"is_steady_state_case", "resolve_names"})
209 ->Ranges({{false, true}, {false, true}});
210
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark,BM_offline_jit_debug_arm)211 BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_jit_debug_arm)(benchmark::State& state) {
212 SingleUnwindBenchmark(state, {.offline_files_dir = "jit_debug_arm/",
213 .arch = ARCH_ARM,
214 .memory_flag = ProcessMemoryFlag::kIncludeJitMemory,
215 .create_maps = false});
216 }
217 BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_jit_debug_arm)
218 ->ArgNames({"is_steady_state_case", "resolve_names"})
219 ->Ranges({{false, true}, {false, true}});
220
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark,BM_offline_profiler_like_multi_process)221 BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_profiler_like_multi_process)
222 (benchmark::State& state) {
223 ConsecutiveUnwindBenchmark(
224 state,
225 std::vector<UnwindSampleInfo>{
226 {.offline_files_dir = "bluetooth_arm64/pc_1/", .arch = ARCH_ARM64, .create_maps = false},
227 {.offline_files_dir = "jit_debug_arm/",
228 .arch = ARCH_ARM,
229 .memory_flag = ProcessMemoryFlag::kIncludeJitMemory,
230 .create_maps = false},
231 {.offline_files_dir = "photos_reset_arm64/", .arch = ARCH_ARM64, .create_maps = false},
232 {.offline_files_dir = "youtube_compiled_arm64/",
233 .arch = ARCH_ARM64,
234 .create_maps = false},
235 {.offline_files_dir = "yt_music_arm64/", .arch = ARCH_ARM64, .create_maps = false},
236 {.offline_files_dir = "maps_compiled_arm64/28656_oat_odex_jar/",
237 .arch = ARCH_ARM64,
238 .create_maps = false}});
239 }
240 BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_profiler_like_multi_process)
241 ->ArgNames({"is_steady_state_case", "resolve_names"})
242 ->Ranges({{false, true}, {false, true}});
243
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark,BM_offline_profiler_like_single_process_multi_thread)244 BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_profiler_like_single_process_multi_thread)
245 (benchmark::State& state) {
246 ConsecutiveUnwindBenchmark(
247 state,
248 std::vector<UnwindSampleInfo>{{.offline_files_dir = "maps_compiled_arm64/28656_oat_odex_jar/",
249 .arch = ARCH_ARM64,
250 .create_maps = false},
251 {.offline_files_dir = "maps_compiled_arm64/28613_main-thread/",
252 .arch = ARCH_ARM64,
253 .create_maps = false},
254 {.offline_files_dir = "maps_compiled_arm64/28644/",
255 .arch = ARCH_ARM64,
256 .create_maps = false},
257 {.offline_files_dir = "maps_compiled_arm64/28648/",
258 .arch = ARCH_ARM64,
259 .create_maps = false},
260 {.offline_files_dir = "maps_compiled_arm64/28667/",
261 .arch = ARCH_ARM64,
262 .create_maps = false}});
263 }
264 BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_profiler_like_single_process_multi_thread)
265 ->ArgNames({"is_steady_state_case", "resolve_names"})
266 ->Ranges({{false, true}, {false, true}});
267
BENCHMARK_DEFINE_F(OfflineUnwindBenchmark,BM_offline_profiler_like_single_thread_diverse_pcs)268 BENCHMARK_DEFINE_F(OfflineUnwindBenchmark, BM_offline_profiler_like_single_thread_diverse_pcs)
269 (benchmark::State& state) {
270 ConsecutiveUnwindBenchmark(
271 state,
272 std::vector<UnwindSampleInfo>{
273 {.offline_files_dir = "bluetooth_arm64/pc_1/", .arch = ARCH_ARM64, .create_maps = false},
274 {.offline_files_dir = "bluetooth_arm64/pc_2/", .arch = ARCH_ARM64, .create_maps = false},
275 {.offline_files_dir = "bluetooth_arm64/pc_3/", .arch = ARCH_ARM64, .create_maps = false},
276 {.offline_files_dir = "bluetooth_arm64/pc_4/",
277 .arch = ARCH_ARM64,
278 .create_maps = false}});
279 }
280 BENCHMARK_REGISTER_F(OfflineUnwindBenchmark, BM_offline_profiler_like_single_thread_diverse_pcs)
281 ->ArgNames({"is_steady_state_case", "resolve_names"})
282 ->Ranges({{false, true}, {false, true}});
283
284 } // namespace
285 } // namespace unwindstack
286