xref: /aosp_15_r20/external/perfetto/src/trace_processor/util/annotated_callsites.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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/util/annotated_callsites.h"
18 
19 #include <iostream>
20 #include <optional>
21 
22 #include "src/trace_processor/tables/profiler_tables_py.h"
23 #include "src/trace_processor/types/trace_processor_context.h"
24 
25 namespace perfetto {
26 namespace trace_processor {
27 
AnnotatedCallsites(const TraceProcessorContext * context)28 AnnotatedCallsites::AnnotatedCallsites(const TraceProcessorContext* context)
29     : context_(*context),
30       // String to identify trampoline frames. If the string does not exist in
31       // TraceProcessor's StringPool (nullopt) then there will be no trampoline
32       // frames in the trace so there is no point in adding it to the pool to do
33       // all comparisons, instead we initialize the member to std::nullopt and
34       // the string comparisons will all fail.
35       art_jni_trampoline_(
36           context->storage->string_pool().GetId("art_jni_trampoline")) {}
37 
GetState(std::optional<CallsiteId> id)38 AnnotatedCallsites::State AnnotatedCallsites::GetState(
39     std::optional<CallsiteId> id) {
40   if (!id) {
41     return State::kInitial;
42   }
43   auto it = states_.find(*id);
44   if (it != states_.end()) {
45     return it->second;
46   }
47 
48   State state =
49       Get(*context_.storage->stack_profile_callsite_table().FindById(*id))
50           .first;
51   states_.emplace(*id, state);
52   return state;
53 }
54 
55 std::pair<AnnotatedCallsites::State, CallsiteAnnotation>
Get(const tables::StackProfileCallsiteTable::ConstRowReference & callsite)56 AnnotatedCallsites::Get(
57     const tables::StackProfileCallsiteTable::ConstRowReference& callsite) {
58   State state = GetState(callsite.parent_id());
59 
60   // Keep immediate callee of a JNI trampoline, but keep tagging all
61   // successive libart frames as common.
62   if (state == State::kKeepNext) {
63     return {State::kEraseLibart, CallsiteAnnotation::kNone};
64   }
65 
66   // Special-case "art_jni_trampoline" frames, keeping their immediate callee
67   // even if it is in libart, as it could be a native implementation of a
68   // managed method. Example for "java.lang.reflect.Method.Invoke":
69   //   art_jni_trampoline
70   //   art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
71   //
72   // Simpleperf also relies on this frame name, so it should be fairly stable.
73   // TODO(rsavitski): consider detecting standard JNI upcall entrypoints -
74   // _JNIEnv::Call*. These are sometimes inlined into other DSOs, so erasing
75   // only the libart frames does not clean up all of the JNI-related frames.
76   auto frame = *context_.storage->stack_profile_frame_table().FindById(
77       callsite.frame_id());
78   // art_jni_trampoline_ could be std::nullopt if the string does not exist in
79   // the StringPool, but that also means no frame will ever have that name.
80   if (art_jni_trampoline_.has_value() &&
81       frame.name() == art_jni_trampoline_.value()) {
82     return {State::kKeepNext, CallsiteAnnotation::kCommonFrame};
83   }
84 
85   MapType map_type = GetMapType(frame.mapping());
86 
87   // Annotate managed frames.
88   if (map_type == MapType::kArtInterp ||  //
89       map_type == MapType::kArtJit ||     //
90       map_type == MapType::kArtAot) {
91     // Now know to be in a managed callstack - erase subsequent ART frames.
92     if (state == State::kInitial) {
93       state = State::kEraseLibart;
94     }
95 
96     if (map_type == MapType::kArtInterp)
97       return {state, CallsiteAnnotation::kArtInterpreted};
98     if (map_type == MapType::kArtJit)
99       return {state, CallsiteAnnotation::kArtJit};
100     if (map_type == MapType::kArtAot)
101       return {state, CallsiteAnnotation::kArtAot};
102   }
103 
104   // Mixed callstack, tag libart frames as uninteresting (common-frame).
105   // Special case a subset of interpreter implementation frames as
106   // "common-frame-interp" using frame name prefixes. Those functions are
107   // actually executed, whereas the managed "interp" frames are synthesised as
108   // their caller by the unwinding library (based on the dex_pc virtual
109   // register restored using the libart's DWARF info). The heuristic covers
110   // the "nterp" and "switch" interpreter implementations.
111   //
112   // Example:
113   //  <towards root>
114   //  android.view.WindowLayout.computeFrames [interp]
115   //  nterp_op_iget_object_slow_path [common-frame-interp]
116   //
117   // This annotation is helpful when trying to answer "what mode was the
118   // process in?" based on the leaf frame of the callstack. As we want to
119   // classify such cases as interpreted, even though the leaf frame is
120   // libart.so.
121   //
122   // For "switch" interpreter, we match any frame starting with
123   // "art::interpreter::" according to itanium mangling.
124   if (state == State::kEraseLibart && map_type == MapType::kNativeLibart) {
125     NullTermStringView fname = context_.storage->GetString(frame.name());
126     if (fname.StartsWith("nterp_") || fname.StartsWith("Nterp") ||
127         fname.StartsWith("ExecuteNterp") ||
128         fname.StartsWith("ExecuteSwitchImpl") ||
129         fname.StartsWith("_ZN3art11interpreter")) {
130       return {state, CallsiteAnnotation::kCommonFrameInterp};
131     }
132     return {state, CallsiteAnnotation::kCommonFrame};
133   }
134 
135   return {state, CallsiteAnnotation::kNone};
136 }
137 
GetMapType(MappingId id)138 AnnotatedCallsites::MapType AnnotatedCallsites::GetMapType(MappingId id) {
139   auto it = map_types_.find(id);
140   if (it != map_types_.end()) {
141     return it->second;
142   }
143 
144   return map_types_
145       .emplace(id, ClassifyMap(context_.storage->GetString(
146                        context_.storage->stack_profile_mapping_table()
147                            .FindById(id)
148                            ->name())))
149       .first->second;
150 }
151 
ClassifyMap(NullTermStringView map)152 AnnotatedCallsites::MapType AnnotatedCallsites::ClassifyMap(
153     NullTermStringView map) {
154   if (map.empty())
155     return MapType::kOther;
156 
157   // Primary mapping where modern ART puts jitted code.
158   // The Zygote's JIT region is inherited by all descendant apps, so it can
159   // still appear in their callstacks.
160   if (map.StartsWith("/memfd:jit-cache") ||
161       map.StartsWith("/memfd:jit-zygote-cache")) {
162     return MapType::kArtJit;
163   }
164 
165   size_t last_slash_pos = map.rfind('/');
166   if (last_slash_pos != NullTermStringView::npos) {
167     base::StringView suffix = map.substr(last_slash_pos);
168     if (suffix.StartsWith("/libart.so") || suffix.StartsWith("/libartd.so"))
169       return MapType::kNativeLibart;
170   }
171 
172   size_t extension_pos = map.rfind('.');
173   if (extension_pos != NullTermStringView::npos) {
174     base::StringView suffix = map.substr(extension_pos);
175     if (suffix.StartsWith(".so"))
176       return MapType::kNativeOther;
177     // unqualified dex
178     if (suffix.StartsWith(".dex"))
179       return MapType::kArtInterp;
180     // dex with verification speedup info, produced by dex2oat
181     if (suffix.StartsWith(".vdex"))
182       return MapType::kArtInterp;
183     // possibly uncompressed dex in a jar archive
184     if (suffix.StartsWith(".jar"))
185       return MapType::kArtInterp;
186     // android package (zip file), this can contain uncompressed dexes or
187     // native libraries that are mmap'd directly into the process. We rely on
188     // libunwindstack's MapInfo::GetFullName, which suffixes the mapping with
189     // "!lib.so" if it knows that the referenced piece of the archive is an
190     // uncompressed ELF file. So an unadorned ".apk" is assumed to be a dex
191     // file.
192     if (suffix.StartsWith(".apk"))
193       return MapType::kArtInterp;
194     // ahead of time compiled ELFs
195     if (suffix.StartsWith(".oat"))
196       return MapType::kArtAot;
197     // older/alternative name for .oat
198     if (suffix.StartsWith(".odex"))
199       return MapType::kArtAot;
200   }
201   return MapType::kOther;
202 }
203 
204 }  // namespace trace_processor
205 }  // namespace perfetto
206