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