Index: runtime/bin/vmservice/client/lib/src/service/object.dart |
diff --git a/runtime/bin/vmservice/client/lib/src/service/object.dart b/runtime/bin/vmservice/client/lib/src/service/object.dart |
index 4dd87c459bae2d9c1ab2117db18c99f8dbd2d80b..828c1ecd0a460c230024f36812398bef5c3b42d0 100644 |
--- a/runtime/bin/vmservice/client/lib/src/service/object.dart |
+++ b/runtime/bin/vmservice/client/lib/src/service/object.dart |
@@ -53,6 +53,9 @@ abstract class ServiceObject extends Observable { |
/// Creates a [ServiceObject] initialized from [map]. |
factory ServiceObject._fromMap(ServiceObjectOwner owner, |
ObservableMap map) { |
+ if (map == null) { |
+ return null; |
+ } |
if (!_isServiceMap(map)) { |
Logger.root.severe('Malformed service object: $map'); |
} |
@@ -65,11 +68,17 @@ abstract class ServiceObject extends Observable { |
obj = new Code._empty(owner); |
break; |
case 'Error': |
- obj = new ServiceError._empty(owner); |
+ obj = new DartError._empty(owner); |
break; |
case 'Isolate': |
obj = new Isolate._empty(owner); |
break; |
+ case 'ServiceError': |
+ obj = new ServiceError._empty(owner); |
+ break; |
+ case 'ServiceException': |
+ obj = new ServiceException._empty(owner); |
+ break; |
case 'Script': |
obj = new Script._empty(owner); |
break; |
@@ -168,8 +177,13 @@ abstract class VM extends ServiceObjectOwner { |
update(toObservable({'id':'vm', 'type':'@VM'})); |
} |
+ final StreamController<ServiceException> exceptions = |
+ new StreamController.broadcast(); |
+ final StreamController<ServiceError> errors = |
+ new StreamController.broadcast(); |
+ |
static final RegExp _currentIsolateMatcher = new RegExp(r'isolates/\d+'); |
- static final RegExp _currentObjectMatcher = new RegExp(r'isolates/\d+(/|$)'); |
+ static final RegExp _currentObjectMatcher = new RegExp(r'isolates/\d+/'); |
static final String _isolatesPrefix = 'isolates/'; |
String _parseObjectId(String id) { |
@@ -248,28 +262,54 @@ abstract class VM extends ServiceObjectOwner { |
}); |
} |
- /// Gets [id] as an [ObservableMap] from the service directly. |
+ /// Gets [id] as an [ObservableMap] from the service directly. If |
+ /// an error occurs, the future is completed as an error with a |
+ /// ServiceError or ServiceException. Therefore any chained then() calls |
+ /// will only receive a map encoding a valid ServiceObject. |
Future<ObservableMap> getAsMap(String id) { |
return getString(id).then((response) { |
try { |
- var map = JSON.decode(response); |
- return toObservable(map); |
+ var map = toObservable(JSON.decode(response)); |
+ // Verify that the top level response is a service map. |
+ if (!_isServiceMap(map)) { |
+ return new Future.error( |
+ new ServiceObject._fromMap(this, toObservable({ |
+ 'type': 'ServiceException', |
+ 'id': '', |
+ 'kind': 'FormatException', |
+ 'response': map, |
+ 'message': 'Top level service responses must be service maps.', |
+ }))); |
+ } |
+ // Preemptively capture ServiceError and ServiceExceptions. |
+ if (map['type'] == 'ServiceError') { |
+ return new Future.error(new ServiceObject._fromMap(this, map)); |
+ } else if (map['type'] == 'ServiceException') { |
+ return new Future.error(new ServiceObject._fromMap(this, map)); |
+ } |
+ // map is now guaranteed to be a non-error/exception ServiceObject. |
+ return map; |
} catch (e, st) { |
- return toObservable({ |
- 'type': 'Error', |
+ print(e); |
+ print(st); |
+ return new Future.error( |
+ new ServiceObject._fromMap(this, toObservable({ |
+ 'type': 'ServiceException', |
'id': '', |
- 'kind': 'DecodeError', |
- 'message': '$e', |
- }); |
+ 'kind': 'DecodeException', |
+ 'response': response, |
+ 'message': 'Could not decode JSON: $e', |
+ }))); |
} |
}).catchError((error) { |
- return toObservable({ |
- 'type': 'Error', |
- 'id': '', |
- 'kind': 'LastResort', |
- 'message': '$error' |
- }); |
- }); |
+ // ServiceError, forward to VM's ServiceError stream. |
+ errors.add(error); |
+ return new Future.error(error); |
+ }, test: (e) => e is ServiceError).catchError((exception) { |
+ // ServiceException, forward to VM's ServiceException stream. |
+ exceptions.add(exception); |
+ return new Future.error(exception); |
+ }, test: (e) => e is ServiceException); |
} |
/// Get [id] as a [String] from the service directly. See [getAsMap]. |
@@ -309,6 +349,89 @@ abstract class VM extends ServiceObjectOwner { |
} |
} |
+/// Snapshot in time of tag counters. |
+class TagProfileSnapshot { |
+ final double seconds; |
+ final List<int> counters; |
+ int get sum => _sum; |
+ int _sum = 0; |
+ TagProfileSnapshot(this.seconds, int countersLength) |
+ : counters = new List<int>(countersLength); |
+ |
+ /// Set [counters] and update [sum]. |
+ void set(List<int> counters) { |
+ this.counters.setAll(0, counters); |
+ for (var i = 0; i < this.counters.length; i++) { |
+ _sum += this.counters[i]; |
+ } |
+ } |
+ |
+ /// Set [counters] with the delta from [counters] to [old_counters] |
+ /// and update [sum]. |
+ void delta(List<int> counters, List<int> old_counters) { |
+ for (var i = 0; i < this.counters.length; i++) { |
+ this.counters[i] = counters[i] - old_counters[i]; |
+ _sum += this.counters[i]; |
+ } |
+ } |
+ |
+ /// Update [counters] with new maximum values seen in [counters]. |
+ void max(List<int> counters) { |
+ for (var i = 0; i < counters.length; i++) { |
+ var c = counters[i]; |
+ this.counters[i] = this.counters[i] > c ? this.counters[i] : c; |
+ } |
+ } |
+ |
+ /// Zero [counters]. |
+ void zero() { |
+ for (var i = 0; i < counters.length; i++) { |
+ counters[i] = 0; |
+ } |
+ } |
+} |
+ |
+class TagProfile { |
+ final List<String> names = new List<String>(); |
+ final List<TagProfileSnapshot> snapshots = new List<TagProfileSnapshot>(); |
+ double get updatedAtSeconds => _seconds; |
+ double _seconds; |
+ TagProfileSnapshot _maxSnapshot; |
+ int _historySize; |
+ int _countersLength = 0; |
+ |
+ TagProfile(this._historySize); |
+ |
+ void _processTagProfile(double seconds, ObservableMap tagProfile) { |
+ _seconds = seconds; |
+ var counters = tagProfile['counters']; |
+ if (names.length == 0) { |
+ // Initialization. |
+ names.addAll(tagProfile['names']); |
+ _countersLength = tagProfile['counters'].length; |
+ for (var i = 0; i < _historySize; i++) { |
+ var snapshot = new TagProfileSnapshot(0.0, _countersLength); |
+ snapshot.zero(); |
+ snapshots.add(snapshot); |
+ } |
+ // The counters monotonically grow, keep track of the maximum value. |
+ _maxSnapshot = new TagProfileSnapshot(0.0, _countersLength); |
+ _maxSnapshot.set(counters); |
+ return; |
+ } |
+ var snapshot = new TagProfileSnapshot(seconds, _countersLength); |
+ // We snapshot the delta from the current counters to the maximum counter |
+ // values. |
+ snapshot.delta(counters, _maxSnapshot.counters); |
+ _maxSnapshot.max(counters); |
+ snapshots.add(snapshot); |
+ // Only keep _historySize snapshots. |
+ if (snapshots.length > _historySize) { |
+ snapshots.removeAt(0); |
+ } |
+ } |
+} |
+ |
/// State for a running isolate. |
class Isolate extends ServiceObjectOwner { |
@reflectable VM get vm => owner; |
@@ -323,8 +446,11 @@ class Isolate extends ServiceObjectOwner { |
@observable bool idle = false; |
Map<String,ServiceObject> _cache = new Map<String,ServiceObject>(); |
+ final TagProfile tagProfile = new TagProfile(20); |
- Isolate._empty(ServiceObjectOwner owner) : super._empty(owner); |
+ Isolate._empty(ServiceObjectOwner owner) : super._empty(owner) { |
+ assert(owner is VM); |
+ } |
/// Creates a link to [id] relative to [this]. |
@reflectable String relativeLink(String id) => '${this.id}/$id'; |
@@ -412,6 +538,8 @@ class Isolate extends ServiceObjectOwner { |
} |
Future<ServiceObject> get(String id) { |
+ // Do not allow null ids or empty ids. |
+ assert(id != null && id != ''); |
var obj = _cache[id]; |
if (obj != null) { |
return obj.reload(); |
@@ -444,6 +572,8 @@ class Isolate extends ServiceObjectOwner { |
@observable String fileAndLine; |
+ @observable DartError error; |
+ |
void _update(ObservableMap map, bool mapIsRef) { |
mainPort = map['mainPort']; |
name = map['name']; |
@@ -492,6 +622,15 @@ class Isolate extends ServiceObjectOwner { |
pausedOnExit = map['pausedOnExit']; |
running = map['topFrame'] != null; |
idle = !pausedOnStart && !pausedOnExit && !running; |
+ error = map['error']; |
+ } |
+ |
+ Future<TagProfile> updateTagProfile() { |
+ return vm.getAsMap(relativeLink('profile/tag')).then((ObservableMap m) { |
+ var seconds = new DateTime.now().millisecondsSinceEpoch / 1000.0; |
+ tagProfile._processTagProfile(seconds, m); |
+ return tagProfile; |
+ }); |
} |
@reflectable CodeTrieNode profileTrieRoot; |
@@ -540,6 +679,7 @@ class Isolate extends ServiceObjectOwner { |
} |
} |
+ |
turnidge
2014/03/25 17:52:53
Extra blank line.
Cutch
2014/03/25 18:01:52
Done.
|
/// A [ServiceObject] which implements [ObservableMap]. |
class ServiceMap extends ServiceObject implements ObservableMap { |
final ObservableMap _map = new ObservableMap(); |
@@ -603,6 +743,29 @@ class ServiceMap extends ServiceObject implements ObservableMap { |
bool get hasObservers => _map.hasObservers; |
} |
+/// A [DartError] is peered to a Dart Error object. |
+class DartError extends ServiceObject { |
+ DartError._empty(ServiceObject owner) : super._empty(owner); |
+ |
+ @observable String kind; |
+ @observable String message; |
+ @observable ServiceMap exception; |
+ @observable ServiceMap stacktrace; |
+ |
+ void _update(ObservableMap map, bool mapIsRef) { |
+ kind = map['kind']; |
+ message = map['message']; |
+ exception = new ServiceObject._fromMap(owner, map['exception']); |
+ stacktrace = new ServiceObject._fromMap(owner, map['stacktrace']); |
+ name = 'DartError $kind'; |
+ vmName = name; |
+ } |
+} |
+ |
+/// A [ServiceError] is an error that was triggered in the service |
+/// server or client. Errors are prorammer mistakes that could have |
+/// been prevented, for example, requesting a non-existant path over the |
+/// service. |
class ServiceError extends ServiceObject { |
ServiceError._empty(ServiceObjectOwner owner) : super._empty(owner); |
@@ -616,8 +779,25 @@ class ServiceError extends ServiceObject { |
name = 'ServiceError $kind'; |
vmName = name; |
} |
+} |
- // TODO: stackTrace? |
+/// A [ServiceException] is an exception that was triggered in the service |
+/// server or client. Exceptions are events that should be handled, |
+/// for example, an isolate went away or the connection to the VM was lost. |
+class ServiceException extends ServiceObject { |
+ ServiceException._empty(ServiceObject owner) : super._empty(owner); |
+ |
+ @observable String kind; |
+ @observable String message; |
+ @observable dynamic response; |
+ |
+ void _update(ObservableMap map, bool mapIsRef) { |
+ kind = map['kind']; |
+ message = map['message']; |
+ response = map['response']; |
+ name = 'ServiceException $kind'; |
+ vmName = name; |
+ } |
} |
class ScriptLine { |