Index: sdk/lib/developer/timeline.dart |
diff --git a/sdk/lib/developer/timeline.dart b/sdk/lib/developer/timeline.dart |
index 54c5b80f5ae89492bc667a170ee6d42f5a06b183..c42e5c0052bc0cce81f854ab901ff45b0ab4ab5d 100644 |
--- a/sdk/lib/developer/timeline.dart |
+++ b/sdk/lib/developer/timeline.dart |
@@ -32,8 +32,8 @@ class Timeline { |
} |
// Pop top item off of stack. |
var block = _stack.removeLast(); |
- // Close it. |
- block.close(); |
+ // Finish it. |
+ block.finish(); |
} |
/// A utility method to time a synchronous [function]. Internally calls |
@@ -52,6 +52,130 @@ class Timeline { |
static final List<_SyncBlock> _stack = new List<_SyncBlock>(); |
} |
+/// An asynchronous task on the timeline. Asynchronous tasks can live |
+/// longer than the current event and can even be shared between isolates. |
+/// An asynchronous task can have many (nested) blocks. To share a |
+/// [TimelineTask] across isolates, you must construct a [TimelineTask] in |
+/// both isolates using the same [taskId] and [category]. |
+class TimelineTask { |
+ /// Create a task. [taskId] will be set by the system. |
+ /// Optionally you can specify a [category] name. |
+ TimelineTask({String category: 'Dart'}) |
+ : _taskId = _getNextAsyncId(), |
+ category = category { |
+ if (category is! String) { |
+ throw new ArgumentError.value(category, |
+ 'category', |
+ 'Must be a String'); |
+ } |
+ } |
+ |
+ /// Create a task with an explicit [taskId]. This is useful if you are |
+ /// passing a task between isolates. Optionally you can specify a [category] |
+ /// name. |
+ TimelineTask.withTaskId(int taskId, {String category: 'Dart'}) |
+ : _taskId = taskId, |
+ category = category { |
+ if (taskId is! int) { |
+ throw new ArgumentError.value(taskId, |
+ 'taskId', |
+ 'Must be an int'); |
+ } |
+ if (category is! String) { |
+ throw new ArgumentError.value(category, |
+ 'category', |
+ 'Must be a String'); |
+ } |
+ } |
+ |
+ /// Start a block in this task named [name]. Optionally takes |
+ /// a [Map] of [arguments]. |
+ /// Returns an [AsyncBlock] which is used to finish this block. |
+ AsyncBlock start(String name, {Map arguments}) { |
+ if (name is! String) { |
+ throw new ArgumentError.value(name, |
+ 'name', |
+ 'Must be a String'); |
+ } |
+ var block = new AsyncBlock._(name, _taskId, category); |
+ if (arguments is Map) { |
+ block.arguments.addAll(arguments); |
+ } |
+ /// Emit start event. |
+ block._start(); |
+ return block; |
+ } |
+ |
+ /// Retrieve the asynchronous task's id. Can be used to construct a |
+ /// [TimelineTask] in another isolate. |
+ int get taskId => _taskId; |
+ final int _taskId; |
+ /// Retrieve the asynchronous task's category. Can be used to construct a |
+ /// [TimelineTask] in another isolate. |
+ final String category; |
+} |
+ |
+/// 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; |
+ /// 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. |
+ final Map arguments = {}; |
+ bool _finished = false; |
+ |
+ AsyncBlock._(this.name, this._taskId, this.category); |
+ |
+ // Emit the start event. |
+ void _start() { |
+ String argumentsAsJson = JSON.encode(arguments); |
+ _reportTaskEvent(_getTraceClock(), |
+ _taskId, |
+ 'b', |
+ category, |
+ name, |
+ argumentsAsJson); |
+ } |
+ |
+ // Emit the finish event. |
+ void _finish() { |
+ _reportTaskEvent(_getTraceClock(), |
+ _taskId, |
+ 'e', |
+ category, |
+ name, |
+ JSON.encode({})); |
+ } |
+ |
+ /// Finish this block. Cannot be called twice. |
+ void finish() { |
+ if (_finished) { |
+ throw new StateError( |
+ 'It is illegal to call finish twice on the same AsyncBlock'); |
+ } |
+ _finished = true; |
+ _finish(); |
+ } |
+ |
+ /// Finishes this block when [future] completes. Returns a [Future] |
+ /// chained to [future]. |
+ Future finishWhenComplete(Future future) { |
+ if (future is! Future) { |
+ throw new ArgumentError.value(future, |
+ 'future', |
+ 'Must be a Future'); |
+ } |
+ return future.whenComplete(() { |
+ finish(); |
+ }); |
+ } |
+} |
+ |
/// A synchronous block of time on the timeline. This block should not be |
/// kept open across isolate messages. |
class _SyncBlock { |
@@ -64,20 +188,13 @@ class _SyncBlock { |
final Map arguments = {}; |
// The start time stamp. |
final int _start; |
- // Has this block been closed? |
- bool _closed = false; |
_SyncBlock._(this.name, |
this._start); |
- /// Close this block of time. At this point, this block can no longer be |
+ /// Finish this block of time. At this point, this block can no longer be |
/// used. |
- void close() { |
- if (_closed) { |
- throw new StateError( |
- 'It is illegal to call close twice on the same _SyncBlock'); |
- } |
- _closed = true; |
+ void finish() { |
var end = _getTraceClock(); |
// Encode arguments map as JSON before reporting. |
@@ -92,9 +209,20 @@ class _SyncBlock { |
} |
} |
+/// Returns the next async task id. |
+external int _getNextAsyncId(); |
+ |
/// Returns the current value from the trace clock. |
external int _getTraceClock(); |
+/// 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 end, |