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