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