xref: /aosp_15_r20/external/perfetto/src/trace_processor/util/profile_builder.h (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2022 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 #ifndef SRC_TRACE_PROCESSOR_UTIL_PROFILE_BUILDER_H_
18 #define SRC_TRACE_PROCESSOR_UTIL_PROFILE_BUILDER_H_
19 
20 #include <optional>
21 
22 #include "perfetto/ext/base/flat_hash_map.h"
23 #include "perfetto/ext/base/string_view.h"
24 #include "perfetto/protozero/packed_repeated_fields.h"
25 #include "perfetto/protozero/scattered_heap_buffer.h"
26 #include "protos/perfetto/trace_processor/stack.pbzero.h"
27 #include "protos/third_party/pprof/profile.pbzero.h"
28 #include "src/trace_processor/containers/string_pool.h"
29 #include "src/trace_processor/storage/trace_storage.h"
30 #include "src/trace_processor/tables/profiler_tables_py.h"
31 #include "src/trace_processor/util/annotated_callsites.h"
32 
33 #include <algorithm>
34 #include <cstdint>
35 #include <functional>
36 #include <unordered_map>
37 #include <vector>
38 
39 namespace perfetto {
40 namespace trace_processor {
41 
42 class TraceProcessorContext;
43 
44 // Builds a |perftools.profiles.Profile| proto.
45 class GProfileBuilder {
46  public:
47   struct ValueType {
48     std::string type;
49     std::string unit;
50   };
51 
52   // |sample_types| A description of the values stored with each sample.
53   // |annotated| Whether to annotate callstack frames.
54   //
55   // Important: Annotations might interfere with certain aggregations, as we
56   // will could have a frame that is annotated with different annotations. That
57   // will lead to multiple functions being generated (sane name, line etc, but
58   // different annotation). Since there is no field in a Profile proto to track
59   // these annotations we extend the function name (my_func [annotation]), so
60   // from pprof perspective we now have different functions. So in flame graphs
61   // for example you will have one separate slice for each of these same
62   // functions with different annotations.
63   GProfileBuilder(const TraceProcessorContext* context,
64                   const std::vector<ValueType>& sample_types);
65   ~GProfileBuilder();
66 
67   // Returns false if the operation fails (e.g callsite_id was not found)
68   bool AddSample(const protos::pbzero::Stack_Decoder& stack,
69                  const std::vector<int64_t>& values);
70 
71   // Finalizes the profile and returns the serialized proto. Can be called
72   // multiple times but after the first invocation `AddSample` calls will have
73   // no effect.
74   std::string Build();
75 
76  private:
77   static constexpr int64_t kEmptyStringIndex = 0;
78   static constexpr uint64_t kNullFunctionId = 0;
79 
80   // Strings are stored in the `Profile` in a table and referenced by their
81   // index. This helper class takes care of all the book keeping.
82   // `TraceProcessor` uses its own `StringPool` for strings. This helper
83   // provides convenient ways of dealing with `StringPool::Id` values instead of
84   // actual string. This class ensures that two equal strings will have the same
85   // index, so you can compare them instead of the actual strings.
86   class StringTable {
87    public:
88     // |result| This is the `Profile` proto we are building. Strings will be
89     // added to it as necessary. |string_pool| `StringPool` to quey for strings
90     // passed as `StringPool:Id`
91     StringTable(protozero::HeapBuffered<
92                     third_party::perftools::profiles::pbzero::Profile>* result,
93                 const StringPool* string_pool);
94 
95     // Adds the given string to the table, if not currently present, and returns
96     // the index to it. Might write data to the infligt `Profile` so it should
97     // not be called while in the middle of writing a message to the proto.
98     int64_t InternString(base::StringView str);
99     // Adds a string stored in the `TraceProcessor` `StringPool` to the table,
100     // if not currently present, and returns the index to it. Might write data
101     // to the inflight `Profile` so it should not be called while in the middle
102     // of writing a message to the proto.
103     int64_t InternString(StringPool::Id id);
104 
105     int64_t GetAnnotatedString(StringPool::Id str,
106                                CallsiteAnnotation annotation);
107     int64_t GetAnnotatedString(base::StringView str,
108                                CallsiteAnnotation annotation);
109 
110    private:
111     // Unconditionally writes the given string to the table and returns its
112     // index.
113     int64_t WriteString(base::StringView str);
114 
115     const StringPool& string_pool_;
116     protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>&
117         result_;
118 
119     std::unordered_map<StringPool::Id, int64_t> seen_string_pool_ids_;
120     // Maps strings (hashes thereof) to indexes in the table.
121     std::unordered_map<uint64_t, int64_t> seen_strings_;
122     // Index where the next string will be written to
123     int64_t next_index_{0};
124   };
125 
126   struct AnnotatedFrameId {
127     struct Hash {
operatorAnnotatedFrameId::Hash128       size_t operator()(const AnnotatedFrameId& id) const {
129         return static_cast<size_t>(perfetto::base::Hasher::Combine(
130             id.frame_id.value, static_cast<int>(id.annotation)));
131       }
132     };
133 
134     FrameId frame_id;
135     CallsiteAnnotation annotation;
136 
137     bool operator==(const AnnotatedFrameId& other) const {
138       return frame_id == other.frame_id && annotation == other.annotation;
139     }
140   };
141 
142   struct Line {
143     uint64_t function_id;
144     int64_t line;
145     bool operator==(const Line& other) const {
146       return function_id == other.function_id && line == other.line;
147     }
148   };
149 
150   // Location, MappingKey, Mapping, Function, and Line are helper structs to
151   // deduplicate entities. We do not write these directly to the proto Profile
152   // but instead stage them and write them out during `Finalize`. Samples on the
153   // other hand are directly written to the proto.
154 
155   struct Location {
156     struct Hash {
operatorLocation::Hash157       size_t operator()(const Location& loc) const {
158         perfetto::base::Hasher hasher;
159         hasher.UpdateAll(loc.mapping_id, loc.rel_pc, loc.lines.size());
160         for (const auto& line : loc.lines) {
161           hasher.UpdateAll(line.function_id, line.line);
162         }
163         return static_cast<size_t>(hasher.digest());
164       }
165     };
166 
167     uint64_t mapping_id;
168     uint64_t rel_pc;
169     std::vector<Line> lines;
170 
171     bool operator==(const Location& other) const {
172       return mapping_id == other.mapping_id && rel_pc == other.rel_pc &&
173              lines == other.lines;
174     }
175   };
176 
177   // Mappings are tricky. We could have samples for different processes and
178   // given address space layout randomization the same mapping could be located
179   // at different addresses. MappingKey has the set of properties that uniquely
180   // identify mapping in order to deduplicate rows in the stack_profile_mapping
181   // table.
182   struct MappingKey {
183     struct Hash {
operatorMappingKey::Hash184       size_t operator()(const MappingKey& mapping) const {
185         perfetto::base::Hasher hasher;
186         hasher.UpdateAll(mapping.size, mapping.file_offset,
187                          mapping.build_id_or_filename);
188         return static_cast<size_t>(hasher.digest());
189       }
190     };
191 
192     explicit MappingKey(
193         const tables::StackProfileMappingTable::ConstRowReference& mapping,
194         StringTable& string_table);
195 
196     bool operator==(const MappingKey& other) const {
197       return size == other.size && file_offset == other.file_offset &&
198              build_id_or_filename == other.build_id_or_filename;
199     }
200 
201     uint64_t size;
202     uint64_t file_offset;
203     int64_t build_id_or_filename;
204   };
205 
206   // Keeps track of what debug information is available for a mapping.
207   // TODO(carlscab): We could be a bit more "clever" here. Currently if there is
208   // debug info for at least one frame we flag the mapping as having debug info.
209   // We could use some heuristic instead, e.g. if x% for frames have the info
210   // etc.
211   struct DebugInfo {
212     bool has_functions{false};
213     bool has_filenames{false};
214     bool has_line_numbers{false};
215     bool has_inline_frames{false};
216   };
217 
218   struct Mapping {
219     explicit Mapping(
220         const tables::StackProfileMappingTable::ConstRowReference& mapping,
221         const StringPool& string_pool,
222         StringTable& string_table);
223 
224     // Heuristic to determine if this maps to the main binary. Bigger scores
225     // mean higher likelihood.
226     int64_t ComputeMainBinaryScore() const;
227 
228     const uint64_t memory_start;
229     const uint64_t memory_limit;
230     const uint64_t file_offset;
231     const int64_t filename;
232     const int64_t build_id;
233 
234     const std::string filename_str;
235 
236     DebugInfo debug_info;
237   };
238 
239   struct Function {
240     struct Hash {
operatorFunction::Hash241       size_t operator()(const Function& func) const {
242         return static_cast<size_t>(perfetto::base::Hasher::Combine(
243             func.name, func.system_name, func.filename));
244       }
245     };
246 
247     int64_t name;
248     int64_t system_name;
249     int64_t filename;
250 
251     bool operator==(const Function& other) const {
252       return name == other.name && system_name == other.system_name &&
253              filename == other.filename;
254     }
255   };
256 
257   // Aggregates samples with the same location_ids (i.e. stack) by computing the
258   // sum of their values. This helps keep the generated profiles small as it
259   // potentially removes a lot of duplication from having multiple samples with
260   // the same stack.
261   class SampleAggregator {
262    public:
263     bool AddSample(const protozero::PackedVarInt& location_ids,
264                    const std::vector<int64_t>& values);
265 
266     void WriteTo(third_party::perftools::profiles::pbzero::Profile& profile);
267 
268    private:
269     // Key holds the serialized value of the Sample::location_id proto field
270     // (packed varint).
271     using SerializedLocationId = std::vector<uint8_t>;
272     struct Hasher {
operatorHasher273       size_t operator()(const SerializedLocationId& data) const {
274         base::Hasher hasher;
275         hasher.Update(reinterpret_cast<const char*>(data.data()), data.size());
276         return static_cast<size_t>(hasher.digest());
277       }
278     };
279     base::FlatHashMap<SerializedLocationId, std::vector<int64_t>, Hasher>
280         samples_;
281   };
282 
283   const protozero::PackedVarInt& GetLocationIdsForCallsite(
284       const CallsiteId& callsite_id,
285       bool annotated);
286 
287   std::vector<Line> GetLinesForSymbolSetId(
288       std::optional<uint32_t> symbol_set_id,
289       CallsiteAnnotation annotation,
290       uint64_t mapping_id);
291 
292   std::vector<Line> GetLines(
293       const tables::StackProfileFrameTable::ConstRowReference& frame,
294       CallsiteAnnotation annotation,
295       uint64_t mapping_id);
296 
297   int64_t GetNameForFrame(
298       const tables::StackProfileFrameTable::ConstRowReference& frame,
299       CallsiteAnnotation annotation);
300 
301   int64_t GetSystemNameForFrame(
302       const tables::StackProfileFrameTable::ConstRowReference& frame);
303 
304   uint64_t WriteLocationIfNeeded(FrameId frame_id,
305                                  CallsiteAnnotation annotation);
306   uint64_t WriteFakeLocationIfNeeded(const std::string& name);
307 
308   uint64_t WriteFunctionIfNeeded(
309       const tables::SymbolTable::ConstRowReference& symbol,
310       CallsiteAnnotation annotation,
311       uint64_t mapping_id);
312 
313   uint64_t WriteFunctionIfNeeded(
314       const tables::StackProfileFrameTable::ConstRowReference& frame,
315       CallsiteAnnotation annotation,
316       uint64_t mapping_id);
317 
318   uint64_t WriteFakeFunctionIfNeeded(int64_t name_id);
319 
320   uint64_t WriteMappingIfNeeded(
321       const tables::StackProfileMappingTable::ConstRowReference& mapping);
322   void WriteMappings();
323   void WriteMapping(uint64_t mapping_id);
324   void WriteFunctions();
325   void WriteLocations();
326 
327   void WriteSampleTypes(const std::vector<ValueType>& sample_types);
328 
329   void Finalize();
330 
GetMapping(uint64_t mapping_id)331   Mapping& GetMapping(uint64_t mapping_id) {
332     return mappings_[static_cast<size_t>(mapping_id - 1)];
333   }
334 
335   // Goes over the list of staged mappings and tries to determine which is the
336   // most likely main binary.
337   std::optional<uint64_t> GuessMainBinary() const;
338 
339   // Profile proto being serialized.
340   protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>
341       result_;
342 
343   const TraceProcessorContext& context_;
344   StringTable string_table_;
345 
346   bool finalized_{false};
347   AnnotatedCallsites annotations_;
348 
349   // Caches a (possibly annotated) CallsiteId (callstack) to the list of
350   // locations emitted to the profile.
351   struct MaybeAnnotatedCallsiteId {
352     struct Hash {
operatorMaybeAnnotatedCallsiteId::Hash353       size_t operator()(const MaybeAnnotatedCallsiteId& id) const {
354         return static_cast<size_t>(
355             perfetto::base::Hasher::Combine(id.callsite_id.value, id.annotate));
356       }
357     };
358 
359     CallsiteId callsite_id;
360     bool annotate;
361 
362     bool operator==(const MaybeAnnotatedCallsiteId& other) const {
363       return callsite_id == other.callsite_id && annotate == other.annotate;
364     }
365   };
366   std::unordered_map<MaybeAnnotatedCallsiteId,
367                      protozero::PackedVarInt,
368                      MaybeAnnotatedCallsiteId::Hash>
369       cached_location_ids_;
370 
371   // Helpers to map TraceProcessor rows to already written Profile entities
372   // (their ids).
373   std::unordered_map<AnnotatedFrameId, uint64_t, AnnotatedFrameId::Hash>
374       seen_locations_;
375   std::unordered_map<AnnotatedFrameId, uint64_t, AnnotatedFrameId::Hash>
376       seen_functions_;
377   std::unordered_map<MappingId, uint64_t> seen_mappings_;
378   std::unordered_map<int64_t, uint64_t> seen_fake_locations_;
379 
380   // Helpers to deduplicate entries. Map entity to its id. These also serve as a
381   // staging area until written out to the profile proto during `Finalize`. Ids
382   // are consecutive integers starting at 1. (Ids with value 0 are not allowed).
383   // Ids are not unique across entities (i.e. there can be a mapping_id = 1 and
384   // a function_id = 1)
385   std::unordered_map<Location, uint64_t, Location::Hash> locations_;
386   std::unordered_map<MappingKey, uint64_t, MappingKey::Hash> mapping_keys_;
387   std::unordered_map<Function, uint64_t, Function::Hash> functions_;
388   // Staging area for Mappings. mapping_id - 1 = index in the vector.
389   std::vector<Mapping> mappings_;
390   SampleAggregator samples_;
391 };
392 
393 }  // namespace trace_processor
394 }  // namespace perfetto
395 
396 #endif  // SRC_TRACE_PROCESSOR_UTIL_PROFILE_BUILDER_H_
397