| // 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. |
| external bool _isDartStreamEnabled(); |
| |
| /// Returns the next async task id. |
| external int _getNextAsyncId(); |
| |
| /// Returns the current value from the trace clock. |
| external int _getTraceClock(); |
| |
| /// Returns the current value from the thread CPU usage clock. |
| external int _getThreadCpuClock(); |
| |
| /// Returns the isolate's main port number. |
| external int _getIsolateNum(); |
| |
| /// Reports an event for a task. |
| external void _reportTaskEvent(int start, |
| int taskId, |
| String phase, |
| String category, |
| String name, |
| String argumentsAsJson); |
| |
| /// Reports a complete synchronous event. |
| external void _reportCompleteEvent(int start, |
| int startCpu, |
| String category, |
| String name, |
| String argumentsAsJson); |
| |
| /// Reports an instant event. |
| external void _reportInstantEvent(int start, |
| String category, |
| String name, |
| String argumentsAsJson); |