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