xref: /aosp_15_r20/external/perfetto/src/trace_processor/util/protozero_to_text.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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/util/protozero_to_text.h"
18 #include <optional>
19 
20 #include "perfetto/ext/base/string_utils.h"
21 #include "perfetto/ext/base/string_view.h"
22 #include "perfetto/protozero/proto_decoder.h"
23 #include "perfetto/protozero/proto_utils.h"
24 #include "protos/perfetto/common/descriptor.pbzero.h"
25 #include "src/trace_processor/util/descriptors.h"
26 
27 // This is the highest level that this protozero to text supports.
28 #include "src/trace_processor/importers/proto/track_event.descriptor.h"
29 
30 namespace perfetto {
31 namespace trace_processor {
32 namespace protozero_to_text {
33 
34 namespace {
35 
36 using protozero::proto_utils::ProtoWireType;
37 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
38 
39 // This function matches the implementation of TextFormatEscaper.escapeBytes
40 // from the Java protobuf library.
QuoteAndEscapeTextProtoString(base::StringView raw)41 std::string QuoteAndEscapeTextProtoString(base::StringView raw) {
42   std::string ret;
43   for (char c : raw) {
44     switch (c) {
45       case '\a':
46         ret += "\\a";
47         break;
48       case '\b':
49         ret += "\\b";
50         break;
51       case '\f':
52         ret += "\\f";
53         break;
54       case '\n':
55         ret += "\\n";
56         break;
57       case '\r':
58         ret += "\\r";
59         break;
60       case '\t':
61         ret += "\\t";
62         break;
63       case '\v':
64         ret += "\\v";
65         break;
66       case '\\':
67         ret += "\\\\";
68         break;
69       case '\'':
70         ret += "\\\'";
71         break;
72       case '"':
73         ret += "\\\"";
74         break;
75       default:
76         // Only ASCII characters between 0x20 (space) and 0x7e (tilde) are
77         // printable; other byte values are escaped with 3-character octal
78         // codes.
79         if (c >= 0x20 && c <= 0x7e) {
80           ret += c;
81         } else {
82           ret += '\\';
83 
84           // Cast to unsigned char to make the right shift unsigned as well.
85           unsigned char uc = static_cast<unsigned char>(c);
86           ret += ('0' + ((uc >> 6) & 3));
87           ret += ('0' + ((uc >> 3) & 7));
88           ret += ('0' + (uc & 7));
89         }
90         break;
91     }
92   }
93   return '"' + ret + '"';
94 }
95 
96 // Append |to_add| which is something string like to |out|.
97 template <typename T>
StrAppend(std::string * out,const T & to_add)98 void StrAppend(std::string* out, const T& to_add) {
99   out->append(to_add);
100 }
101 
102 template <typename T, typename... strings>
StrAppend(std::string * out,const T & first,strings...values)103 void StrAppend(std::string* out, const T& first, strings... values) {
104   StrAppend(out, first);
105   StrAppend(out, values...);
106 }
107 
IncreaseIndents(std::string * out)108 void IncreaseIndents(std::string* out) {
109   StrAppend(out, "  ");
110 }
111 
DecreaseIndents(std::string * out)112 void DecreaseIndents(std::string* out) {
113   PERFETTO_DCHECK(out->size() >= 2);
114   out->erase(out->size() - 2);
115 }
116 
PrintUnknownVarIntField(uint32_t id,int64_t value,std::string * out)117 void PrintUnknownVarIntField(uint32_t id, int64_t value, std::string* out) {
118   StrAppend(out, std::to_string(id), ": ", std::to_string(value));
119 }
120 
PrintEnumField(const FieldDescriptor & fd,const DescriptorPool & pool,uint32_t id,int32_t enum_value,std::string * out)121 void PrintEnumField(const FieldDescriptor& fd,
122                     const DescriptorPool& pool,
123                     uint32_t id,
124                     int32_t enum_value,
125                     std::string* out) {
126   auto opt_enum_descriptor_idx =
127       pool.FindDescriptorIdx(fd.resolved_type_name());
128   if (!opt_enum_descriptor_idx) {
129     PrintUnknownVarIntField(id, enum_value, out);
130     return;
131   }
132   auto opt_enum_string =
133       pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(enum_value);
134   // If the enum value is unknown, treat it like a completely unknown field.
135   if (!opt_enum_string) {
136     PrintUnknownVarIntField(id, enum_value, out);
137     return;
138   }
139   StrAppend(out, fd.name(), ": ", *opt_enum_string);
140 }
141 
FormattedFieldDescriptorName(const FieldDescriptor & field_descriptor)142 std::string FormattedFieldDescriptorName(
143     const FieldDescriptor& field_descriptor) {
144   if (field_descriptor.is_extension()) {
145     // Libprotobuf formatter always formats extension field names as fully
146     // qualified names.
147     // TODO(b/197625974): Assuming for now all our extensions will belong to the
148     // perfetto.protos package. Update this if we ever want to support extendees
149     // in different package.
150     return "[perfetto.protos." + field_descriptor.name() + "]";
151   } else {
152     return field_descriptor.name();
153   }
154 }
155 
PrintVarIntField(const FieldDescriptor * fd,const protozero::Field & field,const DescriptorPool & pool,std::string * out)156 void PrintVarIntField(const FieldDescriptor* fd,
157                       const protozero::Field& field,
158                       const DescriptorPool& pool,
159                       std::string* out) {
160   uint32_t type = fd ? fd->type() : 0;
161   switch (type) {
162     case FieldDescriptorProto::TYPE_INT32:
163       StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32()));
164       return;
165     case FieldDescriptorProto::TYPE_SINT32:
166       StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint32()));
167       return;
168     case FieldDescriptorProto::TYPE_UINT32:
169       StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32()));
170       return;
171     case FieldDescriptorProto::TYPE_INT64:
172       StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64()));
173       return;
174     case FieldDescriptorProto::TYPE_SINT64:
175       StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint64()));
176       return;
177     case FieldDescriptorProto::TYPE_UINT64:
178       StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64()));
179       return;
180     case FieldDescriptorProto::TYPE_BOOL:
181       StrAppend(out, fd->name(), ": ", field.as_bool() ? "true" : "false");
182       return;
183     case FieldDescriptorProto::TYPE_ENUM:
184       PrintEnumField(*fd, pool, field.id(), field.as_int32(), out);
185       return;
186     case 0:
187     default:
188       PrintUnknownVarIntField(field.id(), field.as_int64(), out);
189       return;
190   }
191 }
192 
PrintFixed32Field(const FieldDescriptor * fd,const protozero::Field & field,std::string * out)193 void PrintFixed32Field(const FieldDescriptor* fd,
194                        const protozero::Field& field,
195                        std::string* out) {
196   uint32_t type = fd ? fd->type() : 0;
197   switch (type) {
198     case FieldDescriptorProto::TYPE_SFIXED32:
199       StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32()));
200       break;
201     case FieldDescriptorProto::TYPE_FIXED32:
202       StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32()));
203       break;
204     case FieldDescriptorProto::TYPE_FLOAT:
205       StrAppend(out, fd->name(), ": ", std::to_string(field.as_float()));
206       break;
207     case 0:
208     default:
209       base::StackString<12> padded_hex("0x%08" PRIx32, field.as_uint32());
210       StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str());
211       break;
212   }
213 }
214 
PrintFixed64Field(const FieldDescriptor * fd,const protozero::Field & field,std::string * out)215 void PrintFixed64Field(const FieldDescriptor* fd,
216                        const protozero::Field& field,
217                        std::string* out) {
218   uint32_t type = fd ? fd->type() : 0;
219   switch (type) {
220     case FieldDescriptorProto::TYPE_SFIXED64:
221       StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64()));
222       break;
223     case FieldDescriptorProto::TYPE_FIXED64:
224       StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64()));
225       break;
226     case FieldDescriptorProto::TYPE_DOUBLE:
227       StrAppend(out, fd->name(), ": ", std::to_string(field.as_double()));
228       break;
229     case 0:
230     default:
231       base::StackString<20> padded_hex("0x%016" PRIx64, field.as_uint64());
232       StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str());
233       break;
234   }
235 }
236 
237 void ProtozeroToTextInternal(const std::string& type,
238                              protozero::ConstBytes protobytes,
239                              NewLinesMode new_lines_mode,
240                              const DescriptorPool& pool,
241                              std::string* indents,
242                              std::string* output);
243 
244 template <protozero::proto_utils::ProtoWireType wire_type, typename T>
PrintPackedField(const FieldDescriptor & fd,const protozero::Field & field,NewLinesMode new_lines_mode,const std::string & indents,const DescriptorPool & pool,std::string * out)245 void PrintPackedField(const FieldDescriptor& fd,
246                       const protozero::Field& field,
247                       NewLinesMode new_lines_mode,
248                       const std::string& indents,
249                       const DescriptorPool& pool,
250                       std::string* out) {
251   const bool include_new_lines = new_lines_mode == kIncludeNewLines;
252   bool err = false;
253   bool first_output = true;
254   for (protozero::PackedRepeatedFieldIterator<wire_type, T> it(
255            field.data(), field.size(), &err);
256        it; it++) {
257     T value = *it;
258     if (!first_output) {
259       if (include_new_lines) {
260         StrAppend(out, "\n", indents);
261       } else {
262         StrAppend(out, " ");
263       }
264     }
265     std::string serialized_value;
266     if (fd.type() == FieldDescriptorProto::TYPE_ENUM) {
267       PrintEnumField(fd, pool, field.id(), static_cast<int32_t>(value), out);
268     } else {
269       StrAppend(out, fd.name(), ": ", std::to_string(value));
270     }
271     first_output = false;
272   }
273 
274   if (err) {
275     if (!first_output) {
276       if (include_new_lines) {
277         StrAppend(out, "\n", indents);
278       } else {
279         StrAppend(out, " ");
280       }
281     }
282     StrAppend(out, "# Packed decoding failure for field ", fd.name(), "\n");
283   }
284 }
285 
PrintLengthDelimitedField(const FieldDescriptor * fd,const protozero::Field & field,NewLinesMode new_lines_mode,std::string * indents,const DescriptorPool & pool,std::string * out)286 void PrintLengthDelimitedField(const FieldDescriptor* fd,
287                                const protozero::Field& field,
288                                NewLinesMode new_lines_mode,
289                                std::string* indents,
290                                const DescriptorPool& pool,
291                                std::string* out) {
292   const bool include_new_lines = new_lines_mode == kIncludeNewLines;
293   uint32_t type = fd ? fd->type() : 0;
294   switch (type) {
295     case FieldDescriptorProto::TYPE_BYTES:
296     case FieldDescriptorProto::TYPE_STRING: {
297       std::string value = QuoteAndEscapeTextProtoString(field.as_string());
298       StrAppend(out, fd->name(), ": ", value);
299       return;
300     }
301     case FieldDescriptorProto::TYPE_MESSAGE:
302       StrAppend(out, FormattedFieldDescriptorName(*fd), " {");
303       if (include_new_lines) {
304         IncreaseIndents(indents);
305       }
306       ProtozeroToTextInternal(fd->resolved_type_name(), field.as_bytes(),
307                               new_lines_mode, pool, indents, out);
308       if (include_new_lines) {
309         DecreaseIndents(indents);
310         StrAppend(out, "\n", *indents, "}");
311       } else {
312         StrAppend(out, " }");
313       }
314       return;
315     case FieldDescriptorProto::TYPE_DOUBLE:
316       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64, double>(
317           *fd, field, new_lines_mode, *indents, pool, out);
318       return;
319     case FieldDescriptorProto::TYPE_FLOAT:
320       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32, float>(
321           *fd, field, new_lines_mode, *indents, pool, out);
322       return;
323     case FieldDescriptorProto::TYPE_INT64:
324       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int64_t>(
325           *fd, field, new_lines_mode, *indents, pool, out);
326       return;
327     case FieldDescriptorProto::TYPE_UINT64:
328       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt,
329                        uint64_t>(*fd, field, new_lines_mode, *indents, pool,
330                                  out);
331       return;
332     case FieldDescriptorProto::TYPE_INT32:
333       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int32_t>(
334           *fd, field, new_lines_mode, *indents, pool, out);
335       return;
336     case FieldDescriptorProto::TYPE_FIXED64:
337       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64,
338                        uint64_t>(*fd, field, new_lines_mode, *indents, pool,
339                                  out);
340       return;
341     case FieldDescriptorProto::TYPE_FIXED32:
342       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32,
343                        uint32_t>(*fd, field, new_lines_mode, *indents, pool,
344                                  out);
345       return;
346     case FieldDescriptorProto::TYPE_UINT32:
347       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt,
348                        uint32_t>(*fd, field, new_lines_mode, *indents, pool,
349                                  out);
350       return;
351     case FieldDescriptorProto::TYPE_SFIXED32:
352       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32,
353                        int32_t>(*fd, field, new_lines_mode, *indents, pool,
354                                 out);
355       return;
356     case FieldDescriptorProto::TYPE_SFIXED64:
357       PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64,
358                        int64_t>(*fd, field, new_lines_mode, *indents, pool,
359                                 out);
360       return;
361     case FieldDescriptorProto::TYPE_ENUM:
362       PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int32_t>(
363           *fd, field, new_lines_mode, *indents, pool, out);
364       return;
365     // Our protoc plugin cannot generate code for packed repeated fields with
366     // these types. Output a comment and then fall back to the raw field_id:
367     // string representation.
368     case FieldDescriptorProto::TYPE_BOOL:
369     case FieldDescriptorProto::TYPE_SINT32:
370     case FieldDescriptorProto::TYPE_SINT64:
371       StrAppend(out, "# Packed type ", std::to_string(type),
372                 " not supported. Printing raw string.", "\n", *indents);
373       break;
374     case 0:
375     default:
376       break;
377   }
378   std::string value = QuoteAndEscapeTextProtoString(field.as_string());
379   StrAppend(out, std::to_string(field.id()), ": ", value);
380 }
381 
382 // Recursive case function, Will parse |protobytes| assuming it is a proto of
383 // |type| and will use |pool| to look up the |type|. All output will be placed
384 // in |output|, using |new_lines_mode| to separate fields. When called for
385 // |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)386 void ProtozeroToTextInternal(const std::string& type,
387                              protozero::ConstBytes protobytes,
388                              NewLinesMode new_lines_mode,
389                              const DescriptorPool& pool,
390                              std::string* indents,
391                              std::string* output) {
392   std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type);
393   const ProtoDescriptor* opt_proto_descriptor =
394       opt_proto_desc_idx ? &pool.descriptors()[*opt_proto_desc_idx] : nullptr;
395   const bool include_new_lines = new_lines_mode == kIncludeNewLines;
396 
397   protozero::ProtoDecoder decoder(protobytes.data, protobytes.size);
398   for (auto field = decoder.ReadField(); field.valid();
399        field = decoder.ReadField()) {
400     if (!output->empty()) {
401       if (include_new_lines) {
402         StrAppend(output, "\n", *indents);
403       } else {
404         StrAppend(output, " ", *indents);
405       }
406     } else {
407       StrAppend(output, *indents);
408     }
409     auto* opt_field_descriptor =
410         opt_proto_descriptor ? opt_proto_descriptor->FindFieldByTag(field.id())
411                              : nullptr;
412     switch (field.type()) {
413       case ProtoWireType::kVarInt:
414         PrintVarIntField(opt_field_descriptor, field, pool, output);
415         break;
416       case ProtoWireType::kLengthDelimited:
417         PrintLengthDelimitedField(opt_field_descriptor, field, new_lines_mode,
418                                   indents, pool, output);
419         break;
420       case ProtoWireType::kFixed32:
421         PrintFixed32Field(opt_field_descriptor, field, output);
422         break;
423       case ProtoWireType::kFixed64:
424         PrintFixed64Field(opt_field_descriptor, field, output);
425         break;
426     }
427   }
428   if (decoder.bytes_left() != 0) {
429     if (!output->empty()) {
430       if (include_new_lines) {
431         StrAppend(output, "\n", *indents);
432       } else {
433         StrAppend(output, " ", *indents);
434       }
435     }
436     StrAppend(
437         output, "# Extra bytes: ",
438         QuoteAndEscapeTextProtoString(base::StringView(
439             reinterpret_cast<const char*>(decoder.end() - decoder.bytes_left()),
440             decoder.bytes_left())),
441         "\n");
442   }
443 }
444 
445 }  // namespace
446 
ProtozeroToText(const DescriptorPool & pool,const std::string & type,protozero::ConstBytes protobytes,NewLinesMode new_lines_mode,uint32_t initial_indent_depth)447 std::string ProtozeroToText(const DescriptorPool& pool,
448                             const std::string& type,
449                             protozero::ConstBytes protobytes,
450                             NewLinesMode new_lines_mode,
451                             uint32_t initial_indent_depth) {
452   std::string indent = std::string(2 * initial_indent_depth, ' ');
453   std::string final_result;
454   ProtozeroToTextInternal(type, protobytes, new_lines_mode, pool, &indent,
455                           &final_result);
456   return final_result;
457 }
458 
DebugTrackEventProtozeroToText(const std::string & type,protozero::ConstBytes protobytes)459 std::string DebugTrackEventProtozeroToText(const std::string& type,
460                                            protozero::ConstBytes protobytes) {
461   DescriptorPool pool;
462   auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
463                                               kTrackEventDescriptor.size());
464   PERFETTO_DCHECK(status.ok());
465   return ProtozeroToText(pool, type, protobytes, kIncludeNewLines);
466 }
467 
ShortDebugTrackEventProtozeroToText(const std::string & type,protozero::ConstBytes protobytes)468 std::string ShortDebugTrackEventProtozeroToText(
469     const std::string& type,
470     protozero::ConstBytes protobytes) {
471   DescriptorPool pool;
472   auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
473                                               kTrackEventDescriptor.size());
474   PERFETTO_DCHECK(status.ok());
475   return ProtozeroToText(pool, type, protobytes, kSkipNewLines);
476 }
477 
ProtozeroEnumToText(const std::string & type,int32_t enum_value)478 std::string ProtozeroEnumToText(const std::string& type, int32_t enum_value) {
479   DescriptorPool pool;
480   auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
481                                               kTrackEventDescriptor.size());
482   PERFETTO_DCHECK(status.ok());
483   auto opt_enum_descriptor_idx = pool.FindDescriptorIdx(type);
484   if (!opt_enum_descriptor_idx) {
485     // Fall back to the integer representation of the field.
486     return std::to_string(enum_value);
487   }
488   auto opt_enum_string =
489       pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(enum_value);
490   if (!opt_enum_string) {
491     // Fall back to the integer representation of the field.
492     return std::to_string(enum_value);
493   }
494   return *opt_enum_string;
495 }
496 
ProtozeroToText(const DescriptorPool & pool,const std::string & type,const std::vector<uint8_t> & protobytes,NewLinesMode new_lines_mode)497 std::string ProtozeroToText(const DescriptorPool& pool,
498                             const std::string& type,
499                             const std::vector<uint8_t>& protobytes,
500                             NewLinesMode new_lines_mode) {
501   return ProtozeroToText(
502       pool, type, protozero::ConstBytes{protobytes.data(), protobytes.size()},
503       new_lines_mode);
504 }
505 
506 }  // namespace protozero_to_text
507 }  // namespace trace_processor
508 }  // namespace perfetto
509