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 #include "src/trace_processor/importers/proto/content_analyzer.h"
18
19 #include <cstdint>
20 #include <optional>
21 #include <string>
22
23 #include "perfetto/ext/base/flat_hash_map.h"
24 #include "perfetto/ext/base/string_view.h"
25 #include "perfetto/trace_processor/trace_blob_view.h"
26 #include "src/trace_processor/importers/common/args_tracker.h"
27 #include "src/trace_processor/storage/trace_storage.h"
28 #include "src/trace_processor/tables/trace_proto_tables_py.h"
29 #include "src/trace_processor/types/trace_processor_context.h"
30 #include "src/trace_processor/types/variadic.h"
31 #include "src/trace_processor/util/proto_profiler.h"
32
33 namespace perfetto::trace_processor {
34
ProtoContentAnalyzer(TraceProcessorContext * context)35 ProtoContentAnalyzer::ProtoContentAnalyzer(TraceProcessorContext* context)
36 : context_(context),
37 computer_(context_->descriptor_pool_.get(),
38 ".perfetto.protos.TracePacket") {}
39
40 ProtoContentAnalyzer::~ProtoContentAnalyzer() = default;
41
ProcessPacket(const TraceBlobView & packet,const SampleAnnotation & packet_annotations)42 void ProtoContentAnalyzer::ProcessPacket(
43 const TraceBlobView& packet,
44 const SampleAnnotation& packet_annotations) {
45 auto& map = aggregated_samples_[packet_annotations];
46 computer_.Reset(packet.data(), packet.length());
47 for (auto sample = computer_.GetNext(); sample.has_value();
48 sample = computer_.GetNext()) {
49 auto* value = map.Find(computer_.GetPath());
50 if (value) {
51 value->size += *sample;
52 ++value->count;
53 } else {
54 map.Insert(computer_.GetPath(), Sample{*sample, 1});
55 }
56 }
57 }
58
NotifyEndOfFile()59 void ProtoContentAnalyzer::NotifyEndOfFile() {
60 // TODO(kraskevich): consider generating a flamegraph-compatable table once
61 // Perfetto UI supports custom flamegraphs (b/227644078).
62 for (auto annotated_map = aggregated_samples_.GetIterator(); annotated_map;
63 ++annotated_map) {
64 base::FlatHashMap<util::SizeProfileComputer::FieldPath,
65 tables::ExperimentalProtoPathTable::Id,
66 util::SizeProfileComputer::FieldPathHasher>
67 path_ids;
68 for (auto sample = annotated_map.value().GetIterator(); sample; ++sample) {
69 std::string path_string;
70 std::optional<tables::ExperimentalProtoPathTable::Id> previous_path_id;
71 util::SizeProfileComputer::FieldPath path;
72 for (const auto& field : sample.key()) {
73 if (field.has_field_name()) {
74 if (!path_string.empty()) {
75 path_string += '.';
76 }
77 path_string.append(field.field_name());
78 }
79 if (!path_string.empty()) {
80 path_string += '.';
81 }
82 path_string.append(field.type_name());
83
84 path.push_back(field);
85 // Reuses existing path from |path_ids| if possible.
86 {
87 auto* path_id = path_ids.Find(path);
88 if (path_id) {
89 previous_path_id = *path_id;
90 continue;
91 }
92 }
93 // Create a new row in experimental_proto_path.
94 tables::ExperimentalProtoPathTable::Row path_row;
95 if (field.has_field_name()) {
96 path_row.field_name = context_->storage->InternString(
97 base::StringView(field.field_name()));
98 }
99 path_row.field_type = context_->storage->InternString(
100 base::StringView(field.type_name()));
101 if (previous_path_id.has_value())
102 path_row.parent_id = *previous_path_id;
103
104 auto path_id =
105 context_->storage->mutable_experimental_proto_path_table()
106 ->Insert(path_row)
107 .id;
108 if (!previous_path_id.has_value()) {
109 // Add annotations to the current row as an args set.
110 auto inserter = context_->args_tracker->AddArgsTo(path_id);
111 for (auto& annotation : annotated_map.key()) {
112 inserter.AddArg(annotation.first,
113 Variadic::String(annotation.second));
114 }
115 }
116 previous_path_id = path_id;
117 path_ids[path] = path_id;
118 }
119
120 // Add a content row referring to |previous_path_id|.
121 tables::ExperimentalProtoContentTable::Row content_row;
122 content_row.path =
123 context_->storage->InternString(base::StringView(path_string));
124 content_row.path_id = *previous_path_id;
125 content_row.total_size = static_cast<int64_t>(sample.value().size);
126 content_row.size = static_cast<int64_t>(sample.value().size);
127 content_row.count = static_cast<int64_t>(sample.value().count);
128 context_->storage->mutable_experimental_proto_content_table()->Insert(
129 content_row);
130 }
131 }
132 aggregated_samples_.Clear();
133 }
134
135 } // namespace perfetto::trace_processor
136