1 #include "src/trace_processor/util/protozero_to_text.h"
2
3 #include "perfetto/ext/base/string_utils.h"
4 #include "perfetto/ext/base/string_view.h"
5 #include "perfetto/protozero/proto_decoder.h"
6 #include "perfetto/protozero/proto_utils.h"
7 #include "protos/perfetto/common/descriptor.pbzero.h"
8 #include "src/trace_processor/util/descriptors.h"
9
10 // This is the highest level that this protozero to text supports.
11 #include "src/trace_processor/importers/track_event.descriptor.h"
12
13 namespace perfetto {
14 namespace trace_processor {
15 namespace protozero_to_text {
16
17 namespace {
18
BytesToHexEncodedString(const std::string & bytes)19 std::string BytesToHexEncodedString(const std::string& bytes) {
20 // Each byte becomes four chars 'A' -> "\x41" + 1 for trailing null.
21 std::string value(4 * bytes.size() + 1, 'Z');
22 for (size_t i = 0; i < bytes.size(); ++i) {
23 // snprintf prints 5 characters: '\x', then two hex digits, and finally a
24 // null byte. As we write left to right, we keep overwriting the null
25 // byte, except for the last call to snprintf.
26 snprintf(&(value[4 * i]), 5, "\\x%02hhx", bytes[i]);
27 }
28 // Trim trailing null.
29 value.resize(4 * bytes.size());
30 return value;
31 }
32
33 // Recursively determine the size of all the string like things passed in the
34 // parameter pack |rest|.
SizeOfStr()35 size_t SizeOfStr() {
36 return 0;
37 }
38 template <typename T, typename... Rest>
SizeOfStr(const T & first,Rest...rest)39 size_t SizeOfStr(const T& first, Rest... rest) {
40 return base::StringView(first).size() + SizeOfStr(rest...);
41 }
42
43 // Append |to_add| which is something string like to |out|.
44 template <typename T>
StrAppendInternal(std::string * out,const T & to_add)45 void StrAppendInternal(std::string* out, const T& to_add) {
46 out->append(to_add);
47 }
48
49 template <typename T, typename... strings>
StrAppendInternal(std::string * out,const T & first,strings...values)50 void StrAppendInternal(std::string* out, const T& first, strings... values) {
51 StrAppendInternal(out, first);
52 StrAppendInternal(out, values...);
53 }
54
55 // Append |to_add| which is something string like to |out|.
56 template <typename T>
StrAppend(std::string * out,const T & to_add)57 void StrAppend(std::string* out, const T& to_add) {
58 out->reserve(out->size() + base::StringView(to_add).size());
59 out->append(to_add);
60 }
61
62 template <typename T, typename... strings>
StrAppend(std::string * out,const T & first,strings...values)63 void StrAppend(std::string* out, const T& first, strings... values) {
64 out->reserve(out->size() + SizeOfStr(values...));
65 StrAppendInternal(out, first);
66 StrAppendInternal(out, values...);
67 }
68
ConvertProtoTypeToFieldAndValueString(const FieldDescriptor & fd,const protozero::Field & field,const std::string & separator,const std::string & indent,const DescriptorPool & pool,std::string * out)69 void ConvertProtoTypeToFieldAndValueString(const FieldDescriptor& fd,
70 const protozero::Field& field,
71 const std::string& separator,
72 const std::string& indent,
73 const DescriptorPool& pool,
74 std::string* out) {
75 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
76 switch (fd.type()) {
77 case FieldDescriptorProto::TYPE_INT32:
78 case FieldDescriptorProto::TYPE_SFIXED32:
79 case FieldDescriptorProto::TYPE_FIXED32:
80 StrAppend(out, separator, indent, fd.name(), ": ",
81 std::to_string(field.as_int32()));
82 return;
83 case FieldDescriptorProto::TYPE_SINT32:
84 StrAppend(out, separator, indent, fd.name(), ": ",
85 std::to_string(field.as_sint32()));
86 return;
87 case FieldDescriptorProto::TYPE_INT64:
88 case FieldDescriptorProto::TYPE_SFIXED64:
89 case FieldDescriptorProto::TYPE_FIXED64:
90 StrAppend(out, separator, indent, fd.name(), ": ",
91 std::to_string(field.as_int64()));
92 return;
93 case FieldDescriptorProto::TYPE_SINT64:
94 StrAppend(out, separator, indent, fd.name(), ": ",
95 std::to_string(field.as_sint64()));
96 return;
97 case FieldDescriptorProto::TYPE_UINT32:
98 StrAppend(out, separator, indent, fd.name(), ": ",
99 std::to_string(field.as_uint32()));
100 return;
101 case FieldDescriptorProto::TYPE_UINT64:
102 StrAppend(out, separator, indent, fd.name(), ": ",
103 std::to_string(field.as_uint64()));
104 return;
105 case FieldDescriptorProto::TYPE_BOOL:
106 StrAppend(out, separator, indent, fd.name(), ": ",
107 field.as_bool() ? "true" : "false");
108 return;
109 case FieldDescriptorProto::TYPE_DOUBLE:
110 StrAppend(out, separator, indent, fd.name(), ": ",
111 std::to_string(field.as_double()));
112 return;
113 case FieldDescriptorProto::TYPE_FLOAT:
114 StrAppend(out, separator, indent, fd.name(), ": ",
115 std::to_string(field.as_float()));
116 return;
117 case FieldDescriptorProto::TYPE_STRING: {
118 auto s = base::QuoteAndEscapeControlCodes(field.as_std_string());
119 StrAppend(out, separator, indent, fd.name(), ": ", s);
120 return;
121 }
122 case FieldDescriptorProto::TYPE_BYTES: {
123 std::string value = BytesToHexEncodedString(field.as_std_string());
124 StrAppend(out, separator, indent, fd.name(), ": \"", value, "\"");
125 return;
126 }
127 case FieldDescriptorProto::TYPE_ENUM: {
128 auto opt_enum_descriptor_idx =
129 pool.FindDescriptorIdx(fd.resolved_type_name());
130 PERFETTO_DCHECK(opt_enum_descriptor_idx);
131 auto opt_enum_string =
132 pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(
133 field.as_int32());
134 PERFETTO_DCHECK(opt_enum_string);
135 StrAppend(out, separator, indent, fd.name(), ": ", *opt_enum_string);
136 return;
137 }
138 default: {
139 PERFETTO_FATAL(
140 "Tried to write value of type field %s (in proto type "
141 "%s) which has type enum %d",
142 fd.name().c_str(), fd.resolved_type_name().c_str(), fd.type());
143 }
144 }
145 }
146
IncreaseIndents(std::string * out)147 void IncreaseIndents(std::string* out) {
148 StrAppend(out, " ");
149 }
150
DecreaseIndents(std::string * out)151 void DecreaseIndents(std::string* out) {
152 PERFETTO_DCHECK(out->size() >= 2);
153 out->erase(out->size() - 2);
154 }
155
156 // Recursive case function, Will parse |protobytes| assuming it is a proto of
157 // |type| and will use |pool| to look up the |type|. All output will be placed
158 // in |output| and between fields |separator| will be placed. When called for
159 // |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)160 void ProtozeroToTextInternal(const std::string& type,
161 protozero::ConstBytes protobytes,
162 NewLinesMode new_lines_mode,
163 const DescriptorPool& pool,
164 std::string* indents,
165 std::string* output) {
166 auto opt_proto_descriptor_idx = pool.FindDescriptorIdx(type);
167 PERFETTO_DCHECK(opt_proto_descriptor_idx);
168 auto& proto_descriptor = pool.descriptors()[*opt_proto_descriptor_idx];
169 bool include_new_lines = new_lines_mode == kIncludeNewLines;
170
171 protozero::ProtoDecoder decoder(protobytes.data, protobytes.size);
172 for (auto field = decoder.ReadField(); field.valid();
173 field = decoder.ReadField()) {
174 auto opt_field_descriptor = proto_descriptor.FindFieldByTag(field.id());
175 if (!opt_field_descriptor) {
176 StrAppend(
177 output, output->empty() ? "" : "\n", *indents,
178 "# Ignoring unknown field with id: ", std::to_string(field.id()));
179 continue;
180 }
181 const auto& field_descriptor = *opt_field_descriptor;
182
183 if (field_descriptor.type() ==
184 protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) {
185 if (include_new_lines) {
186 StrAppend(output, output->empty() ? "" : "\n", *indents,
187 field_descriptor.name(), ": {");
188 IncreaseIndents(indents);
189 } else {
190 StrAppend(output, output->empty() ? "" : " ", field_descriptor.name(),
191 ": {");
192 }
193 ProtozeroToTextInternal(field_descriptor.resolved_type_name(),
194 field.as_bytes(), new_lines_mode, pool, indents,
195 output);
196 if (include_new_lines) {
197 DecreaseIndents(indents);
198 StrAppend(output, "\n", *indents, "}");
199 } else {
200 StrAppend(output, " }");
201 }
202 } else {
203 ConvertProtoTypeToFieldAndValueString(
204 field_descriptor, field,
205 output->empty() ? "" : include_new_lines ? "\n" : " ", *indents, pool,
206 output);
207 }
208 }
209 PERFETTO_DCHECK(decoder.bytes_left() == 0);
210 }
211
212 } // namespace
213
ProtozeroToText(const DescriptorPool & pool,const std::string & type,protozero::ConstBytes protobytes,NewLinesMode new_lines_mode)214 std::string ProtozeroToText(const DescriptorPool& pool,
215 const std::string& type,
216 protozero::ConstBytes protobytes,
217 NewLinesMode new_lines_mode) {
218 std::string indent = "";
219 std::string final_result;
220 ProtozeroToTextInternal(type, protobytes, new_lines_mode, pool, &indent,
221 &final_result);
222 return final_result;
223 }
224
DebugTrackEventProtozeroToText(const std::string & type,protozero::ConstBytes protobytes)225 std::string DebugTrackEventProtozeroToText(const std::string& type,
226 protozero::ConstBytes protobytes) {
227 DescriptorPool pool;
228 auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
229 kTrackEventDescriptor.size());
230 PERFETTO_DCHECK(status.ok());
231 return ProtozeroToText(pool, type, protobytes, kIncludeNewLines);
232 }
233
ShortDebugTrackEventProtozeroToText(const std::string & type,protozero::ConstBytes protobytes)234 std::string ShortDebugTrackEventProtozeroToText(
235 const std::string& type,
236 protozero::ConstBytes protobytes) {
237 DescriptorPool pool;
238 auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
239 kTrackEventDescriptor.size());
240 PERFETTO_DCHECK(status.ok());
241 return ProtozeroToText(pool, type, protobytes, kSkipNewLines);
242 }
243
ProtozeroEnumToText(const std::string & type,int32_t enum_value)244 std::string ProtozeroEnumToText(const std::string& type, int32_t enum_value) {
245 DescriptorPool pool;
246 auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
247 kTrackEventDescriptor.size());
248 PERFETTO_DCHECK(status.ok());
249 auto opt_enum_descriptor_idx = pool.FindDescriptorIdx(type);
250 if (!opt_enum_descriptor_idx) {
251 // Fall back to the integer representation of the field.
252 return std::to_string(enum_value);
253 }
254 auto opt_enum_string =
255 pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(enum_value);
256 if (!opt_enum_string) {
257 // Fall back to the integer representation of the field.
258 return std::to_string(enum_value);
259 }
260 return *opt_enum_string;
261 }
262
ProtozeroToText(const DescriptorPool & pool,const std::string & type,const std::vector<uint8_t> & protobytes,NewLinesMode new_lines_mode)263 std::string ProtozeroToText(const DescriptorPool& pool,
264 const std::string& type,
265 const std::vector<uint8_t>& protobytes,
266 NewLinesMode new_lines_mode) {
267 return ProtozeroToText(
268 pool, type, protozero::ConstBytes{protobytes.data(), protobytes.size()},
269 new_lines_mode);
270 }
271
BytesToHexEncodedStringForTesting(const std::string & s)272 std::string BytesToHexEncodedStringForTesting(const std::string& s) {
273 return BytesToHexEncodedString(s);
274 }
275
276 } // namespace protozero_to_text
277 } // namespace trace_processor
278 } // namespace perfetto
279