• 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/dynamic/experimental_annotated_stack_generator.h"
18 
19 #include "perfetto/ext/base/optional.h"
20 #include "src/trace_processor/storage/trace_storage.h"
21 #include "src/trace_processor/tables/profiler_tables.h"
22 #include "src/trace_processor/types/trace_processor_context.h"
23 
24 #include "perfetto/ext/base/string_utils.h"
25 
26 namespace perfetto {
27 namespace trace_processor {
28 
29 namespace {
30 
31 enum class MapType {
32   kArtInterp,
33   kArtJit,
34   kArtAot,
35   kNativeLibart,
36   kNativeOther,
37   kOther
38 };
39 
40 // Mapping examples:
41 //   /system/lib64/libc.so
42 //   /system/framework/framework.jar
43 //   /memfd:jit-cache (deleted)
44 //   [vdso]
45 // TODO(rsavitski): consider moving this to a hidden column on
46 // stack_profile_mapping, once this logic is sufficiently stable.
ClassifyMap(NullTermStringView map)47 MapType ClassifyMap(NullTermStringView map) {
48   if (map.empty())
49     return MapType::kOther;
50 
51   // Primary mapping where modern ART puts jitted code.
52   // TODO(rsavitski): look into /memfd:jit-zygote-cache.
53   if (!strncmp(map.c_str(), "/memfd:jit-cache", 16))
54     return MapType::kArtJit;
55 
56   size_t last_slash_pos = map.rfind('/');
57   if (last_slash_pos != NullTermStringView::npos) {
58     if (!strncmp(map.c_str() + last_slash_pos, "/libart.so", 10))
59       return MapType::kNativeLibart;
60     if (!strncmp(map.c_str() + last_slash_pos, "/libartd.so", 11))
61       return MapType::kNativeLibart;
62   }
63 
64   size_t extension_pos = map.rfind('.');
65   if (extension_pos != NullTermStringView::npos) {
66     if (!strncmp(map.c_str() + extension_pos, ".so", 3))
67       return MapType::kNativeOther;
68     // dex with verification speedup info, produced by dex2oat
69     if (!strncmp(map.c_str() + extension_pos, ".vdex", 5))
70       return MapType::kArtInterp;
71     // possibly uncompressed dex in a jar archive
72     if (!strncmp(map.c_str() + extension_pos, ".jar", 4))
73       return MapType::kArtInterp;
74     // ahead of time compiled ELFs
75     if (!strncmp(map.c_str() + extension_pos, ".oat", 4))
76       return MapType::kArtAot;
77     // older/alternative name for .oat
78     if (!strncmp(map.c_str() + extension_pos, ".odex", 5))
79       return MapType::kArtAot;
80   }
81   return MapType::kOther;
82 }
83 
GetConstraintColumnIndex(TraceProcessorContext * context)84 uint32_t GetConstraintColumnIndex(TraceProcessorContext* context) {
85   // The dynamic table adds two columns on top of the callsite table. Last
86   // column is the hidden constrain (i.e. input arg) column.
87   return context->storage->stack_profile_callsite_table().GetColumnCount() + 1;
88 }
89 
90 }  // namespace
91 
TableName()92 std::string ExperimentalAnnotatedStackGenerator::TableName() {
93   return "experimental_annotated_callstack";
94 }
95 
CreateSchema()96 Table::Schema ExperimentalAnnotatedStackGenerator::CreateSchema() {
97   auto schema = tables::StackProfileCallsiteTable::Schema();
98   schema.columns.push_back(Table::Schema::Column{
99       "annotation", SqlValue::Type::kString, /* is_id = */ false,
100       /* is_sorted = */ false, /* is_hidden = */ false});
101   schema.columns.push_back(Table::Schema::Column{
102       "start_id", SqlValue::Type::kLong, /* is_id = */ false,
103       /* is_sorted = */ false, /* is_hidden = */ true});
104   return schema;
105 }
106 
ValidateConstraints(const QueryConstraints & qc)107 base::Status ExperimentalAnnotatedStackGenerator::ValidateConstraints(
108     const QueryConstraints& qc) {
109   const auto& cs = qc.constraints();
110   int column = static_cast<int>(GetConstraintColumnIndex(context_));
111 
112   auto id_fn = [column](const QueryConstraints::Constraint& c) {
113     return c.column == column && c.op == SQLITE_INDEX_CONSTRAINT_EQ;
114   };
115   bool has_id_cs = std::find_if(cs.begin(), cs.end(), id_fn) != cs.end();
116   return has_id_cs ? base::OkStatus()
117                    : base::ErrStatus("Failed to find required constraints");
118 }
119 
ComputeTable(const std::vector<Constraint> & cs,const std::vector<Order> &,const BitVector &,std::unique_ptr<Table> & table_return)120 base::Status ExperimentalAnnotatedStackGenerator::ComputeTable(
121     const std::vector<Constraint>& cs,
122     const std::vector<Order>&,
123     const BitVector&,
124     std::unique_ptr<Table>& table_return) {
125   const auto& cs_table = context_->storage->stack_profile_callsite_table();
126   const auto& f_table = context_->storage->stack_profile_frame_table();
127   const auto& m_table = context_->storage->stack_profile_mapping_table();
128 
129   // Input (id of the callsite leaf) is the constraint on the hidden |start_id|
130   // column.
131   uint32_t constraint_col = GetConstraintColumnIndex(context_);
132   auto constraint_it =
133       std::find_if(cs.begin(), cs.end(), [constraint_col](const Constraint& c) {
134         return c.col_idx == constraint_col && c.op == FilterOp::kEq;
135       });
136   PERFETTO_DCHECK(constraint_it != cs.end());
137   if (constraint_it == cs.end() ||
138       constraint_it->value.type != SqlValue::Type::kLong) {
139     return base::ErrStatus("invalid input callsite id");
140   }
141 
142   uint32_t start_id = static_cast<uint32_t>(constraint_it->value.AsLong());
143   base::Optional<uint32_t> start_row =
144       cs_table.id().IndexOf(CallsiteId(start_id));
145   if (!start_row) {
146     return base::ErrStatus("callsite with id %" PRIu32 " not found", start_id);
147   }
148 
149   // Iteratively walk the parent_id chain to construct the list of callstack
150   // entries, each pointing at a frame.
151   std::vector<uint32_t> cs_rows;
152   cs_rows.push_back(*start_row);
153   base::Optional<CallsiteId> maybe_parent_id = cs_table.parent_id()[*start_row];
154   while (maybe_parent_id) {
155     uint32_t parent_row = cs_table.id().IndexOf(*maybe_parent_id).value();
156     cs_rows.push_back(parent_row);
157     maybe_parent_id = cs_table.parent_id()[parent_row];
158   }
159 
160   // Walk the callsites root-to-leaf, annotating:
161   // * managed frames with their execution state (interpreted/jit/aot)
162   // * common ART frames, which are usually not relevant
163   //
164   // This is not a per-frame decision, because we do not want to filter out ART
165   // frames immediately after a JNI transition (such frames are often relevant).
166   //
167   // As a consequence of the logic being based on a root-to-leaf walk, a given
168   // callsite will always have the same annotation, as the parent path is always
169   // the same, and children callsites do not affect their parents' annotations.
170   //
171   // This could also be implemented as a hidden column on the callsite table
172   // (populated at import time), but we want to be more flexible for now.
173   StringId art_jni_trampoline =
174       context_->storage->InternString("art_jni_trampoline");
175 
176   StringId common_frame = context_->storage->InternString("common-frame");
177   StringId art_interp = context_->storage->InternString("interp");
178   StringId art_jit = context_->storage->InternString("jit");
179   StringId art_aot = context_->storage->InternString("aot");
180 
181   // Annotation FSM states:
182   // * kInitial: default, native-only callstacks never leave this state.
183   // * kEraseLibart: we've seen a managed frame, and will now "erase" (i.e. tag
184   //                 as a common-frame) frames belonging to the ART runtime.
185   // * kKeepNext: we've seen a special JNI trampoline for managed->native
186   //              transition, keep the immediate child (even if it is in ART),
187   //              and then go back to kEraseLibart.
188   // Regardless of the state, managed frames get annotated with their execution
189   // mode, based on the mapping.
190   enum class State { kInitial, kEraseLibart, kKeepNext };
191   State annotation_state = State::kInitial;
192 
193   std::vector<StringPool::Id> annotations_reversed;
194   for (auto it = cs_rows.rbegin(); it != cs_rows.rend(); ++it) {
195     FrameId frame_id = cs_table.frame_id()[*it];
196     uint32_t frame_row = f_table.id().IndexOf(frame_id).value();
197 
198     MappingId map_id = f_table.mapping()[frame_row];
199     uint32_t map_row = m_table.id().IndexOf(map_id).value();
200 
201     // Keep immediate callee of a JNI trampoline, but keep tagging all
202     // successive libart frames as common.
203     if (annotation_state == State::kKeepNext) {
204       annotations_reversed.push_back(kNullStringId);
205       annotation_state = State::kEraseLibart;
206       continue;
207     }
208 
209     // Special-case "art_jni_trampoline" frames, keeping their immediate callee
210     // even if it is in libart, as it could be a native implementation of a
211     // managed method. Example for "java.lang.reflect.Method.Invoke":
212     //   art_jni_trampoline
213     //   art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)
214     //
215     // Simpleperf also relies on this frame name, so it should be fairly stable.
216     // TODO(rsavitski): consider detecting standard JNI upcall entrypoints -
217     // _JNIEnv::Call*. These are sometimes inlined into other DSOs, so erasing
218     // only the libart frames does not clean up all of the JNI-related frames.
219     StringId fname_id = f_table.name()[frame_row];
220     if (fname_id == art_jni_trampoline) {
221       annotations_reversed.push_back(common_frame);
222       annotation_state = State::kKeepNext;
223       continue;
224     }
225 
226     NullTermStringView map_view =
227         context_->storage->GetString(m_table.name()[map_row]);
228     MapType map_type = ClassifyMap(map_view);
229 
230     // Annotate managed frames.
231     if (map_type == MapType::kArtInterp ||  //
232         map_type == MapType::kArtJit ||     //
233         map_type == MapType::kArtAot) {
234       if (map_type == MapType::kArtInterp)
235         annotations_reversed.push_back(art_interp);
236       else if (map_type == MapType::kArtJit)
237         annotations_reversed.push_back(art_jit);
238       else if (map_type == MapType::kArtAot)
239         annotations_reversed.push_back(art_aot);
240 
241       // Now know to be in a managed callstack - erase subsequent ART frames.
242       if (annotation_state == State::kInitial)
243         annotation_state = State::kEraseLibart;
244       continue;
245     }
246 
247     if (annotation_state == State::kEraseLibart &&
248         map_type == MapType::kNativeLibart) {
249       annotations_reversed.push_back(common_frame);
250       continue;
251     }
252 
253     annotations_reversed.push_back(kNullStringId);
254   }
255 
256   // Build the dynamic table.
257   auto base_rowmap = RowMap(std::move(cs_rows));
258 
259   PERFETTO_DCHECK(base_rowmap.size() == annotations_reversed.size());
260   std::unique_ptr<NullableVector<StringPool::Id>> annotation_vals(
261       new NullableVector<StringPool::Id>());
262   for (auto it = annotations_reversed.rbegin();
263        it != annotations_reversed.rend(); ++it) {
264     annotation_vals->Append(*it);
265   }
266 
267   // Hidden column - always the input, i.e. the callsite leaf.
268   std::unique_ptr<NullableVector<uint32_t>> start_id_vals(
269       new NullableVector<uint32_t>());
270   for (uint32_t i = 0; i < base_rowmap.size(); i++)
271     start_id_vals->Append(start_id);
272 
273   table_return.reset(new Table(
274       cs_table.Apply(std::move(base_rowmap))
275           .ExtendWithColumn("annotation", std::move(annotation_vals),
276                             TypedColumn<StringPool::Id>::default_flags())
277           .ExtendWithColumn("start_id", std::move(start_id_vals),
278                             TypedColumn<uint32_t>::default_flags() |
279                                 TypedColumn<uint32_t>::kHidden)));
280   return base::OkStatus();
281 }
282 
EstimateRowCount()283 uint32_t ExperimentalAnnotatedStackGenerator::EstimateRowCount() {
284   return 1;
285 }
286 
287 }  // namespace trace_processor
288 }  // namespace perfetto
289