blob: f48261d6fc6dd24fbcd61b9e76beb29d87593542 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart=2.12
import 'time_delta.dart';
import 'time_point.dart';
/// The root of the trace model.
class Model {
List<Process> processes = [];
/// Extract a sub-Model defined by a time interval.
///
/// The returned [Model] will act similarly to as if we actually started
/// tracing at [start] and stopped tracing at [end]. Only trace events that
/// begin at or after [start] and end at or before [end] will be included.
///
/// If [start] or [end] is null, then the time interval to select is infinite
/// in that direction.
Model slice(TimePoint? start, TimePoint? end) {
final result = Model();
// The various event types have references to other events, which we will
// need to update so that all the relations in the new model stay within the
// new model. This map tracks for each event of the old model, which event
// in the new model it corresponds to.
final correspondingEvent = {};
// Step 1: Populate the model with new event objects. These events will have
// references into the old model.
for (final process in processes) {
final newProcess = Process(process.pid, name: process.name);
result.processes.add(newProcess);
for (final thread in process.threads) {
final newThread = Thread(thread.tid, name: thread.name);
newProcess.threads.add(newThread);
for (final event in thread.events) {
// Exclude any event that starts or ends outside of the specified range.
if ((start != null && event.start < start) ||
(end != null && event.start > end)) {
continue;
}
Event? newEvent;
if (event is InstantEvent) {
newEvent = InstantEvent(event.scope, event.category, event.name,
event.start, event.pid, event.tid, Map.from(event.args));
} else if (event is CounterEvent) {
newEvent = CounterEvent(event.id, event.category, event.name,
event.start, event.pid, event.tid, Map.from(event.args));
} else if (event is DurationEvent) {
if (end != null && event.duration! > end - event.start) {
continue;
}
newEvent = DurationEvent(
event.duration,
event.parent,
event.childDurations,
event.childFlows,
event.category,
event.name,
event.start,
event.pid,
event.tid,
Map.from(event.args));
} else if (event is AsyncEvent) {
if (end != null && event.duration! > end - event.start) {
continue;
}
newEvent = AsyncEvent(
event.id,
event.duration,
event.category,
event.name,
event.start,
event.pid,
event.tid,
Map.from(event.args));
} else if (event is FlowEvent) {
newEvent = FlowEvent(
event.id,
event.phase,
event.enclosingDuration,
event.previousFlow,
event.nextFlow,
event.category,
event.name,
event.start,
event.pid,
event.tid,
Map.from(event.args));
}
if (newEvent == null) {
throw FormatException('Unknown event type: $event');
}
newThread.events.add(newEvent);
correspondingEvent[event] = newEvent;
}
}
}
// Step 2: Replace all referenced events by their corresponding ones in the
// new model.
for (final process in result.processes) {
for (final thread in process.threads) {
for (final event in thread.events) {
if (event is DurationEvent) {
event
..parent = correspondingEvent[event.parent] as DurationEvent
..childDurations = event.childDurations
.map((e) => correspondingEvent[e] as DurationEvent)
.toList()
..childFlows = event.childFlows
.map((e) => correspondingEvent[e] as FlowEvent)
.toList();
} else if (event is FlowEvent) {
event
..enclosingDuration =
correspondingEvent[event.enclosingDuration] as DurationEvent
..previousFlow =
correspondingEvent[event.previousFlow] as FlowEvent
..nextFlow = correspondingEvent[event.nextFlow] as FlowEvent;
}
}
}
}
return result;
}
}
/// Represents a Process in the trace model.
class Process {
/// The process id of this [Process].
int pid;
/// The process name of this [Process].
String name;
/// A list of [Thread]s that belong to this [Process].
List<Thread> threads;
Process(this.pid, {this.name = '', List<Thread>? threads})
: threads = threads ?? [];
}
/// Represents a Thread in the trace model.
class Thread {
/// The thread id of this [Thread].
int tid;
/// The thread name of this [Thread].
String name;
/// A list of [Event]s that belong to this [Thread].
List<Event> events;
Thread(this.tid, {this.name = '', List<Event>? events})
: events = events ?? [];
}
/// A base class for all trace events in the trace model. Contains
/// fields that are common to all trace event types.
abstract class Event {
/// The category of this [Event].
String category;
/// The name of this [Event].
String name;
/// The timestamp this [Event] started at.
TimePoint start;
/// The process id that this [Event] belongs to.
int pid;
/// The thread id that this [Event] belongs to.
int tid;
/// Extra arguments that the [Event] contains.
Map<String, dynamic> args = {};
Event(this.category, this.name, this.start, this.pid, this.tid, this.args);
Event.fromJson(Map<String, dynamic> json)
: category = json['cat']!,
name = json['name']!,
start =
TimePoint.fromEpochDelta(TimeDelta.fromMicroseconds(json['ts']!)),
pid = json['pid']!,
tid = json['tid']!.toInt(),
args = json['args'] ?? {};
}
/// Represents all different scopes for [InstantEvent]s.
enum InstantEventScope { thread, process, global }
/// Represents an instant event, which is an event that corresponds to a single
/// moment in time.
class InstantEvent extends Event {
InstantEventScope scope;
InstantEvent(
this.scope, category, name, start, pid, tid, Map<String, dynamic> args)
: super(category, name, start, pid, tid, args);
InstantEvent.fromJson(Map<String, dynamic> json)
: scope = (() {
final scopeField = json['s'];
if (scopeField != null && !(scopeField is String)) {
throw FormatException(
'Expected $scopeField to have field "s" of type String');
}
return {
'g': InstantEventScope.global,
'p': InstantEventScope.process,
't': InstantEventScope.thread
}[scopeField] ??
(throw FormatException(
'Expected "s" (scope) field of $scopeField value in {"g", "p", "t"}'));
})(),
super.fromJson(json);
}
/// Represents a counter event.
class CounterEvent extends Event {
int? id;
CounterEvent(
this.id, category, name, start, pid, tid, Map<String, dynamic> args)
: super(category, name, start, pid, tid, args);
CounterEvent.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
if (json.containsKey('id')) {
if (json['id'] is int) {
id = json['id'];
} else if (json['id'] is String) {
id = int.tryParse(json['id']);
}
if (id == null) {
throw FormatException(
'Expected $json with "id" field set to be of type int '
'or a string that parses as an int');
}
}
}
}
/// Represents a duration event. Durations describe work which is happening
/// synchronously on one thread. In our trace model, matching begin/end duration
/// in the raw Chrome trace format are merged into a single [DurationEvent].
/// Chrome complete events become [DurationEvent]s as well. Dangling Chrome
/// begin/end events (i.e. they don't have a matching end/begin event) are
/// dropped.
class DurationEvent extends Event {
/// The duration of the [DurationEvent]. The "dur" field for complete events,
/// and end['ts'] - begin['ts'] for begin/end pairs.
TimeDelta? duration;
/// The [DurationEvent]'s parent [DurationEvent] in the duration stack. [null]
/// if the event has no parent event (i.e. it is the base of the duration
/// stack).
DurationEvent? parent;
/// A list of [DurationEvent]s that are children of this [DurationEvent].
/// I.e., this event is their parent.
List<DurationEvent> childDurations;
/// A list of [FlowEvent]s that have this [DurationEvent] as their enclosing
/// duration.
List<FlowEvent> childFlows;
DurationEvent(
this.duration,
this.parent,
this.childDurations,
this.childFlows,
category,
name,
start,
pid,
tid,
Map<String, dynamic> args)
: super(category, name, start, pid, tid, args);
DurationEvent.fromJson(Map<String, dynamic> json)
: duration = (() {
final microseconds = json['dur'];
if (microseconds == null) {
return null;
}
if (!(microseconds is double || microseconds is int)) {
throw FormatException(
'Expected $json to have field "dur" of type double or int');
}
return TimeDelta.fromMicroseconds(microseconds);
})(),
parent = null,
childDurations = [],
childFlows = [],
super.fromJson(json);
}
/// Represents an async event. Asynchronous events describe work which is
/// happening asynchronously and which may span multiple threads. Dangling
/// Chrome async begin/end events are dropped.
class AsyncEvent extends Event {
int id;
/// The duration of the async begin/end pair.
TimeDelta? duration;
AsyncEvent(this.id, this.duration, category, name, start, pid, tid,
Map<String, dynamic> args)
: super(category, name, start, pid, tid, args);
AsyncEvent.fromJson(this.id, Map<String, dynamic> json)
: super.fromJson(json);
}
/// Represents all different phases of flow events.
enum FlowEventPhase { start, step, end }
/// Represents a flow event. Flow events describe control flow handoffs between
/// threads or across processes.
///
/// Malformed flow events are dropped. Malformed flow events could be any of:
/// * A begin flow event with a (category, name, id) tuple already in progress
/// (this is uncommon in practice).
/// * A step flow event with no preceding (category, name, id) tuple.
/// * An end flow event with no preceding (category, name, id) tuple.
class FlowEvent extends Event {
String id;
/// The phase of the [FlowEvent].
FlowEventPhase phase;
/// The enclosing duration that the [FlowEvent] belongs to. This field should
/// not usually be null, because [FlowEvent]s without enclosing durations are
/// considered to be malformed. However, it may be null if the trace is
/// malformed or while the trace is being imported.
///
/// In the case of a Chrome trace, this field stores the "bound"
/// [DurationEvent], which may be either the enclosing duration or the next
/// duration, depending on the defined binding point.
DurationEvent? enclosingDuration;
/// The previous flow event in the flow sequence. Will be null for begin flow
/// events, and will never be null for step and end flow events.
FlowEvent? previousFlow;
/// The next flow event in the flow sequence. Will never be null for begin and
/// step flow events, and will be null for end flow events.
FlowEvent? nextFlow;
FlowEvent(this.id, this.phase, this.enclosingDuration, this.previousFlow,
this.nextFlow, category, name, start, pid, tid, Map<String, dynamic> args)
: super(category, name, start, pid, tid, args);
FlowEvent.fromJson(this.id, this.enclosingDuration, Map<String, dynamic> json)
: phase = (() {
return {
's': FlowEventPhase.start,
't': FlowEventPhase.step,
'f': FlowEventPhase.end
}[json['ph']]!;
})(),
super.fromJson(json);
}