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 "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.h"
18
19 #include <cinttypes>
20 #include <cstddef>
21 #include <cstdint>
22 #include <memory>
23 #include <optional>
24 #include <string>
25 #include <utility>
26 #include <vector>
27
28 #include "perfetto/base/logging.h"
29 #include "perfetto/base/status.h"
30 #include "perfetto/ext/base/status_or.h"
31 #include "perfetto/ext/base/string_view.h"
32 #include "perfetto/trace_processor/basic_types.h"
33 #include "src/trace_processor/containers/null_term_string_view.h"
34 #include "src/trace_processor/containers/string_pool.h"
35 #include "src/trace_processor/db/column_storage.h"
36 #include "src/trace_processor/db/table.h"
37 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/tables_py.h"
38 #include "src/trace_processor/storage/trace_storage.h"
39 #include "src/trace_processor/tables/profiler_tables_py.h"
40 #include "src/trace_processor/types/trace_processor_context.h"
41
42 namespace perfetto::trace_processor {
43 namespace tables {
44
45 ExperimentalAnnotatedCallstackTable::~ExperimentalAnnotatedCallstackTable() =
46 default;
47
48 } // namespace tables
49
50 namespace {
51
52 enum class MapType {
53 kArtInterp,
54 kArtJit,
55 kArtAot,
56 kNativeLibart,
57 kNativeOther,
58 kOther
59 };
60
61 // Mapping examples:
62 // /system/lib64/libc.so
63 // /system/framework/framework.jar
64 // /memfd:jit-cache (deleted)
65 // /data/dalvik-cache/arm64/<snip>[email protected]
66 // /data/app/<snip>/base.apk!libmonochrome_64.so
67 // [vdso]
68 // TODO(rsavitski): consider moving this to a hidden column on
69 // stack_profile_mapping.
ClassifyMap(NullTermStringView map)70 MapType ClassifyMap(NullTermStringView map) {
71 if (map.empty())
72 return MapType::kOther;
73
74 // Primary mapping where modern ART puts jitted code.
75 // The Zygote's JIT region is inherited by all descendant apps, so it can
76 // still appear in their callstacks.
77 if (map.StartsWith("/memfd:jit-cache") ||
78 map.StartsWith("/memfd:jit-zygote-cache")) {
79 return MapType::kArtJit;
80 }
81
82 size_t last_slash_pos = map.rfind('/');
83 if (last_slash_pos != NullTermStringView::npos) {
84 base::StringView suffix = map.substr(last_slash_pos);
85 if (suffix.StartsWith("/libart.so") || suffix.StartsWith("/libartd.so"))
86 return MapType::kNativeLibart;
87 }
88 size_t extension_pos = map.rfind('.');
89 if (extension_pos != NullTermStringView::npos) {
90 base::StringView suffix = map.substr(extension_pos);
91 if (suffix.StartsWith(".so"))
92 return MapType::kNativeOther;
93 // unqualified dex
94 if (suffix.StartsWith(".dex"))
95 return MapType::kArtInterp;
96 // dex with verification speedup info, produced by dex2oat
97 if (suffix.StartsWith(".vdex"))
98 return MapType::kArtInterp;
99 // possibly uncompressed dex in a jar archive
100 if (suffix.StartsWith(".jar"))
101 return MapType::kArtInterp;
102 // android package (zip file), this can contain uncompressed dexes or
103 // native libraries that are mmap'd directly into the process. We rely on
104 // libunwindstack's MapInfo::GetFullName, which suffixes the mapping with
105 // "!lib.so" if it knows that the referenced piece of the archive is an
106 // uncompressed ELF file. So an unadorned ".apk" is assumed to be a dex
107 // file.
108 if (suffix.StartsWith(".apk"))
109 return MapType::kArtInterp;
110 // ahead of time compiled ELFs
111 if (suffix.StartsWith(".oat"))
112 return MapType::kArtAot;
113 // older/alternative name for .oat
114 if (suffix.StartsWith(".odex"))
115 return MapType::kArtAot;
116 }
117 return MapType::kOther;
118 }
119
120 } // namespace
121
TableName()122 std::string ExperimentalAnnotatedStack::TableName() {
123 return tables::ExperimentalAnnotatedCallstackTable::Name();
124 }
125
CreateSchema()126 Table::Schema ExperimentalAnnotatedStack::CreateSchema() {
127 return tables::ExperimentalAnnotatedCallstackTable::ComputeStaticSchema();
128 }
129
130 // TODO(carlscab): Replace annotation logic with
131 // src/trace_processor/util/annotated_callsites.h
ComputeTable(const std::vector<SqlValue> & arguments)132 base::StatusOr<std::unique_ptr<Table>> ExperimentalAnnotatedStack::ComputeTable(
133 const std::vector<SqlValue>& arguments) {
134 PERFETTO_CHECK(arguments.size() == 1);
135
136 using CallsiteTable = tables::StackProfileCallsiteTable;
137
138 const auto& cs_table = context_->storage->stack_profile_callsite_table();
139 const auto& f_table = context_->storage->stack_profile_frame_table();
140 const auto& m_table = context_->storage->stack_profile_mapping_table();
141
142 if (arguments[0].type != SqlValue::Type::kLong) {
143 return base::ErrStatus("invalid input callsite id");
144 }
145
146 CallsiteId start_id(static_cast<uint32_t>(arguments[0].AsLong()));
147 auto opt_start_ref = cs_table.FindById(start_id);
148 if (!opt_start_ref) {
149 return base::ErrStatus("callsite with id %" PRIu32 " not found",
150 start_id.value);
151 }
152
153 // Iteratively walk the parent_id chain to construct the list of callstack
154 // entries, each pointing at a frame.
155 std::vector<CallsiteTable::RowNumber> cs_rows;
156 cs_rows.push_back(opt_start_ref->ToRowNumber());
157 std::optional<CallsiteId> maybe_parent_id = opt_start_ref->parent_id();
158 while (maybe_parent_id) {
159 auto parent_ref = *cs_table.FindById(*maybe_parent_id);
160 cs_rows.push_back(parent_ref.ToRowNumber());
161 maybe_parent_id = parent_ref.parent_id();
162 }
163
164 // Walk the callsites root-to-leaf, annotating:
165 // * managed frames with their execution state (interpreted/jit/aot)
166 // * common ART frames, which are usually not relevant to
167 // visualisation/inspection
168 //
169 // This is not a per-frame decision, because we do not want to filter out ART
170 // frames immediately after a JNI transition (such frames are often relevant).
171 //
172 // As a consequence of the logic being based on a root-to-leaf walk, a given
173 // callsite will always have the same annotation, as the parent path is always
174 // the same, and children callsites do not affect their parents' annotations.
175 StringId art_jni_trampoline =
176 context_->storage->InternString("art_jni_trampoline");
177
178 StringId common_frame = context_->storage->InternString("common-frame");
179 StringId common_frame_interp =
180 context_->storage->InternString("common-frame-interp");
181 StringId art_interp = context_->storage->InternString("interp");
182 StringId art_jit = context_->storage->InternString("jit");
183 StringId art_aot = context_->storage->InternString("aot");
184
185 // Annotation FSM states:
186 // * kInitial: default, native-only callstacks never leave this state.
187 // * kEraseLibart: we've seen a managed frame, and will now "erase" (i.e. tag
188 // as a common-frame) frames belonging to the ART runtime.
189 // * kKeepNext: we've seen a special JNI trampoline for managed->native
190 // transition, keep the immediate child (even if it is in ART),
191 // and then go back to kEraseLibart.
192 // Regardless of the state, managed frames get annotated with their execution
193 // mode, based on the mapping.
194 enum class State { kInitial, kEraseLibart, kKeepNext };
195 State annotation_state = State::kInitial;
196
197 std::vector<StringPool::Id> annotations_reversed;
198 for (auto it = cs_rows.rbegin(); it != cs_rows.rend(); ++it) {
199 auto cs_ref = it->ToRowReference(cs_table);
200 auto frame_ref = *f_table.FindById(cs_ref.frame_id());
201 auto map_ref = *m_table.FindById(frame_ref.mapping());
202
203 // Keep immediate callee of a JNI trampoline, but keep tagging all
204 // successive libart frames as common.
205 if (annotation_state == State::kKeepNext) {
206 annotations_reversed.push_back(kNullStringId);
207 annotation_state = State::kEraseLibart;
208 continue;
209 }
210
211 // Special-case "art_jni_trampoline" frames, keeping their immediate callee
212 // even if it is in libart, as it could be a native implementation of a
213 // managed method. Example for "java.lang.reflect.Method.Invoke":
214 // art_jni_trampoline
215 // art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
216 //
217 // Simpleperf also relies on this frame name, so it should be fairly stable.
218 // TODO(rsavitski): consider detecting standard JNI upcall entrypoints -
219 // _JNIEnv::Call*. These are sometimes inlined into other DSOs, so erasing
220 // only the libart frames does not clean up all of the JNI-related frames.
221 StringId fname_id = frame_ref.name();
222 if (fname_id == art_jni_trampoline) {
223 annotations_reversed.push_back(common_frame);
224 annotation_state = State::kKeepNext;
225 continue;
226 }
227
228 NullTermStringView map_view = context_->storage->GetString(map_ref.name());
229 MapType map_type = ClassifyMap(map_view);
230
231 // Annotate managed frames.
232 if (map_type == MapType::kArtInterp || //
233 map_type == MapType::kArtJit || //
234 map_type == MapType::kArtAot) {
235 if (map_type == MapType::kArtInterp)
236 annotations_reversed.push_back(art_interp);
237 else if (map_type == MapType::kArtJit)
238 annotations_reversed.push_back(art_jit);
239 else if (map_type == MapType::kArtAot)
240 annotations_reversed.push_back(art_aot);
241
242 // Now know to be in a managed callstack - erase subsequent ART frames.
243 if (annotation_state == State::kInitial)
244 annotation_state = State::kEraseLibart;
245 continue;
246 }
247
248 // Mixed callstack, tag libart frames as uninteresting (common-frame).
249 // Special case a subset of interpreter implementation frames as
250 // "common-frame-interp" using frame name prefixes. Those functions are
251 // actually executed, whereas the managed "interp" frames are synthesised as
252 // their caller by the unwinding library (based on the dex_pc virtual
253 // register restored using the libart's DWARF info). The heuristic covers
254 // the "nterp" and "switch" interpreter implementations.
255 //
256 // Example:
257 // <towards root>
258 // android.view.WindowLayout.computeFrames [interp]
259 // nterp_op_iget_object_slow_path [common-frame-interp]
260 //
261 // This annotation is helpful when trying to answer "what mode was the
262 // process in?" based on the leaf frame of the callstack. As we want to
263 // classify such cases as interpreted, even though the leaf frame is
264 // libart.so.
265 //
266 // For "switch" interpreter, we match any frame starting with
267 // "art::interpreter::" according to itanium mangling.
268 if (annotation_state == State::kEraseLibart &&
269 map_type == MapType::kNativeLibart) {
270 NullTermStringView fname = context_->storage->GetString(fname_id);
271 if (fname.StartsWith("nterp_") || fname.StartsWith("Nterp") ||
272 fname.StartsWith("ExecuteNterp") ||
273 fname.StartsWith("ExecuteSwitchImpl") ||
274 fname.StartsWith("_ZN3art11interpreter")) {
275 annotations_reversed.push_back(common_frame_interp);
276 continue;
277 }
278 annotations_reversed.push_back(common_frame);
279 continue;
280 }
281
282 // default - no special annotation
283 annotations_reversed.push_back(kNullStringId);
284 }
285
286 // Build the dynamic table.
287 PERFETTO_DCHECK(cs_rows.size() == annotations_reversed.size());
288 ColumnStorage<StringPool::Id> annotation_vals;
289 for (auto it = annotations_reversed.rbegin();
290 it != annotations_reversed.rend(); ++it) {
291 annotation_vals.Append(*it);
292 }
293
294 // Hidden column - always the input, i.e. the callsite leaf.
295 ColumnStorage<uint32_t> start_id_vals;
296 for (uint32_t i = 0; i < cs_rows.size(); i++)
297 start_id_vals.Append(start_id.value);
298
299 return std::unique_ptr<Table>(
300 tables::ExperimentalAnnotatedCallstackTable::SelectAndExtendParent(
301 cs_table, std::move(cs_rows), std::move(annotation_vals),
302 std::move(start_id_vals)));
303 }
304
EstimateRowCount()305 uint32_t ExperimentalAnnotatedStack::EstimateRowCount() {
306 return 1;
307 }
308
309 } // namespace perfetto::trace_processor
310