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