blob: f03e83449d11700f399863a492c2ddc141f3925b [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart.developer;
const bool _isProduct = const bool.fromEnvironment("dart.vm.product");
typedef dynamic TimelineSyncFunction();
typedef Future TimelineAsyncFunction();
/// Add to the timeline.
class Timeline {
/// Start a synchronous operation labeled [name]. Optionally takes
/// a [Map] of [arguments]. This operation must be finished before
/// returning to the event queue.
static void startSync(String name, {Map arguments}) {
if (_isProduct) {
return;
}
if (name is! String) {
throw new ArgumentError.value(name, 'name', 'Must be a String');
}
if (!_isDartStreamEnabled()) {
// Push a null onto the stack and return.
_stack.add(null);
return;
}
var block = new _SyncBlock._(name, _getTraceClock(), _getThreadCpuClock());
if (arguments is Map) {
block._appendArguments(arguments);
}
_stack.add(block);
}
/// Finish the last synchronous operation that was started.
static void finishSync() {
if (_isProduct) {
return;
}
if (_stack.length == 0) {
throw new StateError('Uneven calls to startSync and finishSync');
}
// Pop top item off of stack.
var block = _stack.removeLast();
if (block == null) {
// Dart stream was disabled when startSync was called.
return;
}
// Finish it.
block.finish();
}
/// Emit an instant event.
static void instantSync(String name, {Map arguments}) {
if (_isProduct) {
return;
}
if (name is! String) {
throw new ArgumentError.value(name, 'name', 'Must be a String');
}
if (!_isDartStreamEnabled()) {
// Stream is disabled.
return;
}
Map instantArguments;
if (arguments is Map) {
instantArguments = new Map.from(arguments);
}
_reportInstantEvent(
_getTraceClock(), 'Dart', name, _argumentsAsJson(instantArguments));
}
/// A utility method to time a synchronous [function]. Internally calls
/// [function] bracketed by calls to [startSync] and [finishSync].
static dynamic timeSync(String name, TimelineSyncFunction function,
{Map arguments}) {
startSync(name, arguments: arguments);
try {
return function();
} finally {
finishSync();
}
}
/// The current time stamp from the clock used by the timeline. Units are
/// microseconds.
static int get now => _getTraceClock();
static final List<_SyncBlock> _stack = new List<_SyncBlock>();
static final int _isolateId = _getIsolateNum();
static final String _isolateIdString = _isolateId.toString();
}
/// An asynchronous task on the timeline. An asynchronous task can have many
/// (nested) synchronous operations. Synchronous operations can live longer than
/// the current isolate event. To pass a [TimelineTask] to another isolate,
/// you must first call [pass] to get the task id and then construct a new
/// [TimelineTask] in the other isolate.
class TimelineTask {
/// Create a task. [taskId] will be set by the system.
TimelineTask() : _taskId = _getNextAsyncId() {}
/// Create a task with an explicit [taskId]. This is useful if you are
/// passing a task from one isolate to another.
TimelineTask.withTaskId(int taskId) : _taskId = taskId {
if (taskId is! int) {
throw new ArgumentError.value(taskId, 'taskId', 'Must be an int');
}
}
/// Start a synchronous operation within this task named [name].
/// Optionally takes a [Map] of [arguments].
void start(String name, {Map arguments}) {
if (_isProduct) {
return;
}
if (name is! String) {
throw new ArgumentError.value(name, 'name', 'Must be a String');
}
var block = new _AsyncBlock._(name, _taskId);
if (arguments is Map) {
block._appendArguments(arguments);
}
_stack.add(block);
block._start();
}
/// Emit an instant event for this task.
void instant(String name, {Map arguments}) {
if (_isProduct) {
return;
}
if (name is! String) {
throw new ArgumentError.value(name, 'name', 'Must be a String');
}
Map instantArguments;
if (arguments is Map) {
instantArguments = new Map.from(arguments);
}
_reportTaskEvent(_getTraceClock(), _taskId, 'n', 'Dart', name,
_argumentsAsJson(instantArguments));
}
/// Finish the last synchronous operation that was started.
void finish() {
if (_isProduct) {
return;
}
if (_stack.length == 0) {
throw new StateError('Uneven calls to start and finish');
}
// Pop top item off of stack.
var block = _stack.removeLast();
block._finish();
}
/// Retrieve the [TimelineTask]'s task id. Will throw an exception if the
/// stack is not empty.
int pass() {
if (_stack.length > 0) {
throw new StateError(
'You cannot pass a TimelineTask without finishing all started '
'operations');
}
int r = _taskId;
return r;
}
final int _taskId;
final List<_AsyncBlock> _stack = [];
}
/// An asynchronous block of time on the timeline. This block can be kept
/// open across isolate messages.
class _AsyncBlock {
/// The category this block belongs to.
final String category = 'Dart';
/// The name of this block.
final String name;
/// The asynchronous task id.
final int _taskId;
/// An (optional) set of arguments which will be serialized to JSON and
/// associated with this block.
Map _arguments;
_AsyncBlock._(this.name, this._taskId);
// Emit the start event.
void _start() {
_reportTaskEvent(_getTraceClock(), _taskId, 'b', category, name,
_argumentsAsJson(_arguments));
}
// Emit the finish event.
void _finish() {
_reportTaskEvent(
_getTraceClock(), _taskId, 'e', category, name, _argumentsAsJson(null));
}
void _appendArguments(Map arguments) {
if (_arguments == null) {
_arguments = {};
}
_arguments.addAll(arguments);
}
}
/// A synchronous block of time on the timeline. This block should not be
/// kept open across isolate messages.
class _SyncBlock {
/// The category this block belongs to.
final String category = 'Dart';
/// The name of this block.
final String name;
/// An (optional) set of arguments which will be serialized to JSON and
/// associated with this block.
Map _arguments;
// The start time stamp.
final int _start;
// The start time stamp of the thread cpu clock.
final int _startCpu;
_SyncBlock._(this.name, this._start, this._startCpu);
/// Finish this block of time. At this point, this block can no longer be
/// used.
void finish() {
// Report event to runtime.
_reportCompleteEvent(
_start, _startCpu, category, name, _argumentsAsJson(_arguments));
}
void _appendArguments(Map arguments) {
if (arguments == null) {
return;
}
if (_arguments == null) {
_arguments = {};
}
_arguments.addAll(arguments);
}
}
String _fastPathArguments;
String _argumentsAsJson(Map arguments) {
if ((arguments == null) || (arguments.length == 0)) {
// Fast path no arguments. Avoid calling JSON.encode.
if (_fastPathArguments == null) {
_fastPathArguments = '{"isolateNumber":"${Timeline._isolateId}"}';
}
return _fastPathArguments;
}
// Add isolateNumber to arguments map.
arguments['isolateNumber'] = Timeline._isolateIdString;
return JSON.encode(arguments);
}
/// Returns true if the Dart Timeline stream is enabled.
bool _isDartStreamEnabled() native "Timeline_isDartStreamEnabled";
/// Returns the next async task id.
int _getNextAsyncId() native "Timeline_getNextAsyncId";
/// Returns the current value from the trace clock.
int _getTraceClock() native "Timeline_getTraceClock";
/// Returns the current value from the thread CPU usage clock.
int _getThreadCpuClock() native "Timeline_getThreadCpuClock";
/// Returns the isolate's main port number.
int _getIsolateNum() native "Timeline_getIsolateNum";
/// Reports an event for a task.
void _reportTaskEvent(int start, int taskId, String phase, String category,
String name, String argumentsAsJson) native "Timeline_reportTaskEvent";
/// Reports a complete synchronous event.
void _reportCompleteEvent(int start, int startCpu, String category, String name,
String argumentsAsJson) native "Timeline_reportCompleteEvent";
/// Reports an instant event.
void _reportInstantEvent(int start, String category, String name,
String argumentsAsJson) native "Timeline_reportInstantEvent";