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""" Functions to write trace data in perfetto protobuf format. 6""" 7 8import collections 9 10import perfetto_proto_classes as proto 11 12CLOCK_BOOTTIME = 6 13CLOCK_TELEMETRY = 64 14 15 16def reset_global_state(): 17 global _interned_categories_by_tid 18 global _interned_event_names_by_tid 19 global _next_sequence_id 20 global _sequence_ids 21 22 # Dicts of strings for interning. 23 # Note that each thread has its own interning index. 24 _interned_categories_by_tid = collections.defaultdict(dict) 25 _interned_event_names_by_tid = collections.defaultdict(dict) 26 27 # Trusted sequence ids from telemetry should not overlap with 28 # trusted sequence ids from other trace producers. Chrome assigns 29 # sequence ids incrementally starting from 1 and we expect all its ids 30 # to be well below 10000. Starting from 2^20 will give us enough 31 # confidence that it will not overlap. 32 _next_sequence_id = 1<<20 33 _sequence_ids = {} 34 35 36reset_global_state() 37 38 39def _get_sequence_id(tid): 40 global _sequence_ids 41 global _next_sequence_id 42 if tid not in _sequence_ids: 43 _sequence_ids[tid] = _next_sequence_id 44 _next_sequence_id += 1 45 return _sequence_ids[tid] 46 47 48def _intern_category(category, trace_packet, tid): 49 global _interned_categories_by_tid 50 categories = _interned_categories_by_tid[tid] 51 if category not in categories: 52 # note that interning indices start from 1 53 categories[category] = len(categories) + 1 54 if trace_packet.interned_data is None: 55 trace_packet.interned_data = proto.InternedData() 56 trace_packet.interned_data.event_category = proto.EventCategory() 57 trace_packet.interned_data.event_category.iid = categories[category] 58 trace_packet.interned_data.event_category.name = category 59 return categories[category] 60 61 62def _intern_event_name(event_name, trace_packet, tid): 63 global _interned_event_names_by_tid 64 event_names = _interned_event_names_by_tid[tid] 65 if event_name not in event_names: 66 # note that interning indices start from 1 67 event_names[event_name] = len(event_names) + 1 68 if trace_packet.interned_data is None: 69 trace_packet.interned_data = proto.InternedData() 70 trace_packet.interned_data.legacy_event_name = proto.LegacyEventName() 71 trace_packet.interned_data.legacy_event_name.iid = event_names[event_name] 72 trace_packet.interned_data.legacy_event_name.name = event_name 73 return event_names[event_name] 74 75 76def write_thread_descriptor_event(output, pid, tid, ts): 77 """Write the first event in a sequence. 78 79 Call this function before writing any other events. 80 Note that this function is NOT thread-safe. 81 82 Args: 83 output: a file-like object to write events into. 84 pid: process ID. 85 tid: thread ID. 86 ts: timestamp in microseconds. 87 """ 88 thread_descriptor_packet = proto.TracePacket() 89 thread_descriptor_packet.trusted_packet_sequence_id = _get_sequence_id(tid) 90 thread_descriptor_packet.timestamp = int(ts * 1e3) 91 thread_descriptor_packet.timestamp_clock_id = CLOCK_TELEMETRY 92 93 thread_descriptor_packet.thread_descriptor = proto.ThreadDescriptor() 94 thread_descriptor_packet.thread_descriptor.pid = pid 95 # Thread ID from threading module doesn't fit into int32. 96 # But we don't need the exact thread ID, just some number to 97 # distinguish one thread from another. We assume that the last 31 bits 98 # will do for that purpose. 99 thread_descriptor_packet.thread_descriptor.tid = tid & 0x7FFFFFFF 100 thread_descriptor_packet.incremental_state_cleared = True; 101 102 proto.write_trace_packet(output, thread_descriptor_packet) 103 104 105def write_event(output, ph, category, name, ts, args, tid): 106 """Write a trace event. 107 108 Note that this function is NOT thread-safe. 109 110 Args: 111 output: a file-like object to write events into. 112 ph: phase of event. 113 category: category of event. 114 name: event name. 115 ts: timestamp in microseconds. 116 args: dict of arbitrary key-values to be stored as DebugAnnotations. 117 tid: thread ID. 118 """ 119 packet = proto.TracePacket() 120 packet.trusted_packet_sequence_id = _get_sequence_id(tid) 121 packet.timestamp = int(ts * 1e3) 122 packet.timestamp_clock_id = CLOCK_TELEMETRY 123 124 packet.track_event = proto.TrackEvent() 125 packet.track_event.category_iids = [_intern_category(category, packet, tid)] 126 legacy_event = proto.LegacyEvent() 127 legacy_event.phase = ord(ph) 128 legacy_event.name_iid = _intern_event_name(name, packet, tid) 129 packet.track_event.legacy_event = legacy_event 130 131 for name, value in args.iteritems(): 132 debug_annotation = proto.DebugAnnotation() 133 debug_annotation.name = name 134 if isinstance(value, int): 135 debug_annotation.int_value = value 136 elif isinstance(value, float): 137 debug_annotation.double_value = value 138 else: 139 debug_annotation.string_value = str(value) 140 packet.track_event.debug_annotations.append(debug_annotation) 141 142 proto.write_trace_packet(output, packet) 143 144 145def write_chrome_metadata(output, clock_domain): 146 """Write a chrome trace event with metadata. 147 148 Args: 149 output: a file-like object to write events into. 150 clock_domain: a string representing the trace clock domain. 151 """ 152 chrome_metadata = proto.ChromeMetadata() 153 chrome_metadata.name = 'clock-domain' 154 chrome_metadata.string_value = clock_domain 155 chrome_event = proto.ChromeEventBundle() 156 chrome_event.metadata.append(chrome_metadata) 157 packet = proto.TracePacket() 158 packet.chrome_event = chrome_event 159 proto.write_trace_packet(output, packet) 160 161 162def write_metadata( 163 output, 164 benchmark_start_time_us, 165 story_run_time_us, 166 benchmark_name, 167 benchmark_description, 168 story_name, 169 story_tags, 170 story_run_index, 171 label=None, 172): 173 """Write a ChromeBenchmarkMetadata message.""" 174 metadata = proto.ChromeBenchmarkMetadata() 175 metadata.benchmark_start_time_us = int(benchmark_start_time_us) 176 metadata.story_run_time_us = int(story_run_time_us) 177 metadata.benchmark_name = benchmark_name 178 metadata.benchmark_description = benchmark_description 179 metadata.story_name = story_name 180 metadata.story_tags = list(story_tags) 181 metadata.story_run_index = int(story_run_index) 182 if label is not None: 183 metadata.label = label 184 185 packet = proto.TracePacket() 186 packet.chrome_benchmark_metadata = metadata 187 proto.write_trace_packet(output, packet) 188 189 190def write_clock_snapshot( 191 output, 192 tid, 193 telemetry_ts=None, 194 boottime_ts=None, 195): 196 """Write a ClockSnapshot message. 197 198 Note that this function is NOT thread-safe. 199 200 Args: 201 output: a file-like object to write events into. 202 telemetry_ts: host BOOTTIME timestamp in microseconds. 203 boottime_ts: device BOOTTIME timestamp in microseconds. 204 """ 205 clock_snapshot = proto.ClockSnapshot() 206 if telemetry_ts is not None: 207 clock = proto.Clock() 208 clock.clock_id = CLOCK_TELEMETRY 209 clock.timestamp = int(telemetry_ts * 1e3) 210 clock_snapshot.clocks.append(clock) 211 if boottime_ts is not None: 212 clock = proto.Clock() 213 clock.clock_id = CLOCK_BOOTTIME 214 clock.timestamp = int(boottime_ts * 1e3) 215 clock_snapshot.clocks.append(clock) 216 packet = proto.TracePacket() 217 packet.trusted_packet_sequence_id = _get_sequence_id(tid) 218 packet.clock_snapshot = clock_snapshot 219 proto.write_trace_packet(output, packet) 220