| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "src/trace_processor/util/protozero_to_text.h" |
| #include <optional> |
| |
| #include "perfetto/ext/base/string_utils.h" |
| #include "perfetto/ext/base/string_view.h" |
| #include "perfetto/protozero/proto_decoder.h" |
| #include "perfetto/protozero/proto_utils.h" |
| #include "protos/perfetto/common/descriptor.pbzero.h" |
| #include "src/trace_processor/util/descriptors.h" |
| |
| // This is the highest level that this protozero to text supports. |
| #include "src/trace_processor/importers/proto/track_event.descriptor.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| namespace protozero_to_text { |
| |
| namespace { |
| |
| using protozero::proto_utils::ProtoWireType; |
| using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto; |
| |
| // This function matches the implementation of TextFormatEscaper.escapeBytes |
| // from the Java protobuf library. |
| std::string QuoteAndEscapeTextProtoString(base::StringView raw) { |
| std::string ret; |
| for (char c : raw) { |
| switch (c) { |
| case '\a': |
| ret += "\\a"; |
| break; |
| case '\b': |
| ret += "\\b"; |
| break; |
| case '\f': |
| ret += "\\f"; |
| break; |
| case '\n': |
| ret += "\\n"; |
| break; |
| case '\r': |
| ret += "\\r"; |
| break; |
| case '\t': |
| ret += "\\t"; |
| break; |
| case '\v': |
| ret += "\\v"; |
| break; |
| case '\\': |
| ret += "\\\\"; |
| break; |
| case '\'': |
| ret += "\\\'"; |
| break; |
| case '"': |
| ret += "\\\""; |
| break; |
| default: |
| // Only ASCII characters between 0x20 (space) and 0x7e (tilde) are |
| // printable; other byte values are escaped with 3-character octal |
| // codes. |
| if (c >= 0x20 && c <= 0x7e) { |
| ret += c; |
| } else { |
| ret += '\\'; |
| |
| // Cast to unsigned char to make the right shift unsigned as well. |
| unsigned char uc = static_cast<unsigned char>(c); |
| ret += ('0' + ((uc >> 6) & 3)); |
| ret += ('0' + ((uc >> 3) & 7)); |
| ret += ('0' + (uc & 7)); |
| } |
| break; |
| } |
| } |
| return '"' + ret + '"'; |
| } |
| |
| // Append |to_add| which is something string like to |out|. |
| template <typename T> |
| void StrAppend(std::string* out, const T& to_add) { |
| out->append(to_add); |
| } |
| |
| template <typename T, typename... strings> |
| void StrAppend(std::string* out, const T& first, strings... values) { |
| StrAppend(out, first); |
| StrAppend(out, values...); |
| } |
| |
| void IncreaseIndents(std::string* out) { |
| StrAppend(out, " "); |
| } |
| |
| void DecreaseIndents(std::string* out) { |
| PERFETTO_DCHECK(out->size() >= 2); |
| out->erase(out->size() - 2); |
| } |
| |
| void PrintUnknownVarIntField(uint32_t id, int64_t value, std::string* out) { |
| StrAppend(out, std::to_string(id), ": ", std::to_string(value)); |
| } |
| |
| void PrintEnumField(const FieldDescriptor& fd, |
| const DescriptorPool& pool, |
| uint32_t id, |
| int32_t enum_value, |
| std::string* out) { |
| auto opt_enum_descriptor_idx = |
| pool.FindDescriptorIdx(fd.resolved_type_name()); |
| if (!opt_enum_descriptor_idx) { |
| PrintUnknownVarIntField(id, enum_value, out); |
| return; |
| } |
| auto opt_enum_string = |
| pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(enum_value); |
| // If the enum value is unknown, treat it like a completely unknown field. |
| if (!opt_enum_string) { |
| PrintUnknownVarIntField(id, enum_value, out); |
| return; |
| } |
| StrAppend(out, fd.name(), ": ", *opt_enum_string); |
| } |
| |
| std::string FormattedFieldDescriptorName( |
| const FieldDescriptor& field_descriptor) { |
| if (field_descriptor.is_extension()) { |
| // Libprotobuf formatter always formats extension field names as fully |
| // qualified names. |
| // TODO(b/197625974): Assuming for now all our extensions will belong to the |
| // perfetto.protos package. Update this if we ever want to support extendees |
| // in different package. |
| return "[perfetto.protos." + field_descriptor.name() + "]"; |
| } else { |
| return field_descriptor.name(); |
| } |
| } |
| |
| void PrintVarIntField(const FieldDescriptor* fd, |
| const protozero::Field& field, |
| const DescriptorPool& pool, |
| std::string* out) { |
| uint32_t type = fd ? fd->type() : 0; |
| switch (type) { |
| case FieldDescriptorProto::TYPE_INT32: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32())); |
| return; |
| case FieldDescriptorProto::TYPE_SINT32: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint32())); |
| return; |
| case FieldDescriptorProto::TYPE_UINT32: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32())); |
| return; |
| case FieldDescriptorProto::TYPE_INT64: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64())); |
| return; |
| case FieldDescriptorProto::TYPE_SINT64: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint64())); |
| return; |
| case FieldDescriptorProto::TYPE_UINT64: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64())); |
| return; |
| case FieldDescriptorProto::TYPE_BOOL: |
| StrAppend(out, fd->name(), ": ", field.as_bool() ? "true" : "false"); |
| return; |
| case FieldDescriptorProto::TYPE_ENUM: |
| PrintEnumField(*fd, pool, field.id(), field.as_int32(), out); |
| return; |
| case 0: |
| default: |
| PrintUnknownVarIntField(field.id(), field.as_int64(), out); |
| return; |
| } |
| } |
| |
| void PrintFixed32Field(const FieldDescriptor* fd, |
| const protozero::Field& field, |
| std::string* out) { |
| uint32_t type = fd ? fd->type() : 0; |
| switch (type) { |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32())); |
| break; |
| case FieldDescriptorProto::TYPE_FIXED32: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32())); |
| break; |
| case FieldDescriptorProto::TYPE_FLOAT: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_float())); |
| break; |
| case 0: |
| default: |
| base::StackString<12> padded_hex("0x%08" PRIx32, field.as_uint32()); |
| StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str()); |
| break; |
| } |
| } |
| |
| void PrintFixed64Field(const FieldDescriptor* fd, |
| const protozero::Field& field, |
| std::string* out) { |
| uint32_t type = fd ? fd->type() : 0; |
| switch (type) { |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64())); |
| break; |
| case FieldDescriptorProto::TYPE_FIXED64: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64())); |
| break; |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| StrAppend(out, fd->name(), ": ", std::to_string(field.as_double())); |
| break; |
| case 0: |
| default: |
| base::StackString<20> padded_hex("0x%016" PRIx64, field.as_uint64()); |
| StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str()); |
| break; |
| } |
| } |
| |
| void ProtozeroToTextInternal(const std::string& type, |
| protozero::ConstBytes protobytes, |
| NewLinesMode new_lines_mode, |
| const DescriptorPool& pool, |
| std::string* indents, |
| std::string* output); |
| |
| template <protozero::proto_utils::ProtoWireType wire_type, typename T> |
| void PrintPackedField(const FieldDescriptor& fd, |
| const protozero::Field& field, |
| NewLinesMode new_lines_mode, |
| const std::string& indents, |
| const DescriptorPool& pool, |
| std::string* out) { |
| const bool include_new_lines = new_lines_mode == kIncludeNewLines; |
| bool err = false; |
| bool first_output = true; |
| for (protozero::PackedRepeatedFieldIterator<wire_type, T> it( |
| field.data(), field.size(), &err); |
| it; it++) { |
| T value = *it; |
| if (!first_output) { |
| if (include_new_lines) { |
| StrAppend(out, "\n", indents); |
| } else { |
| StrAppend(out, " "); |
| } |
| } |
| std::string serialized_value; |
| if (fd.type() == FieldDescriptorProto::TYPE_ENUM) { |
| PrintEnumField(fd, pool, field.id(), static_cast<int32_t>(value), out); |
| } else { |
| StrAppend(out, fd.name(), ": ", std::to_string(value)); |
| } |
| first_output = false; |
| } |
| |
| if (err) { |
| if (!first_output) { |
| if (include_new_lines) { |
| StrAppend(out, "\n", indents); |
| } else { |
| StrAppend(out, " "); |
| } |
| } |
| StrAppend(out, "# Packed decoding failure for field ", fd.name(), "\n"); |
| } |
| } |
| |
| void PrintLengthDelimitedField(const FieldDescriptor* fd, |
| const protozero::Field& field, |
| NewLinesMode new_lines_mode, |
| std::string* indents, |
| const DescriptorPool& pool, |
| std::string* out) { |
| const bool include_new_lines = new_lines_mode == kIncludeNewLines; |
| uint32_t type = fd ? fd->type() : 0; |
| switch (type) { |
| case FieldDescriptorProto::TYPE_BYTES: |
| case FieldDescriptorProto::TYPE_STRING: { |
| std::string value = QuoteAndEscapeTextProtoString(field.as_string()); |
| StrAppend(out, fd->name(), ": ", value); |
| return; |
| } |
| case FieldDescriptorProto::TYPE_MESSAGE: |
| StrAppend(out, FormattedFieldDescriptorName(*fd), " {"); |
| if (include_new_lines) { |
| IncreaseIndents(indents); |
| } |
| ProtozeroToTextInternal(fd->resolved_type_name(), field.as_bytes(), |
| new_lines_mode, pool, indents, out); |
| if (include_new_lines) { |
| DecreaseIndents(indents); |
| StrAppend(out, "\n", *indents, "}"); |
| } else { |
| StrAppend(out, " }"); |
| } |
| return; |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64, double>( |
| *fd, field, new_lines_mode, *indents, pool, out); |
| return; |
| case FieldDescriptorProto::TYPE_FLOAT: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32, float>( |
| *fd, field, new_lines_mode, *indents, pool, out); |
| return; |
| case FieldDescriptorProto::TYPE_INT64: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int64_t>( |
| *fd, field, new_lines_mode, *indents, pool, out); |
| return; |
| case FieldDescriptorProto::TYPE_UINT64: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, |
| uint64_t>(*fd, field, new_lines_mode, *indents, pool, |
| out); |
| return; |
| case FieldDescriptorProto::TYPE_INT32: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int32_t>( |
| *fd, field, new_lines_mode, *indents, pool, out); |
| return; |
| case FieldDescriptorProto::TYPE_FIXED64: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64, |
| uint64_t>(*fd, field, new_lines_mode, *indents, pool, |
| out); |
| return; |
| case FieldDescriptorProto::TYPE_FIXED32: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32, |
| uint32_t>(*fd, field, new_lines_mode, *indents, pool, |
| out); |
| return; |
| case FieldDescriptorProto::TYPE_UINT32: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, |
| uint32_t>(*fd, field, new_lines_mode, *indents, pool, |
| out); |
| return; |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32, |
| int32_t>(*fd, field, new_lines_mode, *indents, pool, |
| out); |
| return; |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64, |
| int64_t>(*fd, field, new_lines_mode, *indents, pool, |
| out); |
| return; |
| case FieldDescriptorProto::TYPE_ENUM: |
| PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int32_t>( |
| *fd, field, new_lines_mode, *indents, pool, out); |
| return; |
| // Our protoc plugin cannot generate code for packed repeated fields with |
| // these types. Output a comment and then fall back to the raw field_id: |
| // string representation. |
| case FieldDescriptorProto::TYPE_BOOL: |
| case FieldDescriptorProto::TYPE_SINT32: |
| case FieldDescriptorProto::TYPE_SINT64: |
| StrAppend(out, "# Packed type ", std::to_string(type), |
| " not supported. Printing raw string.", "\n", *indents); |
| break; |
| case 0: |
| default: |
| break; |
| } |
| std::string value = QuoteAndEscapeTextProtoString(field.as_string()); |
| StrAppend(out, std::to_string(field.id()), ": ", value); |
| } |
| |
| // Recursive case function, Will parse |protobytes| assuming it is a proto of |
| // |type| and will use |pool| to look up the |type|. All output will be placed |
| // in |output|, using |new_lines_mode| to separate fields. When called for |
| // |indents| will be increased by 2 spaces to improve readability. |
| void ProtozeroToTextInternal(const std::string& type, |
| protozero::ConstBytes protobytes, |
| NewLinesMode new_lines_mode, |
| const DescriptorPool& pool, |
| std::string* indents, |
| std::string* output) { |
| std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type); |
| const ProtoDescriptor* opt_proto_descriptor = |
| opt_proto_desc_idx ? &pool.descriptors()[*opt_proto_desc_idx] : nullptr; |
| const bool include_new_lines = new_lines_mode == kIncludeNewLines; |
| |
| protozero::ProtoDecoder decoder(protobytes.data, protobytes.size); |
| for (auto field = decoder.ReadField(); field.valid(); |
| field = decoder.ReadField()) { |
| if (!output->empty()) { |
| if (include_new_lines) { |
| StrAppend(output, "\n", *indents); |
| } else { |
| StrAppend(output, " ", *indents); |
| } |
| } else { |
| StrAppend(output, *indents); |
| } |
| auto* opt_field_descriptor = |
| opt_proto_descriptor ? opt_proto_descriptor->FindFieldByTag(field.id()) |
| : nullptr; |
| switch (field.type()) { |
| case ProtoWireType::kVarInt: |
| PrintVarIntField(opt_field_descriptor, field, pool, output); |
| break; |
| case ProtoWireType::kLengthDelimited: |
| PrintLengthDelimitedField(opt_field_descriptor, field, new_lines_mode, |
| indents, pool, output); |
| break; |
| case ProtoWireType::kFixed32: |
| PrintFixed32Field(opt_field_descriptor, field, output); |
| break; |
| case ProtoWireType::kFixed64: |
| PrintFixed64Field(opt_field_descriptor, field, output); |
| break; |
| } |
| } |
| if (decoder.bytes_left() != 0) { |
| if (!output->empty()) { |
| if (include_new_lines) { |
| StrAppend(output, "\n", *indents); |
| } else { |
| StrAppend(output, " ", *indents); |
| } |
| } |
| StrAppend( |
| output, "# Extra bytes: ", |
| QuoteAndEscapeTextProtoString(base::StringView( |
| reinterpret_cast<const char*>(decoder.end() - decoder.bytes_left()), |
| decoder.bytes_left())), |
| "\n"); |
| } |
| } |
| |
| } // namespace |
| |
| std::string ProtozeroToText(const DescriptorPool& pool, |
| const std::string& type, |
| protozero::ConstBytes protobytes, |
| NewLinesMode new_lines_mode, |
| uint32_t initial_indent_depth) { |
| std::string indent = std::string(2 * initial_indent_depth, ' '); |
| std::string final_result; |
| ProtozeroToTextInternal(type, protobytes, new_lines_mode, pool, &indent, |
| &final_result); |
| return final_result; |
| } |
| |
| std::string DebugTrackEventProtozeroToText(const std::string& type, |
| protozero::ConstBytes protobytes) { |
| DescriptorPool pool; |
| auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(), |
| kTrackEventDescriptor.size()); |
| PERFETTO_DCHECK(status.ok()); |
| return ProtozeroToText(pool, type, protobytes, kIncludeNewLines); |
| } |
| |
| std::string ShortDebugTrackEventProtozeroToText( |
| const std::string& type, |
| protozero::ConstBytes protobytes) { |
| DescriptorPool pool; |
| auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(), |
| kTrackEventDescriptor.size()); |
| PERFETTO_DCHECK(status.ok()); |
| return ProtozeroToText(pool, type, protobytes, kSkipNewLines); |
| } |
| |
| std::string ProtozeroEnumToText(const std::string& type, int32_t enum_value) { |
| DescriptorPool pool; |
| auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(), |
| kTrackEventDescriptor.size()); |
| PERFETTO_DCHECK(status.ok()); |
| auto opt_enum_descriptor_idx = pool.FindDescriptorIdx(type); |
| if (!opt_enum_descriptor_idx) { |
| // Fall back to the integer representation of the field. |
| return std::to_string(enum_value); |
| } |
| auto opt_enum_string = |
| pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(enum_value); |
| if (!opt_enum_string) { |
| // Fall back to the integer representation of the field. |
| return std::to_string(enum_value); |
| } |
| return *opt_enum_string; |
| } |
| |
| std::string ProtozeroToText(const DescriptorPool& pool, |
| const std::string& type, |
| const std::vector<uint8_t>& protobytes, |
| NewLinesMode new_lines_mode) { |
| return ProtozeroToText( |
| pool, type, protozero::ConstBytes{protobytes.data(), protobytes.size()}, |
| new_lines_mode); |
| } |
| |
| } // namespace protozero_to_text |
| } // namespace trace_processor |
| } // namespace perfetto |