aboutsummaryrefslogtreecommitdiff
path: root/catapult/common/py_trace_event/py_trace_event/trace_event_impl/perfetto_trace_writer.py
blob: 37809538797ad54368059b6fefb58afab70c9383 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

""" Functions to write trace data in perfetto protobuf format.
"""

import collections

import perfetto_proto_classes as proto



# Dicts of strings for interning.
# Note that each thread has its own interning index.
_interned_categories_by_tid = collections.defaultdict(dict)
_interned_event_names_by_tid = collections.defaultdict(dict)

# Trusted sequence ids from telemetry should not overlap with
# trusted sequence ids from other trace producers. Chrome assigns
# sequence ids incrementally starting from 1 and we expect all its ids
# to be well below 10000. Starting from 2^20 will give us enough
# confidence that it will not overlap.
_next_sequence_id = 1<<20
_sequence_ids = {}

# Timestamp of the last event from each thread. Used for delta-encoding
# of timestamps.
_last_timestamps = {}


def _get_sequence_id(tid):
  global _sequence_ids
  global _next_sequence_id
  if tid not in _sequence_ids:
    _sequence_ids[tid] = _next_sequence_id
    _next_sequence_id += 1
  return _sequence_ids[tid]


def _intern_category(category, trace_packet, tid):
  global _interned_categories_by_tid
  categories = _interned_categories_by_tid[tid]
  if category not in categories:
    # note that interning indices start from 1
    categories[category] = len(categories) + 1
    if trace_packet.interned_data is None:
      trace_packet.interned_data = proto.InternedData()
    trace_packet.interned_data.event_category = proto.EventCategory()
    trace_packet.interned_data.event_category.iid = categories[category]
    trace_packet.interned_data.event_category.name = category
  return categories[category]


def _intern_event_name(event_name, trace_packet, tid):
  global _interned_event_names_by_tid
  event_names = _interned_event_names_by_tid[tid]
  if event_name not in event_names:
    # note that interning indices start from 1
    event_names[event_name] = len(event_names) + 1
    if trace_packet.interned_data is None:
      trace_packet.interned_data = proto.InternedData()
    trace_packet.interned_data.legacy_event_name = proto.LegacyEventName()
    trace_packet.interned_data.legacy_event_name.iid = event_names[event_name]
    trace_packet.interned_data.legacy_event_name.name = event_name
  return event_names[event_name]


def write_thread_descriptor_event(output, pid, tid, ts):
  """ Write the first event in a sequence.

  Call this function before writing any other events.
  Note that this function is NOT thread-safe.

  Args:
    output: a file-like object to write events into.
    pid: process ID.
    tid: thread ID.
    ts: timestamp in microseconds.
  """
  global _last_timestamps
  ts_us = int(ts)
  _last_timestamps[tid] = ts_us

  thread_descriptor_packet = proto.TracePacket()
  thread_descriptor_packet.trusted_packet_sequence_id = _get_sequence_id(tid)
  thread_descriptor_packet.thread_descriptor = proto.ThreadDescriptor()
  thread_descriptor_packet.thread_descriptor.pid = pid
  # Thread ID from threading module doesn't fit into int32.
  # But we don't need the exact thread ID, just some number to
  # distinguish one thread from another. We assume that the last 31 bits
  # will do for that purpose.
  thread_descriptor_packet.thread_descriptor.tid = tid & 0x7FFFFFFF
  thread_descriptor_packet.thread_descriptor.reference_timestamp_us = ts_us
  thread_descriptor_packet.incremental_state_cleared = True;

  proto.write_trace_packet(output, thread_descriptor_packet)


def write_event(output, ph, category, name, ts, args, tid):
  """ Write a trace event.

  Note that this function is NOT thread-safe.

  Args:
    output: a file-like object to write events into.
    ph: phase of event.
    category: category of event.
    name: event name.
    ts: timestamp in microseconds.
    args: this argument is currently ignored.
    tid: thread ID.
  """
  del args  # TODO(khokhlov): Encode args as DebugAnnotations.

  global _last_timestamps
  ts_us = int(ts)
  delta_ts = ts_us - _last_timestamps[tid]

  packet = proto.TracePacket()
  packet.trusted_packet_sequence_id = _get_sequence_id(tid)
  packet.track_event = proto.TrackEvent()

  if delta_ts >= 0:
    packet.track_event.timestamp_delta_us = delta_ts
    _last_timestamps[tid] = ts_us
  else:
    packet.track_event.timestamp_absolute_us = ts_us

  packet.track_event.category_iids = [_intern_category(category, packet, tid)]
  legacy_event = proto.LegacyEvent()
  legacy_event.phase = ord(ph)
  legacy_event.name_iid = _intern_event_name(name, packet, tid)
  packet.track_event.legacy_event = legacy_event
  proto.write_trace_packet(output, packet)


def write_metadata(
    output,
    benchmark_start_time_us,
    story_run_time_us,
    benchmark_name,
    benchmark_description,
    story_name,
    story_tags,
    story_run_index,
    label=None,
    had_failures=None,
):
  metadata = proto.ChromeBenchmarkMetadata()
  metadata.benchmark_start_time_us = int(benchmark_start_time_us)
  metadata.story_run_time_us = int(story_run_time_us)
  metadata.benchmark_name = benchmark_name
  metadata.benchmark_description = benchmark_description
  metadata.story_name = story_name
  metadata.story_tags = list(story_tags)
  metadata.story_run_index = int(story_run_index)
  if label is not None:
    metadata.label = label
  if had_failures is not None:
    metadata.had_failures = had_failures

  packet = proto.TracePacket()
  packet.chrome_benchmark_metadata = metadata
  proto.write_trace_packet(output, packet)