blob: 37809538797ad54368059b6fefb58afab70c9383 [file] [log] [blame]
Oussama Ben Abdelbakidcd74cf2020-08-10 14:00:36 -04001# 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""" Functions to write trace data in perfetto protobuf format.
6"""
7
8import collections
9
10import perfetto_proto_classes as proto
11
12
13
14# Dicts of strings for interning.
15# Note that each thread has its own interning index.
16_interned_categories_by_tid = collections.defaultdict(dict)
17_interned_event_names_by_tid = collections.defaultdict(dict)
18
19# Trusted sequence ids from telemetry should not overlap with
20# trusted sequence ids from other trace producers. Chrome assigns
21# sequence ids incrementally starting from 1 and we expect all its ids
22# to be well below 10000. Starting from 2^20 will give us enough
23# confidence that it will not overlap.
24_next_sequence_id = 1<<20
25_sequence_ids = {}
26
27# Timestamp of the last event from each thread. Used for delta-encoding
28# of timestamps.
29_last_timestamps = {}
30
31
32def _get_sequence_id(tid):
33 global _sequence_ids
34 global _next_sequence_id
35 if tid not in _sequence_ids:
36 _sequence_ids[tid] = _next_sequence_id
37 _next_sequence_id += 1
38 return _sequence_ids[tid]
39
40
41def _intern_category(category, trace_packet, tid):
42 global _interned_categories_by_tid
43 categories = _interned_categories_by_tid[tid]
44 if category not in categories:
45 # note that interning indices start from 1
46 categories[category] = len(categories) + 1
47 if trace_packet.interned_data is None:
48 trace_packet.interned_data = proto.InternedData()
49 trace_packet.interned_data.event_category = proto.EventCategory()
50 trace_packet.interned_data.event_category.iid = categories[category]
51 trace_packet.interned_data.event_category.name = category
52 return categories[category]
53
54
55def _intern_event_name(event_name, trace_packet, tid):
56 global _interned_event_names_by_tid
57 event_names = _interned_event_names_by_tid[tid]
58 if event_name not in event_names:
59 # note that interning indices start from 1
60 event_names[event_name] = len(event_names) + 1
61 if trace_packet.interned_data is None:
62 trace_packet.interned_data = proto.InternedData()
63 trace_packet.interned_data.legacy_event_name = proto.LegacyEventName()
64 trace_packet.interned_data.legacy_event_name.iid = event_names[event_name]
65 trace_packet.interned_data.legacy_event_name.name = event_name
66 return event_names[event_name]
67
68
69def write_thread_descriptor_event(output, pid, tid, ts):
70 """ Write the first event in a sequence.
71
72 Call this function before writing any other events.
73 Note that this function is NOT thread-safe.
74
75 Args:
76 output: a file-like object to write events into.
77 pid: process ID.
78 tid: thread ID.
79 ts: timestamp in microseconds.
80 """
81 global _last_timestamps
82 ts_us = int(ts)
83 _last_timestamps[tid] = ts_us
84
85 thread_descriptor_packet = proto.TracePacket()
86 thread_descriptor_packet.trusted_packet_sequence_id = _get_sequence_id(tid)
87 thread_descriptor_packet.thread_descriptor = proto.ThreadDescriptor()
88 thread_descriptor_packet.thread_descriptor.pid = pid
89 # Thread ID from threading module doesn't fit into int32.
90 # But we don't need the exact thread ID, just some number to
91 # distinguish one thread from another. We assume that the last 31 bits
92 # will do for that purpose.
93 thread_descriptor_packet.thread_descriptor.tid = tid & 0x7FFFFFFF
94 thread_descriptor_packet.thread_descriptor.reference_timestamp_us = ts_us
95 thread_descriptor_packet.incremental_state_cleared = True;
96
97 proto.write_trace_packet(output, thread_descriptor_packet)
98
99
100def write_event(output, ph, category, name, ts, args, tid):
101 """ Write a trace event.
102
103 Note that this function is NOT thread-safe.
104
105 Args:
106 output: a file-like object to write events into.
107 ph: phase of event.
108 category: category of event.
109 name: event name.
110 ts: timestamp in microseconds.
111 args: this argument is currently ignored.
112 tid: thread ID.
113 """
114 del args # TODO(khokhlov): Encode args as DebugAnnotations.
115
116 global _last_timestamps
117 ts_us = int(ts)
118 delta_ts = ts_us - _last_timestamps[tid]
119
120 packet = proto.TracePacket()
121 packet.trusted_packet_sequence_id = _get_sequence_id(tid)
122 packet.track_event = proto.TrackEvent()
123
124 if delta_ts >= 0:
125 packet.track_event.timestamp_delta_us = delta_ts
126 _last_timestamps[tid] = ts_us
127 else:
128 packet.track_event.timestamp_absolute_us = ts_us
129
130 packet.track_event.category_iids = [_intern_category(category, packet, tid)]
131 legacy_event = proto.LegacyEvent()
132 legacy_event.phase = ord(ph)
133 legacy_event.name_iid = _intern_event_name(name, packet, tid)
134 packet.track_event.legacy_event = legacy_event
135 proto.write_trace_packet(output, packet)
136
137
138def write_metadata(
139 output,
140 benchmark_start_time_us,
141 story_run_time_us,
142 benchmark_name,
143 benchmark_description,
144 story_name,
145 story_tags,
146 story_run_index,
147 label=None,
148 had_failures=None,
149):
150 metadata = proto.ChromeBenchmarkMetadata()
151 metadata.benchmark_start_time_us = int(benchmark_start_time_us)
152 metadata.story_run_time_us = int(story_run_time_us)
153 metadata.benchmark_name = benchmark_name
154 metadata.benchmark_description = benchmark_description
155 metadata.story_name = story_name
156 metadata.story_tags = list(story_tags)
157 metadata.story_run_index = int(story_run_index)
158 if label is not None:
159 metadata.label = label
160 if had_failures is not None:
161 metadata.had_failures = had_failures
162
163 packet = proto.TracePacket()
164 packet.chrome_benchmark_metadata = metadata
165 proto.write_trace_packet(output, packet)
166