1# Copyright 2019 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5""" Classes representing perfetto trace protobuf messages.
6
7This module makes use of neither python-protobuf library nor python classes
8compiled from .proto definitions, because currently there's no way to
9deploy those to all the places where telemetry is run.
10
11TODO(crbug.com/944078): Remove this module after the python-protobuf library
12is deployed to all the bots.
13
14Definitions of perfetto messages can be found here:
15https://android.googlesource.com/platform/external/perfetto/+/refs/heads/master/protos/perfetto/trace/
16"""
17
18import encoder
19import wire_format
20
21
22class TracePacket(object):
23  def __init__(self):
24    self.clock_snapshot = None
25    self.timestamp = None
26    self.timestamp_clock_id = None
27    self.interned_data = None
28    self.thread_descriptor = None
29    self.incremental_state_cleared = None
30    self.chrome_event = None
31    self.track_event = None
32    self.trusted_packet_sequence_id = None
33    self.chrome_benchmark_metadata = None
34
35  def encode(self):
36    parts = []
37    if self.chrome_event is not None:
38      tag = encoder.TagBytes(5, wire_format.WIRETYPE_LENGTH_DELIMITED)
39      data = self.chrome_event.encode()
40      length = encoder._VarintBytes(len(data))
41      parts += [tag, length, data]
42    if self.clock_snapshot is not None:
43      tag = encoder.TagBytes(6, wire_format.WIRETYPE_LENGTH_DELIMITED)
44      data = self.clock_snapshot.encode()
45      length = encoder._VarintBytes(len(data))
46      parts += [tag, length, data]
47    if self.timestamp is not None:
48      writer = encoder.UInt64Encoder(8, False, False)
49      writer(parts.append, self.timestamp)
50    if self.trusted_packet_sequence_id is not None:
51      writer = encoder.UInt32Encoder(10, False, False)
52      writer(parts.append, self.trusted_packet_sequence_id)
53    if self.track_event is not None:
54      tag = encoder.TagBytes(11, wire_format.WIRETYPE_LENGTH_DELIMITED)
55      data = self.track_event.encode()
56      length = encoder._VarintBytes(len(data))
57      parts += [tag, length, data]
58    if self.interned_data is not None:
59      tag = encoder.TagBytes(12, wire_format.WIRETYPE_LENGTH_DELIMITED)
60      data = self.interned_data.encode()
61      length = encoder._VarintBytes(len(data))
62      parts += [tag, length, data]
63    if self.incremental_state_cleared is not None:
64      writer = encoder.BoolEncoder(41, False, False)
65      writer(parts.append, self.incremental_state_cleared)
66    if self.thread_descriptor is not None:
67      tag = encoder.TagBytes(44, wire_format.WIRETYPE_LENGTH_DELIMITED)
68      data = self.thread_descriptor.encode()
69      length = encoder._VarintBytes(len(data))
70      parts += [tag, length, data]
71    if self.chrome_benchmark_metadata is not None:
72      tag = encoder.TagBytes(48, wire_format.WIRETYPE_LENGTH_DELIMITED)
73      data = self.chrome_benchmark_metadata.encode()
74      length = encoder._VarintBytes(len(data))
75      parts += [tag, length, data]
76    if self.timestamp_clock_id is not None:
77      writer = encoder.UInt32Encoder(58, False, False)
78      writer(parts.append, self.timestamp_clock_id)
79
80    return b"".join(parts)
81
82
83class InternedData(object):
84  def __init__(self):
85    self.event_category = None
86    self.legacy_event_name = None
87
88  def encode(self):
89    parts = []
90    if self.event_category is not None:
91      tag = encoder.TagBytes(1, wire_format.WIRETYPE_LENGTH_DELIMITED)
92      data = self.event_category.encode()
93      length = encoder._VarintBytes(len(data))
94      parts += [tag, length, data]
95    if self.legacy_event_name is not None:
96      tag = encoder.TagBytes(2, wire_format.WIRETYPE_LENGTH_DELIMITED)
97      data = self.legacy_event_name.encode()
98      length = encoder._VarintBytes(len(data))
99      parts += [tag, length, data]
100
101    return b"".join(parts)
102
103
104class EventCategory(object):
105  def __init__(self):
106    self.iid = None
107    self.name = None
108
109  def encode(self):
110    if (self.iid is None or self.name is None):
111      raise RuntimeError("Missing mandatory fields.")
112
113    parts = []
114    writer = encoder.UInt32Encoder(1, False, False)
115    writer(parts.append, self.iid)
116    writer = encoder.StringEncoder(2, False, False)
117    writer(parts.append, self.name)
118
119    return b"".join(parts)
120
121
122LegacyEventName = EventCategory
123
124
125class ThreadDescriptor(object):
126  def __init__(self):
127    self.pid = None
128    self.tid = None
129
130  def encode(self):
131    if (self.pid is None or self.tid is None):
132      raise RuntimeError("Missing mandatory fields.")
133
134    parts = []
135    writer = encoder.UInt32Encoder(1, False, False)
136    writer(parts.append, self.pid)
137    writer = encoder.UInt32Encoder(2, False, False)
138    writer(parts.append, self.tid)
139
140    return b"".join(parts)
141
142
143class ChromeEventBundle(object):
144  def __init__(self):
145    self.metadata = []
146
147  def encode(self):
148    parts = []
149    for item in self.metadata:
150      tag = encoder.TagBytes(2, wire_format.WIRETYPE_LENGTH_DELIMITED)
151      data = item.encode()
152      length = encoder._VarintBytes(len(data))
153      parts += [tag, length, data]
154
155    return b"".join(parts)
156
157
158class TrackEvent(object):
159  def __init__(self):
160    self.legacy_event = None
161    self.category_iids = None
162    self.debug_annotations = []
163
164  def encode(self):
165    parts = []
166    if self.category_iids is not None:
167      writer = encoder.UInt32Encoder(3, is_repeated=True, is_packed=False)
168      writer(parts.append, self.category_iids)
169    for annotation in self.debug_annotations:
170      tag = encoder.TagBytes(4, wire_format.WIRETYPE_LENGTH_DELIMITED)
171      data = annotation.encode()
172      length = encoder._VarintBytes(len(data))
173      parts += [tag, length, data]
174    if self.legacy_event is not None:
175      tag = encoder.TagBytes(6, wire_format.WIRETYPE_LENGTH_DELIMITED)
176      data = self.legacy_event.encode()
177      length = encoder._VarintBytes(len(data))
178      parts += [tag, length, data]
179
180    return b"".join(parts)
181
182
183class LegacyEvent(object):
184  def __init__(self):
185    self.phase = None
186    self.name_iid = None
187
188  def encode(self):
189    parts = []
190    if self.name_iid is not None:
191      writer = encoder.UInt32Encoder(1, False, False)
192      writer(parts.append, self.name_iid)
193    if self.phase is not None:
194      writer = encoder.Int32Encoder(2, False, False)
195      writer(parts.append, self.phase)
196
197    return b"".join(parts)
198
199
200class ChromeBenchmarkMetadata(object):
201  def __init__(self):
202    self.benchmark_start_time_us = None
203    self.story_run_time_us = None
204    self.benchmark_name = None
205    self.benchmark_description = None
206    self.story_name = None
207    self.story_tags = None
208    self.story_run_index = None
209    self.label = None
210
211  def encode(self):
212    parts = []
213    if self.benchmark_start_time_us is not None:
214      writer = encoder.Int64Encoder(1, False, False)
215      writer(parts.append, self.benchmark_start_time_us)
216    if self.story_run_time_us is not None:
217      writer = encoder.Int64Encoder(2, False, False)
218      writer(parts.append, self.story_run_time_us)
219    if self.benchmark_name is not None:
220      writer = encoder.StringEncoder(3, False, False)
221      writer(parts.append, self.benchmark_name)
222    if self.benchmark_description is not None:
223      writer = encoder.StringEncoder(4, False, False)
224      writer(parts.append, self.benchmark_description)
225    if self.label is not None:
226      writer = encoder.StringEncoder(5, False, False)
227      writer(parts.append, self.label)
228    if self.story_name is not None:
229      writer = encoder.StringEncoder(6, False, False)
230      writer(parts.append, self.story_name)
231    if self.story_tags is not None:
232      writer = encoder.StringEncoder(7, is_repeated=True, is_packed=False)
233      writer(parts.append, self.story_tags)
234    if self.story_run_index is not None:
235      writer = encoder.Int32Encoder(8, False, False)
236      writer(parts.append, self.story_run_index)
237
238    return b"".join(parts)
239
240
241def write_trace_packet(output, trace_packet):
242  tag = encoder.TagBytes(1, wire_format.WIRETYPE_LENGTH_DELIMITED)
243  output.write(tag)
244  binary_data = trace_packet.encode()
245  encoder._EncodeVarint(output.write, len(binary_data))
246  output.write(binary_data)
247
248
249class DebugAnnotation(object):
250  def __init__(self):
251    self.name = None
252    self.int_value = None
253    self.double_value = None
254    self.string_value = None
255
256  def encode(self):
257    if self.name is None:
258      raise RuntimeError("DebugAnnotation must have a name.")
259    if ((self.string_value is not None) +
260        (self.int_value is not None) +
261        (self.double_value is not None)) != 1:
262      raise RuntimeError("DebugAnnotation must have exactly one value.")
263
264    parts = []
265    writer = encoder.StringEncoder(10, False, False)
266    writer(parts.append, self.name)
267    if self.int_value is not None:
268      writer = encoder.Int64Encoder(4, False, False)
269      writer(parts.append, self.int_value)
270    if self.double_value is not None:
271      writer = encoder.DoubleEncoder(5, False, False)
272      writer(parts.append, self.double_value)
273    if self.string_value is not None:
274      writer = encoder.StringEncoder(6, False, False)
275      writer(parts.append, self.string_value)
276
277    return b"".join(parts)
278
279
280class ChromeMetadata(object):
281  def __init__(self):
282    self.name = None
283    self.string_value = None
284
285  def encode(self):
286    if self.name is None or self.string_value is None:
287      raise RuntimeError("ChromeMetadata must have a name and a value.")
288
289    parts = []
290    writer = encoder.StringEncoder(1, False, False)
291    writer(parts.append, self.name)
292    writer = encoder.StringEncoder(2, False, False)
293    writer(parts.append, self.string_value)
294
295    return b"".join(parts)
296
297
298class Clock(object):
299  def __init__(self):
300    self.clock_id = None
301    self.timestamp = None
302
303  def encode(self):
304    if self.clock_id is None or self.timestamp is None:
305      raise RuntimeError("Clock must have a clock_id and a timestamp.")
306
307    parts = []
308    writer = encoder.UInt32Encoder(1, False, False)
309    writer(parts.append, self.clock_id)
310    writer = encoder.UInt64Encoder(2, False, False)
311    writer(parts.append, self.timestamp)
312
313    return b"".join(parts)
314
315
316class ClockSnapshot(object):
317  def __init__(self):
318    self.clocks = []
319
320  def encode(self):
321    if len(self.clocks) < 2:
322      raise RuntimeError("ClockSnapshot must have at least two clocks.")
323
324    parts = []
325    for clock in self.clocks:
326      tag = encoder.TagBytes(1, wire_format.WIRETYPE_LENGTH_DELIMITED)
327      data = clock.encode()
328      length = encoder._VarintBytes(len(data))
329      parts += [tag, length, data]
330
331    return b"".join(parts)
332