blob: 37809538797ad54368059b6fefb58afab70c9383 [file] [log] [blame]
# 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)