xref: /aosp_15_r20/external/perfetto/src/tools/protoprofile/main.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2019 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 <algorithm>
18 #include <vector>
19 
20 #include "perfetto/ext/base/file_utils.h"
21 #include "perfetto/ext/base/flat_hash_map.h"
22 #include "perfetto/ext/base/scoped_file.h"
23 #include "perfetto/protozero/field.h"
24 #include "perfetto/protozero/packed_repeated_fields.h"
25 #include "perfetto/protozero/proto_decoder.h"
26 #include "perfetto/protozero/proto_utils.h"
27 #include "perfetto/protozero/scattered_heap_buffer.h"
28 #include "src/trace_processor/importers/proto/trace.descriptor.h"
29 #include "src/trace_processor/util/proto_profiler.h"
30 
31 #include "protos/third_party/pprof/profile.pbzero.h"
32 
33 namespace perfetto {
34 namespace protoprofile {
35 namespace {
36 
37 class PprofProfileComputer {
38  public:
39   std::string Compute(const uint8_t* ptr,
40                       size_t size,
41                       const std::string& message_type,
42                       trace_processor::DescriptorPool* pool);
43 
44  private:
45   int InternString(const std::string& str);
46   int InternLocation(const std::string& str);
47 
48   // Interned strings:
49   std::vector<std::string> strings_;
50   base::FlatHashMap<std::string, int> string_to_id_;
51 
52   // Interned 'locations', each location is a single frame of the stack.
53   base::FlatHashMap<std::string, int> locations_;
54 };
55 
InternString(const std::string & s)56 int PprofProfileComputer::InternString(const std::string& s) {
57   auto val = string_to_id_.Find(s);
58   if (val) {
59     return *val;
60   }
61   strings_.push_back(s);
62   int id = static_cast<int>(strings_.size() - 1);
63   string_to_id_[s] = id;
64   return id;
65 }
66 
InternLocation(const std::string & s)67 int PprofProfileComputer::InternLocation(const std::string& s) {
68   auto val = locations_.Find(s);
69   if (val) {
70     return *val;
71   }
72   int id = static_cast<int>(locations_.size()) + 1;
73   locations_[s] = id;
74   return id;
75 }
76 
Compute(const uint8_t * ptr,size_t size,const std::string & message_type,trace_processor::DescriptorPool * pool)77 std::string PprofProfileComputer::Compute(
78     const uint8_t* ptr,
79     size_t size,
80     const std::string& message_type,
81     trace_processor::DescriptorPool* pool) {
82   PERFETTO_CHECK(InternString("") == 0);
83 
84   trace_processor::util::SizeProfileComputer computer(pool, message_type);
85   computer.Reset(ptr, size);
86 
87   using PathToSamplesMap = std::unordered_map<
88       trace_processor::util::SizeProfileComputer::FieldPath,
89       std::vector<size_t>,
90       trace_processor::util::SizeProfileComputer::FieldPathHasher>;
91   PathToSamplesMap field_path_to_samples;
92   for (auto sample = computer.GetNext(); sample; sample = computer.GetNext()) {
93     field_path_to_samples[computer.GetPath()].push_back(*sample);
94   }
95 
96   protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>
97       profile;
98 
99   auto* sample_type = profile->add_sample_type();
100   sample_type->set_type(InternString("protos"));
101   sample_type->set_unit(InternString("count"));
102 
103   sample_type = profile->add_sample_type();
104   sample_type->set_type(InternString("max_size"));
105   sample_type->set_unit(InternString("bytes"));
106 
107   sample_type = profile->add_sample_type();
108   sample_type->set_type(InternString("min_size"));
109   sample_type->set_unit(InternString("bytes"));
110 
111   sample_type = profile->add_sample_type();
112   sample_type->set_type(InternString("median"));
113   sample_type->set_unit(InternString("bytes"));
114 
115   sample_type = profile->add_sample_type();
116   sample_type->set_type(InternString("total_size"));
117   sample_type->set_unit(InternString("bytes"));
118 
119   // For each unique field path we've seen write out the stats:
120   for (auto& entry : field_path_to_samples) {
121     std::vector<std::string> field_path;
122     for (const auto& field : entry.first) {
123       if (field.has_field_name())
124         field_path.push_back(field.field_name());
125       field_path.push_back(field.type_name());
126     }
127     std::vector<size_t>& samples = entry.second;
128 
129     protozero::PackedVarInt location_ids;
130     auto* sample = profile->add_sample();
131     for (auto loc_it = field_path.rbegin(); loc_it != field_path.rend();
132          ++loc_it) {
133       location_ids.Append(InternLocation(*loc_it));
134     }
135     sample->set_location_id(location_ids);
136 
137     std::sort(samples.begin(), samples.end());
138     size_t count = samples.size();
139     size_t total_size = 0;
140     size_t max_size = samples[count - 1];
141     size_t min_size = samples[0];
142     size_t median_size = samples[count / 2];
143     for (size_t i = 0; i < count; ++i)
144       total_size += samples[i];
145     // These have to be in the same order as the sample types above:
146     protozero::PackedVarInt values;
147     values.Append(static_cast<int64_t>(count));
148     values.Append(static_cast<int64_t>(max_size));
149     values.Append(static_cast<int64_t>(min_size));
150     values.Append(static_cast<int64_t>(median_size));
151     values.Append(static_cast<int64_t>(total_size));
152     sample->set_value(values);
153   }
154 
155   // The proto profile has a two step mapping where samples are associated with
156   // locations which in turn are associated to functions. We don't currently
157   // distinguish them so we make a 1:1 mapping between the locations and the
158   // functions:
159   for (auto it = locations_.GetIterator(); it; ++it) {
160     auto* location = profile->add_location();
161     location->set_id(static_cast<uint64_t>(it.value()));
162 
163     auto* line = location->add_line();
164     line->set_function_id(static_cast<uint64_t>(it.value()));
165 
166     auto* function = profile->add_function();
167     function->set_id(static_cast<uint64_t>(it.value()));
168     function->set_name(InternString(it.key()));
169   }
170   // Finally the string table. We intern more strings above, so this has to be
171   // last.
172   for (size_t i = 0; i < strings_.size(); i++) {
173     profile->add_string_table(strings_[i]);
174   }
175   return profile.SerializeAsString();
176 }
177 
PrintUsage(int,const char ** argv)178 int PrintUsage(int, const char** argv) {
179   fprintf(stderr, "Usage: %s INPUT_PATH OUTPUT_PATH\n", argv[0]);
180   return 1;
181 }
182 
Main(int argc,const char ** argv)183 int Main(int argc, const char** argv) {
184   if (argc != 3)
185     return PrintUsage(argc, argv);
186 
187   const char* input_path = argv[1];
188   const char* output_path = argv[2];
189 
190   base::ScopedFile proto_fd = base::OpenFile(input_path, O_RDONLY);
191   if (!proto_fd) {
192     PERFETTO_ELOG("Could not open input path (%s)", input_path);
193     return 1;
194   }
195 
196   std::string s;
197   base::ReadFileDescriptor(proto_fd.get(), &s);
198 
199   trace_processor::DescriptorPool pool;
200   base::Status status = pool.AddFromFileDescriptorSet(kTraceDescriptor.data(),
201                                                       kTraceDescriptor.size());
202   if (!status.ok()) {
203     PERFETTO_ELOG("Could not add Trace proto descriptor: %s",
204                   status.c_message());
205     return 1;
206   }
207 
208   const uint8_t* start = reinterpret_cast<const uint8_t*>(s.data());
209   size_t size = s.size();
210 
211   base::ScopedFile output_fd =
212       base::OpenFile(output_path, O_WRONLY | O_TRUNC | O_CREAT, 0600);
213   if (!output_fd) {
214     PERFETTO_ELOG("Could not open output path (%s)", output_path);
215     return 1;
216   }
217   PprofProfileComputer computer;
218   std::string out =
219       computer.Compute(start, size, ".perfetto.protos.Trace", &pool);
220   base::WriteAll(output_fd.get(), out.data(), out.size());
221   base::FlushFile(output_fd.get());
222 
223   return 0;
224 }
225 
226 }  // namespace
227 }  // namespace protoprofile
228 }  // namespace perfetto
229 
main(int argc,const char ** argv)230 int main(int argc, const char** argv) {
231   return perfetto::protoprofile::Main(argc, argv);
232 }
233