Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(792)

Unified Diff: runtime/bin/vmservice/client/lib/src/service/object.dart

Issue 192443004: Complete the switch to ServiceObject (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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
new file mode 100644
index 0000000000000000000000000000000000000000..f92a20d13c42bb05ee2c1cb80d787beb76b59bbf
--- /dev/null
+++ b/runtime/bin/vmservice/client/lib/src/service/object.dart
@@ -0,0 +1,621 @@
+// Copyright (c) 2014, 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 service;
+
+/// State for a running isolate.
+class Isolate extends ServiceObject {
+ final VM vm;
+ String get link => _id;
+ String get hashLink => '#/$_id';
+
+ ScriptCache _scripts;
turnidge 2014/03/10 21:03:28 consider blank lines between decls.
+ /// Script cache.
+ ScriptCache get scripts => _scripts;
+ CodeCache _codes;
+ /// Code cache.
+ CodeCache get codes => _codes;
+ /// Class cache.
+ ClassCache _classes;
+ ClassCache get classes => _classes;
+
+ void _initOnce() {
+ // Only called once.
+ assert(_isolate == null);
+ _isolate = this;
+ _scripts = new ScriptCache(this);
+ _codes = new CodeCache(this);
+ _classes = new ClassCache(this);
+ }
+
+ Isolate.fromId(this.vm, String id) : super(null, id, '@Isolate') {
+ _initOnce();
+ }
+
+ Isolate.fromMap(this.vm, Map map) : super.fromMap(null, map) {
+ _initOnce();
+ }
+
+ /// Creates a link to [id] relative to [this].
+ @reflectable String relativeLink(String id) => '${this.id}/$id';
+ /// Creates a relative link to [id] with a '#/' prefix.
+ @reflectable String relativeHashLink(String id) => '#/${relativeLink(id)}';
+
+ Future<ScriptCache> refreshCoverage() {
+ return fetch('coverage').then(_scripts._processCoverage);
+ }
+
+ void processProfile(ServiceMap profile) {
+ assert(profile.serviceType == 'Profile');
+ var codeTable = new List<Code>();
+ var profileCodes = profile['codes'];
+ for (var profileCode in profileCodes) {
+ Code code = profileCode['code'];
+ codeTable.add(code);
+ }
+ _codes._resetProfileData();
+ _codes._updateProfileData(profile, codeTable);
+ }
+
+ /// Requests [serviceId] from [this]. Completes to a [ServiceObject].
+ /// Can return pre-existing, cached, [ServiceObject]s.
+ Future<ServiceObject> get(String serviceId) {
+ if (_scripts.cachesId(serviceId)) {
+ return _scripts.get(serviceId);
+ }
+ if (_codes.cachesId(serviceId)) {
+ return _codes.get(serviceId);
+ }
+ if (_classes.cachesId(serviceId)) {
+ return _classes.get(serviceId);
+ }
+ return vm.get(this, relativeLink(serviceId));
+ }
+
+ /// Fetches [serviceId] from [this]. Completes to a [ServiceObject].
+ /// Always creates a new ServiceObject.
+ Future<ServiceObject> fetch(String serviceId, [Map options]) {
turnidge 2014/03/10 21:03:28 I find the distinction between the names "get" and
+ return vm.get(this, relativeLink(serviceId));
+ }
+
+
turnidge 2014/03/10 21:03:28 Remove one blank line here?
+ @observable ServiceMap rootLib;
+ @observable ServiceMap topFrame;
+
+ @observable String name;
+ @observable String vmName;
+ @observable Map entry;
+
+ @observable final Map<String, double> timers =
+ toObservable(new Map<String, double>());
+
+ @observable int newHeapUsed = 0;
+ @observable int oldHeapUsed = 0;
+
+ @observable String fileAndLine;
+
+ void _update(ObservableMap map) {
+ if (map['type'] == '@Isolate') {
+ if (isRef()) {
+ // Eagerly derefence isolates.
+ deref();
+ }
+ return;
+ }
+ _ref = false;
+ if (map['rootLib'] == null ||
+ map['timers'] == null ||
+ map['heap'] == null) {
+ Logger.root.severe("Malformed 'Isolate' response: $map");
+ return;
+ }
+ rootLib = new ServiceMap.fromMap(this, map['rootLib']);
+ vmName = map['name'];
+ if (map['entry'] != null) {
+ entry = map['entry'];
+ name = entry['name'];
+ } else {
+ // fred
+ name = 'root isolate';
+ }
+ if (map['topFrame'] != null) {
+ topFrame = new ServiceMap.fromMap(this, map['topFrame']);
+ } else {
+ topFrame = null ;
+ }
+
+ var timerMap = {};
+ map['timers'].forEach((timer) {
+ timerMap[timer['name']] = timer['time'];
+ });
+ timers['total'] = timerMap['time_total_runtime'];
+ timers['compile'] = timerMap['time_compilation'];
+ timers['gc'] = 0.0; // TODO(turnidge): Export this from VM.
+ timers['init'] = (timerMap['time_script_loading'] +
+ timerMap['time_creating_snapshot'] +
+ timerMap['time_isolate_initialization'] +
+ timerMap['time_bootstrap']);
+ timers['dart'] = timerMap['time_dart_execution'];
+
+ newHeapUsed = map['heap']['usedNew'];
+ oldHeapUsed = map['heap']['usedOld'];
+ }
+}
+
+// TODO(johnmccutchan): Make this into an IsolateCache.
+class IsolateList extends ServiceObject {
+ final VM _vm;
+ VM get vm => _vm;
+ @observable final ObservableMap isolates = new ObservableMap();
+ IsolateList(this._vm) : super(null, 'isolates', 'IsolateList') {
+ name = 'IsolateList';
+ vmName = name;
+ }
+ IsolateList.fromMap(this._vm, Map m) : super.fromMap(null, m) {
+ name = 'IsolateList';
+ vmName = name;
+ }
+
+ Future<ServiceObject> refresh() {
+ return vm.fetchMap(id).then(update);
+ }
+
+ void _update(ObservableMap map) {
+ _updateIsolates(map['members']);
+ }
+
+ void _updateIsolates(List<Map> members) {
+ // Find dead isolates.
+ var deadIsolates = [];
+ isolates.forEach((k, v) {
+ if (!_foundIsolateInMembers(k, members)) {
+ deadIsolates.add(k);
+ }
+ });
+ // Remove them.
+ deadIsolates.forEach((id) {
+ isolates.remove(id);
+ Logger.root.info('Isolate \'$id\' has gone away.');
+ });
+
+ // Add new isolates.
+ members.forEach((map) {
+ var id = map['id'];
+ var isolate = isolates[id];
+ if (isolate == null) {
+ isolate = new Isolate.fromMap(vm, map);
+ Logger.root.info('Created ServiceObject for \'${isolate.id}\' with '
+ 'type \'${isolate.serviceType}\'');
+ isolates[id] = isolate;
+ }
+ });
+
+ // After updating the isolate list, refresh each isolate.
+ _refreshIsolates();
+ }
+
+ void _refreshIsolates() {
+ // This is technically asynchronous but we don't need to wait for
+ // the result.
+ isolates.forEach((k, Isolate isolate) {
+ isolate.refresh();
+ });
+ }
+
+ Isolate getIsolate(String id) {
+ assert(id.startsWith('isolates/'));
+ var isolate = isolates[id];
+ if (isolate != null) {
+ return isolate;
+ }
+ isolate = new Isolate.fromId(vm, id);
+ isolates[id] = isolate;
+ isolate.deref();
+ return isolate;
+ }
+
+ static bool _foundIsolateInMembers(String id, List<Map> members) {
+ return members.any((E) => E['id'] == id);
+ }
+}
+
+
+/// A [ServiceObject] which implements [ObservableMap].
+class ServiceMap extends ServiceObject implements ObservableMap {
+ final ObservableMap _map = new ObservableMap();
+ ServiceMap(Isolate isolate, String id, String serviceType) :
+ super(isolate, id, serviceType) {
+ }
+
+ ServiceMap.fromMap(Isolate isolate, ObservableMap m) :
+ super.fromMap(isolate, m);
+
+ bool _isUpgradeTarget(v) {
+ return (v is Map) && ServiceObject.isServiceMap(v);
+ }
+
+ String toString() => _map.toString();
+
+ ServiceObject _upgrade(Map m) {
+ var upgraded = _upgradeToServiceObject(isolate.vm, isolate, m);
+ if (upgraded is ServiceMap) {
+ // Recurse.
+ upgraded._upgradeValues();
+ }
+ return upgraded;
+ }
+
+ void _upgradeList(List l) {
+ for (var i = 0; i < l.length; i++) {
+ if (_isUpgradeTarget(l[i])) {
+ l[i] = _upgrade(l[i]);
+ } else if (l[i] is List) {
+ _upgradeList(l[i]);
+ } else if (l[i] is Map) {
+ _upgradeMap(l[i]);
+ }
+ }
+ }
+
+ void _upgradeMap(Map m) {
+ m.forEach((k, v) {
+ if (_isUpgradeTarget(v)) {
+ m[k] = _upgrade(v);
+ } else if (v is List) {
+ _upgradeList(v);
+ } else if (v is Map) {
+ _upgradeMap(v);
+ }
+ });
+ }
+
+ void _upgradeValues() {
+ assert(isolate != null);
+ _upgradeMap(_map);
+ }
+
+ void _update(ObservableMap m) {
+ _map.clear();
+ _map.addAll(m);
+ name = _map['user_name'];
+ vmName = _map['name'];
+ _upgradeValues();
+ }
+
+ // Forward Map interface calls.
+ void addAll(Map other) => _map.addAll(other);
+ void clear() => _map.clear();
+ bool containsValue(v) => _map.containsValue(v);
+ bool containsKey(k) => _map.containsKey(k);
+ void forEach(Function f) => _map.forEach(f);
+ putIfAbsent(key, Function ifAbsent) => _map.putIfAbsent(key, ifAbsent);
+ void remove(key) => _map.remove(key);
+ operator [](k) => _map[k];
+ operator []=(k, v) => _map[k] = v;
+ bool get isEmpty => _map.isEmpty;
+ bool get isNotEmpty => _map.isNotEmpty;
+ Iterable get keys => _map.keys;
+ Iterable get values => _map.values;
+ int get length => _map.length;
+
+ // Forward ChangeNotifier interface calls.
+ bool deliverChanges() => _map.deliverChanges();
+ void notifyChange(ChangeRecord record) => _map.notifyChange(record);
+ notifyPropertyChange(Symbol field, Object oldValue, Object newValue) =>
+ _map.notifyPropertyChange(field, oldValue, newValue);
+ void observed() => _map.observed();
+ void unobserved() => _map.unobserved();
+ Stream<List<ChangeRecord>> get changes => _map.changes;
+ bool get hasObservers => _map.hasObservers;
+}
+
+class ServiceError extends ServiceObject {
+ ServiceError.fromMap(Isolate isolate, Map m) : super.fromMap(isolate, m);
+
+ @observable String kind;
+ @observable String message;
+
+ void _update(ObservableMap map) {
+ kind = map['kind'];
+ message = map['message'];
+ name = 'ServiceError $kind';
+ vmName = name;
+ }
+
+ // TODO: stackTrace?
+}
+
+class ScriptLine {
+ @reflectable final int line;
+ @reflectable final String text;
+ ScriptLine(this.line, this.text);
+}
+
+class Script extends ServiceObject {
+ @reflectable final lines = new ObservableList<ScriptLine>();
+ @reflectable final hits = new ObservableMap<int, int>();
+ @observable ServiceObject library;
+ @observable String kind;
+
+ String _shortUrl;
+ String _url;
+
+ Script.fromMap(Isolate isolate, Map m) : super.fromMap(isolate, m);
+
+ void _update(ObservableMap m) {
+ // Assert that m is a service map.
+ assert(ServiceObject.isServiceMap(m));
+ if ((m['type'] == 'Error') && (m['kind'] == 'NotFoundError')) {
+ // TODO(johnmccutchan): Find out why dart:core/identical.dart can't
+ // be found but shows up in coverage. i.e. a function has reference
+ // to script that no library does.
+ Logger.root.info(m['message']);
+ return;
+ }
+ // Assert that the id hasn't changed.
+ assert(m['id'] == _id);
+ // Assert that the type hasn't changed.
+ assert(ServiceObject.unreffedType(m['type']) == _serviceType);
+ _url = m['name'];
+ _shortUrl = _url.substring(_url.lastIndexOf('/') + 1);
+ name = _shortUrl;
+ vmName = _url;
+ kind = m['kind'];
+ _processSource(m['source']);
+ }
+
+ void _processHits(List scriptHits) {
+ if (_ref) {
+ // Eagerly grab script source.
+ deref();
+ }
+ // Update hits table.
+ for (var i = 0; i < scriptHits.length; i += 2) {
+ var line = scriptHits[i];
+ var hit = scriptHits[i + 1]; // hit status.
+ assert(line >= 1); // Lines start at 1.
+ hits[line] = hit;
+ }
+ }
+
+ void _processSource(String source) {
+ // Preemptyively mark that this is a reference.
turnidge 2014/03/10 21:03:28 typo in comment.
+ _ref = true;
+ if (source == null) {
+ return;
+ }
+ var sourceLines = source.split('\n');
+ if (sourceLines.length == 0) {
+ return;
+ }
+ // We have the source to the script. This is no longer a reference.
+ _ref = false;
+ lines.clear();
+ Logger.root.info('Adding ${sourceLines.length} source lines for ${_url}');
+ for (var i = 0; i < sourceLines.length; i++) {
+ lines.add(new ScriptLine(i + 1, sourceLines[i]));
+ }
+ }
+
+
+}
+
+class CodeTick {
+ final int address;
+ final int exclusiveTicks;
+ final int inclusiveTicks;
+ CodeTick(this.address, this.exclusiveTicks, this.inclusiveTicks);
+}
+
+
+class CodeInstruction extends Observable {
+ @observable final int address;
+ @observable final String machine;
+ @observable final String human;
+
+ static String formatPercent(num a, num total) {
+ var percent = 100.0 * (a / total);
+ return '${percent.toStringAsFixed(2)}%';
+ }
+
+ CodeInstruction(this.address, this.machine, this.human);
+
+ @reflectable String formattedAddress() {
+ if (address == 0) {
+ return '';
+ }
+ return '0x${address.toRadixString(16)}';
+ }
+
+ @reflectable String formattedInclusive(Code code) {
+ if (code == null) {
+ return '';
+ }
+ var tick = code.addressTicks[address];
+ if (tick == null) {
+ return '';
+ }
+ var pcent = formatPercent(tick.inclusiveTicks, code.totalSamplesInProfile);
+ return '${tick.inclusiveTicks} ($pcent)';
+ }
+
+ @reflectable String formattedExclusive(Code code) {
+ if (code == null) {
+ return '';
+ }
+ var tick = code.addressTicks[address];
+ if (tick == null) {
+ return '';
+ }
+ var pcent = formatPercent(tick.exclusiveTicks, code.totalSamplesInProfile);
+ return '${tick.exclusiveTicks} ($pcent)';
+ }
+}
+
+class CodeKind {
+ final _value;
+ const CodeKind._internal(this._value);
+ String toString() => 'CodeKind.$_value';
+
+ static CodeKind fromString(String s) {
+ if (s == 'Native') {
+ return Native;
+ } else if (s == 'Dart') {
+ return Dart;
+ } else if (s == 'Collected') {
+ return Collected;
+ }
+ throw new FallThroughError();
+ }
+ static const Native = const CodeKind._internal('Native');
+ static const Dart = const CodeKind._internal('Dart');
+ static const Collected = const CodeKind._internal('Collected');
+}
+
+class CodeCallCount {
+ final Code code;
+ final int count;
+ CodeCallCount(this.code, this.count);
+}
+
+class Code extends ServiceObject {
+ @observable CodeKind kind;
+ @observable int totalSamplesInProfile = 0;
+ @reflectable int exclusiveTicks = 0;
+ @reflectable int inclusiveTicks = 0;
+ @reflectable int startAddress = 0;
+ @reflectable int endAddress = 0;
+ @reflectable final callers = new List<CodeCallCount>();
+ @reflectable final callees = new List<CodeCallCount>();
+ @reflectable final instructions = new ObservableList<CodeInstruction>();
+ @reflectable final addressTicks = new ObservableMap<int, CodeTick>();
+
+ @observable ServiceMap function;
+ String name;
+ String vmName;
+
+ Code.fromMap(Isolate isolate, Map map) : super.fromMap(isolate, map);
+
+ // Reset all data associated with a profile.
+ void resetProfileData() {
+ totalSamplesInProfile = 0;
+ exclusiveTicks = 0;
+ inclusiveTicks = 0;
+ callers.clear();
+ callees.clear();
+ addressTicks.clear();
+ }
+
+ void _resolveCalls(List<CodeCallCount> calls, List data, List<Code> codes) {
+ // Assert that this has been cleared.
+ assert(calls.length == 0);
+ // Resolve.
+ for (var i = 0; i < data.length; i += 2) {
+ var index = int.parse(data[i]);
+ var count = int.parse(data[i + 1]);
+ assert(index >= 0);
+ assert(index < codes.length);
+ calls.add(new CodeCallCount(codes[index], count));
+ }
+ // Sort to descending count order.
+ calls.sort((a, b) => b.count - a.count);
+ }
+
+
+ void updateProfileData(Map profileData,
+ List<Code> codeTable,
+ int sampleCount) {
+ // Assert we have a ProfileCode entry.
+ assert(profileData['type'] == 'ProfileCode');
+ // Assert we are handed profile data for this code object.
+ assert(profileData['code'] == this);
+ totalSamplesInProfile = sampleCount;
+ inclusiveTicks = int.parse(profileData['inclusive_ticks']);
+ exclusiveTicks = int.parse(profileData['exclusive_ticks']);
+ _resolveCalls(callers, profileData['callers'], codeTable);
+ _resolveCalls(callees, profileData['callees'], codeTable);
+ var ticks = profileData['ticks'];
+ if (ticks != null) {
+ _processTicks(ticks);
+ }
+ }
+
+ void _update(ObservableMap m) {
+ assert(ServiceObject.isServiceMap(m));
+ assert(m['id'] == _id);
+ assert(ServiceObject.unreffedType(m['type']) == _serviceType);
+ name = m['user_name'];
+ vmName = m['name'];
+ startAddress = int.parse(m['start'], radix:16);
+ endAddress = int.parse(m['end'], radix:16);
+ // Upgrade the function.
+ function = _upgradeToServiceObject(isolate.vm, isolate, m['function']);
+ var disassembly = m['disassembly'];
+ if (disassembly != null) {
+ _processDisassembly(disassembly);
+ }
+ // We are a reference if we don't have instructions.
+ _ref = (instructions.length == 0);
+ }
+
+ void _processDisassembly(List<String> disassembly){
+ assert(disassembly != null);
+ instructions.clear();
+ assert((disassembly.length % 3) == 0);
+ for (var i = 0; i < disassembly.length; i += 3) {
+ var address = 0; // Assume code comment.
+ var machine = disassembly[i + 1];
+ var human = disassembly[i + 2];
+ if (disassembly[i] != '') {
+ // Not a code comment, extract address.
+ address = int.parse(disassembly[i]);
+ }
+ var instruction = new CodeInstruction(address, machine, human);
+ instructions.add(instruction);
+ }
+ }
+
+ void _processTicks(List<String> profileTicks) {
+ assert(profileTicks != null);
+ assert((profileTicks.length % 3) == 0);
+ for (var i = 0; i < profileTicks.length; i += 3) {
+ var address = int.parse(profileTicks[i], radix:16);
+ var exclusive = int.parse(profileTicks[i + 1]);
+ var inclusive = int.parse(profileTicks[i + 2]);
+ var tick = new CodeTick(address, exclusive, inclusive);
+ addressTicks[address] = tick;
+ }
+ }
+
+ /// Returns true if [address] is contained inside [this].
+ bool contains(int address) {
+ return (address >= startAddress) && (address < endAddress);
+ }
+
+ /// Sum all caller counts.
+ int sumCallersCount() => _sumCallCount(callers);
+ /// Specific caller count.
+ int callersCount(Code code) => _callCount(callers, code);
+ /// Sum of callees count.
+ int sumCalleesCount() => _sumCallCount(callees);
+ /// Specific callee count.
+ int calleesCount(Code code) => _callCount(callees, code);
+
+ int _sumCallCount(List<CodeCallCount> calls) {
+ var sum = 0;
+ for (CodeCallCount caller in calls) {
+ sum += caller.count;
+ }
+ return sum;
+ }
+
+ int _callCount(List<CodeCallCount> calls, Code code) {
+ for (CodeCallCount caller in calls) {
+ if (caller.code == code) {
+ return caller.count;
+ }
+ }
+ return 0;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698