1 #include "src/trace_processor/util/protozero_to_text.h"
2 
3 #include "perfetto/ext/base/string_utils.h"
4 #include "perfetto/ext/base/string_view.h"
5 #include "perfetto/protozero/proto_decoder.h"
6 #include "perfetto/protozero/proto_utils.h"
7 #include "protos/perfetto/common/descriptor.pbzero.h"
8 #include "src/trace_processor/util/descriptors.h"
9 
10 // This is the highest level that this protozero to text supports.
11 #include "src/trace_processor/importers/track_event.descriptor.h"
12 
13 namespace perfetto {
14 namespace trace_processor {
15 namespace protozero_to_text {
16 
17 namespace {
18 
BytesToHexEncodedString(const std::string & bytes)19 std::string BytesToHexEncodedString(const std::string& bytes) {
20   // Each byte becomes four chars 'A' -> "\x41" + 1 for trailing null.
21   std::string value(4 * bytes.size() + 1, 'Z');
22   for (size_t i = 0; i < bytes.size(); ++i) {
23     // snprintf prints 5 characters: '\x', then two hex digits, and finally a
24     // null byte. As we write left to right, we keep overwriting the null
25     // byte, except for the last call to snprintf.
26     snprintf(&(value[4 * i]), 5, "\\x%02hhx", bytes[i]);
27   }
28   // Trim trailing null.
29   value.resize(4 * bytes.size());
30   return value;
31 }
32 
33 // Recursively determine the size of all the string like things passed in the
34 // parameter pack |rest|.
SizeOfStr()35 size_t SizeOfStr() {
36   return 0;
37 }
38 template <typename T, typename... Rest>
SizeOfStr(const T & first,Rest...rest)39 size_t SizeOfStr(const T& first, Rest... rest) {
40   return base::StringView(first).size() + SizeOfStr(rest...);
41 }
42 
43 // Append |to_add| which is something string like to |out|.
44 template <typename T>
StrAppendInternal(std::string * out,const T & to_add)45 void StrAppendInternal(std::string* out, const T& to_add) {
46   out->append(to_add);
47 }
48 
49 template <typename T, typename... strings>
StrAppendInternal(std::string * out,const T & first,strings...values)50 void StrAppendInternal(std::string* out, const T& first, strings... values) {
51   StrAppendInternal(out, first);
52   StrAppendInternal(out, values...);
53 }
54 
55 // Append |to_add| which is something string like to |out|.
56 template <typename T>
StrAppend(std::string * out,const T & to_add)57 void StrAppend(std::string* out, const T& to_add) {
58   out->reserve(out->size() + base::StringView(to_add).size());
59   out->append(to_add);
60 }
61 
62 template <typename T, typename... strings>
StrAppend(std::string * out,const T & first,strings...values)63 void StrAppend(std::string* out, const T& first, strings... values) {
64   out->reserve(out->size() + SizeOfStr(values...));
65   StrAppendInternal(out, first);
66   StrAppendInternal(out, values...);
67 }
68 
ConvertProtoTypeToFieldAndValueString(const FieldDescriptor & fd,const protozero::Field & field,const std::string & separator,const std::string & indent,const DescriptorPool & pool,std::string * out)69 void ConvertProtoTypeToFieldAndValueString(const FieldDescriptor& fd,
70                                            const protozero::Field& field,
71                                            const std::string& separator,
72                                            const std::string& indent,
73                                            const DescriptorPool& pool,
74                                            std::string* out) {
75   using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
76   switch (fd.type()) {
77     case FieldDescriptorProto::TYPE_INT32:
78     case FieldDescriptorProto::TYPE_SFIXED32:
79     case FieldDescriptorProto::TYPE_FIXED32:
80       StrAppend(out, separator, indent, fd.name(), ": ",
81                 std::to_string(field.as_int32()));
82       return;
83     case FieldDescriptorProto::TYPE_SINT32:
84       StrAppend(out, separator, indent, fd.name(), ": ",
85                 std::to_string(field.as_sint32()));
86       return;
87     case FieldDescriptorProto::TYPE_INT64:
88     case FieldDescriptorProto::TYPE_SFIXED64:
89     case FieldDescriptorProto::TYPE_FIXED64:
90       StrAppend(out, separator, indent, fd.name(), ": ",
91                 std::to_string(field.as_int64()));
92       return;
93     case FieldDescriptorProto::TYPE_SINT64:
94       StrAppend(out, separator, indent, fd.name(), ": ",
95                 std::to_string(field.as_sint64()));
96       return;
97     case FieldDescriptorProto::TYPE_UINT32:
98       StrAppend(out, separator, indent, fd.name(), ": ",
99                 std::to_string(field.as_uint32()));
100       return;
101     case FieldDescriptorProto::TYPE_UINT64:
102       StrAppend(out, separator, indent, fd.name(), ": ",
103                 std::to_string(field.as_uint64()));
104       return;
105     case FieldDescriptorProto::TYPE_BOOL:
106       StrAppend(out, separator, indent, fd.name(), ": ",
107                 field.as_bool() ? "true" : "false");
108       return;
109     case FieldDescriptorProto::TYPE_DOUBLE:
110       StrAppend(out, separator, indent, fd.name(), ": ",
111                 std::to_string(field.as_double()));
112       return;
113     case FieldDescriptorProto::TYPE_FLOAT:
114       StrAppend(out, separator, indent, fd.name(), ": ",
115                 std::to_string(field.as_float()));
116       return;
117     case FieldDescriptorProto::TYPE_STRING: {
118       auto s = base::QuoteAndEscapeControlCodes(field.as_std_string());
119       StrAppend(out, separator, indent, fd.name(), ": ", s);
120       return;
121     }
122     case FieldDescriptorProto::TYPE_BYTES: {
123       std::string value = BytesToHexEncodedString(field.as_std_string());
124       StrAppend(out, separator, indent, fd.name(), ": \"", value, "\"");
125       return;
126     }
127     case FieldDescriptorProto::TYPE_ENUM: {
128       auto opt_enum_descriptor_idx =
129           pool.FindDescriptorIdx(fd.resolved_type_name());
130       PERFETTO_DCHECK(opt_enum_descriptor_idx);
131       auto opt_enum_string =
132           pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(
133               field.as_int32());
134       PERFETTO_DCHECK(opt_enum_string);
135       StrAppend(out, separator, indent, fd.name(), ": ", *opt_enum_string);
136       return;
137     }
138     default: {
139       PERFETTO_FATAL(
140           "Tried to write value of type field %s (in proto type "
141           "%s) which has type enum %d",
142           fd.name().c_str(), fd.resolved_type_name().c_str(), fd.type());
143     }
144   }
145 }
146 
IncreaseIndents(std::string * out)147 void IncreaseIndents(std::string* out) {
148   StrAppend(out, "  ");
149 }
150 
DecreaseIndents(std::string * out)151 void DecreaseIndents(std::string* out) {
152   PERFETTO_DCHECK(out->size() >= 2);
153   out->erase(out->size() - 2);
154 }
155 
156 // Recursive case function, Will parse |protobytes| assuming it is a proto of
157 // |type| and will use |pool| to look up the |type|. All output will be placed
158 // in |output| and between fields |separator| will be placed. When called for
159 // |indents| will be increased by 2 spaces to improve readability.
ProtozeroToTextInternal(const std::string & type,protozero::ConstBytes protobytes,NewLinesMode new_lines_mode,const DescriptorPool & pool,std::string * indents,std::string * output)160 void ProtozeroToTextInternal(const std::string& type,
161                              protozero::ConstBytes protobytes,
162                              NewLinesMode new_lines_mode,
163                              const DescriptorPool& pool,
164                              std::string* indents,
165                              std::string* output) {
166   auto opt_proto_descriptor_idx = pool.FindDescriptorIdx(type);
167   PERFETTO_DCHECK(opt_proto_descriptor_idx);
168   auto& proto_descriptor = pool.descriptors()[*opt_proto_descriptor_idx];
169   bool include_new_lines = new_lines_mode == kIncludeNewLines;
170 
171   protozero::ProtoDecoder decoder(protobytes.data, protobytes.size);
172   for (auto field = decoder.ReadField(); field.valid();
173        field = decoder.ReadField()) {
174     auto opt_field_descriptor = proto_descriptor.FindFieldByTag(field.id());
175     if (!opt_field_descriptor) {
176       StrAppend(
177           output, output->empty() ? "" : "\n", *indents,
178           "# Ignoring unknown field with id: ", std::to_string(field.id()));
179       continue;
180     }
181     const auto& field_descriptor = *opt_field_descriptor;
182 
183     if (field_descriptor.type() ==
184         protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) {
185       if (include_new_lines) {
186         StrAppend(output, output->empty() ? "" : "\n", *indents,
187                   field_descriptor.name(), ": {");
188         IncreaseIndents(indents);
189       } else {
190         StrAppend(output, output->empty() ? "" : " ", field_descriptor.name(),
191                   ": {");
192       }
193       ProtozeroToTextInternal(field_descriptor.resolved_type_name(),
194                               field.as_bytes(), new_lines_mode, pool, indents,
195                               output);
196       if (include_new_lines) {
197         DecreaseIndents(indents);
198         StrAppend(output, "\n", *indents, "}");
199       } else {
200         StrAppend(output, " }");
201       }
202     } else {
203       ConvertProtoTypeToFieldAndValueString(
204           field_descriptor, field,
205           output->empty() ? "" : include_new_lines ? "\n" : " ", *indents, pool,
206           output);
207     }
208   }
209   PERFETTO_DCHECK(decoder.bytes_left() == 0);
210 }
211 
212 }  // namespace
213 
ProtozeroToText(const DescriptorPool & pool,const std::string & type,protozero::ConstBytes protobytes,NewLinesMode new_lines_mode)214 std::string ProtozeroToText(const DescriptorPool& pool,
215                             const std::string& type,
216                             protozero::ConstBytes protobytes,
217                             NewLinesMode new_lines_mode) {
218   std::string indent = "";
219   std::string final_result;
220   ProtozeroToTextInternal(type, protobytes, new_lines_mode, pool, &indent,
221                           &final_result);
222   return final_result;
223 }
224 
DebugTrackEventProtozeroToText(const std::string & type,protozero::ConstBytes protobytes)225 std::string DebugTrackEventProtozeroToText(const std::string& type,
226                                            protozero::ConstBytes protobytes) {
227   DescriptorPool pool;
228   auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
229                                               kTrackEventDescriptor.size());
230   PERFETTO_DCHECK(status.ok());
231   return ProtozeroToText(pool, type, protobytes, kIncludeNewLines);
232 }
233 
ShortDebugTrackEventProtozeroToText(const std::string & type,protozero::ConstBytes protobytes)234 std::string ShortDebugTrackEventProtozeroToText(
235     const std::string& type,
236     protozero::ConstBytes protobytes) {
237   DescriptorPool pool;
238   auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
239                                               kTrackEventDescriptor.size());
240   PERFETTO_DCHECK(status.ok());
241   return ProtozeroToText(pool, type, protobytes, kSkipNewLines);
242 }
243 
ProtozeroEnumToText(const std::string & type,int32_t enum_value)244 std::string ProtozeroEnumToText(const std::string& type, int32_t enum_value) {
245   DescriptorPool pool;
246   auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
247                                               kTrackEventDescriptor.size());
248   PERFETTO_DCHECK(status.ok());
249   auto opt_enum_descriptor_idx = pool.FindDescriptorIdx(type);
250   if (!opt_enum_descriptor_idx) {
251     // Fall back to the integer representation of the field.
252     return std::to_string(enum_value);
253   }
254   auto opt_enum_string =
255       pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(enum_value);
256   if (!opt_enum_string) {
257     // Fall back to the integer representation of the field.
258     return std::to_string(enum_value);
259   }
260   return *opt_enum_string;
261 }
262 
ProtozeroToText(const DescriptorPool & pool,const std::string & type,const std::vector<uint8_t> & protobytes,NewLinesMode new_lines_mode)263 std::string ProtozeroToText(const DescriptorPool& pool,
264                             const std::string& type,
265                             const std::vector<uint8_t>& protobytes,
266                             NewLinesMode new_lines_mode) {
267   return ProtozeroToText(
268       pool, type, protozero::ConstBytes{protobytes.data(), protobytes.size()},
269       new_lines_mode);
270 }
271 
BytesToHexEncodedStringForTesting(const std::string & s)272 std::string BytesToHexEncodedStringForTesting(const std::string& s) {
273   return BytesToHexEncodedString(s);
274 }
275 
276 }  // namespace protozero_to_text
277 }  // namespace trace_processor
278 }  // namespace perfetto
279