Oussama Ben Abdelbaki | dcd74cf | 2020-08-10 14:00:36 -0400 | [diff] [blame] | 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 | |
| 8 | import collections |
| 9 | |
| 10 | import 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 | |
| 32 | def _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 | |
| 41 | def _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 | |
| 55 | def _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 | |
| 69 | def 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 | |
| 100 | def 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 | |
| 138 | def 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 | |