| #include "src/trace_processor/util/protozero_to_text.h" |
| |
| #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/track_event.descriptor.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| namespace protozero_to_text { |
| |
| namespace { |
| |
| std::string BytesToHexEncodedString(const std::string& bytes) { |
| // Each byte becomes four chars 'A' -> "\x41" + 1 for trailing null. |
| std::string value(4 * bytes.size() + 1, 'Z'); |
| for (size_t i = 0; i < bytes.size(); ++i) { |
| // snprintf prints 5 characters: '\x', then two hex digits, and finally a |
| // null byte. As we write left to right, we keep overwriting the null |
| // byte, except for the last call to snprintf. |
| snprintf(&(value[4 * i]), 5, "\\x%02hhx", bytes[i]); |
| } |
| // Trim trailing null. |
| value.resize(4 * bytes.size()); |
| return value; |
| } |
| |
| // This function matches the implementation of TextFormatEscaper.escapeBytes |
| // from the Java protobuf library. |
| std::string QuoteAndEscapeTextProtoString(const std::string& raw) { |
| std::string ret; |
| for (auto it = raw.cbegin(); it != raw.cend(); it++) { |
| switch (*it) { |
| 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 (*it >= 0x20 && *it <= 0x7e) { |
| ret += *it; |
| } else { |
| ret += '\\'; |
| |
| // Cast to unsigned char to make the right shift unsigned as well. |
| unsigned char c = static_cast<unsigned char>(*it); |
| ret += ('0' + ((c >> 6) & 3)); |
| ret += ('0' + ((c >> 3) & 7)); |
| ret += ('0' + (c & 7)); |
| } |
| break; |
| } |
| } |
| return '"' + ret + '"'; |
| } |
| |
| // Recursively determine the size of all the string like things passed in the |
| // parameter pack |rest|. |
| size_t SizeOfStr() { |
| return 0; |
| } |
| template <typename T, typename... Rest> |
| size_t SizeOfStr(const T& first, Rest... rest) { |
| return base::StringView(first).size() + SizeOfStr(rest...); |
| } |
| |
| // Append |to_add| which is something string like to |out|. |
| template <typename T> |
| void StrAppendInternal(std::string* out, const T& to_add) { |
| out->append(to_add); |
| } |
| |
| template <typename T, typename... strings> |
| void StrAppendInternal(std::string* out, const T& first, strings... values) { |
| StrAppendInternal(out, first); |
| StrAppendInternal(out, values...); |
| } |
| |
| // Append |to_add| which is something string like to |out|. |
| template <typename T> |
| void StrAppend(std::string* out, const T& to_add) { |
| out->reserve(out->size() + base::StringView(to_add).size()); |
| out->append(to_add); |
| } |
| |
| template <typename T, typename... strings> |
| void StrAppend(std::string* out, const T& first, strings... values) { |
| out->reserve(out->size() + SizeOfStr(values...)); |
| StrAppendInternal(out, first); |
| StrAppendInternal(out, values...); |
| } |
| |
| void ConvertProtoTypeToFieldAndValueString(const FieldDescriptor& fd, |
| const protozero::Field& field, |
| const std::string& separator, |
| const std::string& indent, |
| const DescriptorPool& pool, |
| std::string* out) { |
| using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto; |
| switch (fd.type()) { |
| case FieldDescriptorProto::TYPE_INT32: |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| case FieldDescriptorProto::TYPE_FIXED32: |
| StrAppend(out, separator, indent, fd.name(), ": ", |
| std::to_string(field.as_int32())); |
| return; |
| case FieldDescriptorProto::TYPE_SINT32: |
| StrAppend(out, separator, indent, fd.name(), ": ", |
| std::to_string(field.as_sint32())); |
| return; |
| case FieldDescriptorProto::TYPE_INT64: |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| case FieldDescriptorProto::TYPE_FIXED64: |
| StrAppend(out, separator, indent, fd.name(), ": ", |
| std::to_string(field.as_int64())); |
| return; |
| case FieldDescriptorProto::TYPE_SINT64: |
| StrAppend(out, separator, indent, fd.name(), ": ", |
| std::to_string(field.as_sint64())); |
| return; |
| case FieldDescriptorProto::TYPE_UINT32: |
| StrAppend(out, separator, indent, fd.name(), ": ", |
| std::to_string(field.as_uint32())); |
| return; |
| case FieldDescriptorProto::TYPE_UINT64: |
| StrAppend(out, separator, indent, fd.name(), ": ", |
| std::to_string(field.as_uint64())); |
| return; |
| case FieldDescriptorProto::TYPE_BOOL: |
| StrAppend(out, separator, indent, fd.name(), ": ", |
| field.as_bool() ? "true" : "false"); |
| return; |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| StrAppend(out, separator, indent, fd.name(), ": ", |
| std::to_string(field.as_double())); |
| return; |
| case FieldDescriptorProto::TYPE_FLOAT: |
| StrAppend(out, separator, indent, fd.name(), ": ", |
| std::to_string(field.as_float())); |
| return; |
| case FieldDescriptorProto::TYPE_STRING: { |
| auto s = QuoteAndEscapeTextProtoString(field.as_std_string()); |
| StrAppend(out, separator, indent, fd.name(), ": ", s); |
| return; |
| } |
| case FieldDescriptorProto::TYPE_BYTES: { |
| std::string value = BytesToHexEncodedString(field.as_std_string()); |
| StrAppend(out, separator, indent, fd.name(), ": \"", value, "\""); |
| return; |
| } |
| case FieldDescriptorProto::TYPE_ENUM: { |
| auto opt_enum_descriptor_idx = |
| pool.FindDescriptorIdx(fd.resolved_type_name()); |
| PERFETTO_DCHECK(opt_enum_descriptor_idx); |
| auto opt_enum_string = |
| pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString( |
| field.as_int32()); |
| PERFETTO_DCHECK(opt_enum_string); |
| StrAppend(out, separator, indent, fd.name(), ": ", *opt_enum_string); |
| return; |
| } |
| default: { |
| PERFETTO_FATAL( |
| "Tried to write value of type field %s (in proto type " |
| "%s) which has type enum %d", |
| fd.name().c_str(), fd.resolved_type_name().c_str(), fd.type()); |
| } |
| } |
| } |
| |
| void IncreaseIndents(std::string* out) { |
| StrAppend(out, " "); |
| } |
| |
| void DecreaseIndents(std::string* out) { |
| PERFETTO_DCHECK(out->size() >= 2); |
| out->erase(out->size() - 2); |
| } |
| |
| 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(); |
| } |
| } |
| |
| // 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| and between fields |separator| will be placed. 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) { |
| auto opt_proto_descriptor_idx = pool.FindDescriptorIdx(type); |
| PERFETTO_DCHECK(opt_proto_descriptor_idx); |
| auto& proto_descriptor = pool.descriptors()[*opt_proto_descriptor_idx]; |
| 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()) { |
| auto opt_field_descriptor = proto_descriptor.FindFieldByTag(field.id()); |
| if (!opt_field_descriptor) { |
| StrAppend( |
| output, output->empty() ? "" : "\n", *indents, |
| "# Ignoring unknown field with id: ", std::to_string(field.id())); |
| continue; |
| } |
| const auto& field_descriptor = *opt_field_descriptor; |
| |
| if (field_descriptor.type() == |
| protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) { |
| if (include_new_lines) { |
| StrAppend(output, output->empty() ? "" : "\n", *indents, |
| FormattedFieldDescriptorName(field_descriptor), ": {"); |
| IncreaseIndents(indents); |
| } else { |
| StrAppend(output, output->empty() ? "" : " ", |
| FormattedFieldDescriptorName(field_descriptor), ": {"); |
| } |
| ProtozeroToTextInternal(field_descriptor.resolved_type_name(), |
| field.as_bytes(), new_lines_mode, pool, indents, |
| output); |
| if (include_new_lines) { |
| DecreaseIndents(indents); |
| StrAppend(output, "\n", *indents, "}"); |
| } else { |
| StrAppend(output, " }"); |
| } |
| } else { |
| ConvertProtoTypeToFieldAndValueString( |
| field_descriptor, field, |
| output->empty() ? "" : include_new_lines ? "\n" : " ", *indents, pool, |
| output); |
| } |
| } |
| PERFETTO_DCHECK(decoder.bytes_left() == 0); |
| } |
| |
| } // namespace |
| |
| std::string ProtozeroToText(const DescriptorPool& pool, |
| const std::string& type, |
| protozero::ConstBytes protobytes, |
| NewLinesMode new_lines_mode) { |
| std::string indent = ""; |
| 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); |
| } |
| |
| std::string BytesToHexEncodedStringForTesting(const std::string& s) { |
| return BytesToHexEncodedString(s); |
| } |
| |
| } // namespace protozero_to_text |
| } // namespace trace_processor |
| } // namespace perfetto |