xref: /aosp_15_r20/system/unwinding/libunwindstack/benchmarks/OfflineUnwindBenchmarks.cpp (revision eb293b8f56ee8303637c5595cfcdeef8039e85c6)
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