From f219e5152d4e447a5a8b09f6bf0fb0dcd263877e Mon Sep 17 00:00:00 2001 From: Alexander Aprelev Date: Wed, 16 Oct 2019 18:05:06 +0000 Subject: [PATCH] [vm/service] Introduce IsolateGroup entity to vm service api. This is to enable vm clients to ask how much memory is consumed by isolate group. Issue https://github.com/dart-lang/sdk/issues/36097 Change-Id: I4f1c499bf02c20b80e9802d8ad60e7ea65cfb375 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/119724 Commit-Queue: Alexander Aprelev Reviewed-by: Ryan Macnak Reviewed-by: Martin Kustermann --- pkg/vm_service/example/vm_service_assert.dart | 28 ++++ pkg/vm_service/java/.gitignore | 4 + pkg/vm_service/lib/vm_service.dart | 158 ++++++++++++++++++ .../test/get_isolate_group_memory_usage.dart | 35 ++++ runtime/observatory/analysis_options.yaml | 5 + runtime/observatory/lib/models.dart | 2 + runtime/observatory/lib/repositories.dart | 1 + .../lib/src/models/objects/isolate_group.dart | 22 +++ .../lib/src/models/objects/vm.dart | 1 + .../models/repositories/isolate_group.dart | 9 + .../lib/src/repositories/isolate_group.dart | 22 +++ .../observatory/lib/src/service/object.dart | 151 +++++++++++++++-- runtime/observatory/observatory_sources.gni | 3 + .../get_isolate_group_memory_usage.dart | 64 +++++++ .../tests/service/get_version_rpc_test.dart | 2 +- .../tests/service/get_vm_rpc_test.dart | 2 + .../http_get_isolate_group_rpc_common.dart | 83 +++++++++ .../http_get_isolate_group_rpc_test.dart | 14 ++ .../service/http_get_isolate_rpc_common.dart | 1 + .../tests/service/http_get_vm_rpc_common.dart | 2 + runtime/vm/dart.cc | 3 + runtime/vm/dart_api_impl.cc | 2 + runtime/vm/isolate.cc | 110 ++++++++++++ runtime/vm/isolate.h | 31 +++- runtime/vm/json_stream.cc | 5 + runtime/vm/json_stream.h | 4 + runtime/vm/service.cc | 112 +++++++++---- runtime/vm/service.h | 6 +- runtime/vm/service/service.md | 70 ++++++++ 29 files changed, 905 insertions(+), 47 deletions(-) create mode 100644 pkg/vm_service/test/get_isolate_group_memory_usage.dart create mode 100644 runtime/observatory/lib/src/models/objects/isolate_group.dart create mode 100644 runtime/observatory/lib/src/models/repositories/isolate_group.dart create mode 100644 runtime/observatory/lib/src/repositories/isolate_group.dart create mode 100644 runtime/observatory/tests/service/get_isolate_group_memory_usage.dart create mode 100644 runtime/observatory/tests/service/http_get_isolate_group_rpc_common.dart create mode 100644 runtime/observatory/tests/service/http_get_isolate_group_rpc_test.dart diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart index baaad586951..65e42bc2c80 100644 --- a/pkg/vm_service/example/vm_service_assert.dart +++ b/pkg/vm_service/example/vm_service_assert.dart @@ -672,6 +672,33 @@ vms.Isolate assertIsolate(vms.Isolate obj) { return obj; } +vms.IsolateGroupRef assertIsolateGroupRef(vms.IsolateGroupRef obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.number); + assertString(obj.name); + return obj; +} + +List assertListOfIsolateGroupRef( + List list) { + for (vms.IsolateGroupRef elem in list) { + assertIsolateGroupRef(elem); + } + return list; +} + +vms.IsolateGroup assertIsolateGroup(vms.IsolateGroup obj) { + assertNotNull(obj); + assertString(obj.type); + assertString(obj.id); + assertString(obj.number); + assertString(obj.name); + assertListOfIsolateRef(obj.isolates); + return obj; +} + vms.InboundReferences assertInboundReferences(vms.InboundReferences obj) { assertNotNull(obj); assertString(obj.type); @@ -1108,5 +1135,6 @@ vms.VM assertVM(vms.VM obj) { assertInt(obj.pid); assertInt(obj.startTime); assertListOfIsolateRef(obj.isolates); + assertListOfIsolateGroupRef(obj.isolateGroups); return obj; } diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore index b5f4f08f303..ccd4e3467ae 100644 --- a/pkg/vm_service/java/.gitignore +++ b/pkg/vm_service/java/.gitignore @@ -9,6 +9,8 @@ src/org/dartlang/vm/service/consumer/EvaluateInFrameConsumer.java src/org/dartlang/vm/service/consumer/FlagListConsumer.java src/org/dartlang/vm/service/consumer/GetInboundReferencesConsumer.java src/org/dartlang/vm/service/consumer/GetIsolateConsumer.java +src/org/dartlang/vm/service/consumer/GetIsolateGroupConsumer.java +src/org/dartlang/vm/service/consumer/GetIsolateGroupMemoryUsageConsumer.java src/org/dartlang/vm/service/consumer/GetMemoryUsageConsumer.java src/org/dartlang/vm/service/consumer/GetObjectConsumer.java src/org/dartlang/vm/service/consumer/InstanceSetConsumer.java @@ -63,6 +65,8 @@ src/org/dartlang/vm/service/element/InstanceKind.java src/org/dartlang/vm/service/element/InstanceRef.java src/org/dartlang/vm/service/element/InstanceSet.java src/org/dartlang/vm/service/element/Isolate.java +src/org/dartlang/vm/service/element/IsolateGroup.java +src/org/dartlang/vm/service/element/IsolateGroupRef.java src/org/dartlang/vm/service/element/IsolateRef.java src/org/dartlang/vm/service/element/Library.java src/org/dartlang/vm/service/element/LibraryDependency.java diff --git a/pkg/vm_service/lib/vm_service.dart b/pkg/vm_service/lib/vm_service.dart index 0eeea9bf9b1..aa2356df1ea 100644 --- a/pkg/vm_service/lib/vm_service.dart +++ b/pkg/vm_service/lib/vm_service.dart @@ -131,6 +131,8 @@ Map _typeFactories = { 'Instance': Instance.parse, '@Isolate': IsolateRef.parse, 'Isolate': Isolate.parse, + '@IsolateGroup': IsolateGroupRef.parse, + 'IsolateGroup': IsolateGroup.parse, 'InboundReferences': InboundReferences.parse, 'InboundReference': InboundReference.parse, 'InstanceSet': InstanceSet.parse, @@ -188,7 +190,9 @@ Map> _methodReturnTypes = { 'getInboundReferences': const ['InboundReferences', 'Sentinel'], 'getInstances': const ['InstanceSet'], 'getIsolate': const ['Isolate', 'Sentinel'], + 'getIsolateGroup': const ['IsolateGroup', 'Sentinel'], 'getMemoryUsage': const ['MemoryUsage', 'Sentinel'], + 'getIsolateGroupMemoryUsage': const ['MemoryUsage', 'Sentinel'], 'getScripts': const ['ScriptList'], 'getObject': const ['Obj', 'Sentinel'], 'getRetainingPath': const ['RetainingPath'], @@ -506,6 +510,21 @@ abstract class VmServiceInterface { /// The return value can be one of [Isolate] or [Sentinel]. Future getIsolate(String isolateId); + /// The `getIsolateGroup` RPC is used to lookup an `IsolateGroup` object by + /// its `id`. + /// + /// If `isolateGroupId` refers to an isolate group which has exited, then the + /// `Expired` [Sentinel] is returned. + /// + /// `IsolateGroup` `id` is an opaque identifier that can be fetched from an + /// `IsolateGroup`. List of active `IsolateGroup`'s, for example, is available + /// on `VM` object. + /// + /// See [IsolateGroup], [VM]. + /// + /// The return value can be one of [IsolateGroup] or [Sentinel]. + Future getIsolateGroup(String isolateGroupId); + /// The `getMemoryUsage` RPC is used to lookup an isolate's memory usage /// statistics by its `id`. /// @@ -517,6 +536,17 @@ abstract class VmServiceInterface { /// The return value can be one of [MemoryUsage] or [Sentinel]. Future getMemoryUsage(String isolateId); + /// The `getIsolateGroupMemoryUsage` RPC is used to lookup an isolate group's + /// memory usage statistics by its `id`. + /// + /// If `isolateGroupId` refers to an isolate group which has exited, then the + /// `Expired` [Sentinel] is returned. + /// + /// See [IsolateGroup]. + /// + /// The return value can be one of [MemoryUsage] or [Sentinel]. + Future getIsolateGroupMemoryUsage(String isolateGroupId); + /// The `getScripts` RPC is used to retrieve a `ScriptList` containing all /// scripts for an isolate based on the isolate's `isolateId`. /// @@ -1027,11 +1057,21 @@ class VmServerConnection { params['isolateId'], ); break; + case 'getIsolateGroup': + response = await _serviceImplementation.getIsolateGroup( + params['isolateGroupId'], + ); + break; case 'getMemoryUsage': response = await _serviceImplementation.getMemoryUsage( params['isolateId'], ); break; + case 'getIsolateGroupMemoryUsage': + response = await _serviceImplementation.getIsolateGroupMemoryUsage( + params['isolateGroupId'], + ); + break; case 'getScripts': response = await _serviceImplementation.getScripts( params['isolateId'], @@ -1468,11 +1508,22 @@ class VmService implements VmServiceInterface { return _call('getIsolate', {'isolateId': isolateId}); } + @override + Future getIsolateGroup(String isolateGroupId) { + return _call('getIsolateGroup', {'isolateGroupId': isolateGroupId}); + } + @override Future getMemoryUsage(String isolateId) { return _call('getMemoryUsage', {'isolateId': isolateId}); } + @override + Future getIsolateGroupMemoryUsage(String isolateGroupId) { + return _call( + 'getIsolateGroupMemoryUsage', {'isolateGroupId': isolateGroupId}); + } + @override Future getScripts(String isolateId) { return _call('getScripts', {'isolateId': isolateId}); @@ -1699,6 +1750,7 @@ class VmService implements VmServiceInterface { void dispose() { _streamSub.cancel(); _completers.values.forEach((c) => c.completeError('disposed')); + _completers.clear(); if (_disposeHandler != null) { _disposeHandler(); } @@ -4444,6 +4496,105 @@ class Isolate extends Response { String toString() => '[Isolate]'; } +/// `IsolateGroupRef` is a reference to an `IsolateGroup` object. +class IsolateGroupRef extends Response { + static IsolateGroupRef parse(Map json) => + json == null ? null : IsolateGroupRef._fromJson(json); + + /// The id which is passed to the getIsolateGroup RPC to load this isolate + /// group. + String id; + + /// A numeric id for this isolate group, represented as a string. Unique. + String number; + + /// A name identifying this isolate group. Not guaranteed to be unique. + String name; + + IsolateGroupRef({ + @required this.id, + @required this.number, + @required this.name, + }); + IsolateGroupRef._fromJson(Map json) : super._fromJson(json) { + id = json['id']; + number = json['number']; + name = json['name']; + } + + @override + Map toJson() { + var json = {}; + json['type'] = '@IsolateGroup'; + json.addAll({ + 'id': id, + 'number': number, + 'name': name, + }); + return json; + } + + int get hashCode => id.hashCode; + + operator ==(other) => other is IsolateGroupRef && id == other.id; + + String toString() => + '[IsolateGroupRef type: ${type}, id: ${id}, number: ${number}, name: ${name}]'; +} + +/// An `Isolate` object provides information about one isolate in the VM. +class IsolateGroup extends Response { + static IsolateGroup parse(Map json) => + json == null ? null : IsolateGroup._fromJson(json); + + /// The id which is passed to the getIsolate RPC to reload this isolate. + String id; + + /// A numeric id for this isolate, represented as a string. Unique. + String number; + + /// A name identifying this isolate. Not guaranteed to be unique. + String name; + + /// A list of all isolates in this isolate group. + List isolates; + + IsolateGroup({ + @required this.id, + @required this.number, + @required this.name, + @required this.isolates, + }); + IsolateGroup._fromJson(Map json) : super._fromJson(json) { + id = json['id']; + number = json['number']; + name = json['name']; + isolates = List.from( + createServiceObject(json['isolates'], const ['IsolateRef'])); + } + + @override + Map toJson() { + var json = {}; + json['type'] = 'IsolateGroup'; + json.addAll({ + 'id': id, + 'number': number, + 'name': name, + 'isolates': isolates.map((f) => f.toJson()).toList(), + }); + return json; + } + + int get hashCode => id.hashCode; + + operator ==(other) => other is IsolateGroup && id == other.id; + + String toString() => '[IsolateGroup ' // + 'type: ${type}, id: ${id}, number: ${number}, name: ${name}, ' // + 'isolates: ${isolates}]'; +} + /// See [getInboundReferences]. class InboundReferences extends Response { static InboundReferences parse(Map json) => @@ -6236,6 +6387,9 @@ class VM extends Response { /// A list of isolates running in the VM. List isolates; + /// A list of isolate groups running in the VM. + List isolateGroups; + VM({ @required this.name, @required this.architectureBits, @@ -6246,6 +6400,7 @@ class VM extends Response { @required this.pid, @required this.startTime, @required this.isolates, + @required this.isolateGroups, }); VM._fromJson(Map json) : super._fromJson(json) { name = json['name']; @@ -6258,6 +6413,8 @@ class VM extends Response { startTime = json['startTime']; isolates = List.from( createServiceObject(json['isolates'], const ['IsolateRef'])); + isolateGroups = List.from( + createServiceObject(json['isolateGroups'], const ['IsolateGroupRef'])); } @override @@ -6274,6 +6431,7 @@ class VM extends Response { 'pid': pid, 'startTime': startTime, 'isolates': isolates.map((f) => f.toJson()).toList(), + 'isolateGroups': isolateGroups.map((f) => f.toJson()).toList(), }); return json; } diff --git a/pkg/vm_service/test/get_isolate_group_memory_usage.dart b/pkg/vm_service/test/get_isolate_group_memory_usage.dart new file mode 100644 index 00000000000..e6323a15f9a --- /dev/null +++ b/pkg/vm_service/test/get_isolate_group_memory_usage.dart @@ -0,0 +1,35 @@ +// Copyright (c) 2019, 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. + +import 'package:test/test.dart'; +import 'package:vm_service/vm_service.dart'; + +import 'common/test_helper.dart'; + +var tests = [ + (VmService service) async { + final vm = await service.getVM(); + final result = + await service.getIsolateGroupMemoryUsage(vm.isolateGroups.first.id); + expect(result.heapUsage, isPositive); + expect(result.heapCapacity, isPositive); + expect(result.externalUsage, isNonNegative); + }, + (VmService service) async { + bool caughtException; + try { + await service.getMemoryUsage('badid'); + fail('Unreachable'); + } on RPCError catch (e) { + caughtException = true; + expect( + e.details, + contains( + "getMemoryUsage: invalid 'isolateGroupId' parameter: badid")); + } + expect(caughtException, isTrue); + }, +]; + +main(args) async => runVMTests(args, tests); diff --git a/runtime/observatory/analysis_options.yaml b/runtime/observatory/analysis_options.yaml index ecd5b5026a4..6babf28a427 100644 --- a/runtime/observatory/analysis_options.yaml +++ b/runtime/observatory/analysis_options.yaml @@ -10,3 +10,8 @@ analyzer: - tests/service/get_isolate_after_language_error_test.dart - tests/service/get_user_level_retaining_path_rpc_test.dart - tests/service/pause_on_unhandled_async_exceptions_test.dart + +linter: + rules: + - prefer_final_fields + - prefer_final_locals diff --git a/runtime/observatory/lib/models.dart b/runtime/observatory/lib/models.dart index 60809ef0ece..1b13845ef5f 100644 --- a/runtime/observatory/lib/models.dart +++ b/runtime/observatory/lib/models.dart @@ -29,6 +29,7 @@ part 'src/models/objects/icdata.dart'; part 'src/models/objects/inbound_references.dart'; part 'src/models/objects/instance.dart'; part 'src/models/objects/isolate.dart'; +part 'src/models/objects/isolate_group.dart'; part 'src/models/objects/library.dart'; part 'src/models/objects/local_var_descriptors.dart'; part 'src/models/objects/map_association.dart'; @@ -74,6 +75,7 @@ part 'src/models/repositories/icdata.dart'; part 'src/models/repositories/inbound_references.dart'; part 'src/models/repositories/instance.dart'; part 'src/models/repositories/isolate.dart'; +part 'src/models/repositories/isolate_group.dart'; part 'src/models/repositories/library.dart'; part 'src/models/repositories/megamorphiccache.dart'; part 'src/models/repositories/metric.dart'; diff --git a/runtime/observatory/lib/repositories.dart b/runtime/observatory/lib/repositories.dart index 70cb59394e2..4810e58141c 100644 --- a/runtime/observatory/lib/repositories.dart +++ b/runtime/observatory/lib/repositories.dart @@ -30,6 +30,7 @@ part 'src/repositories/icdata.dart'; part 'src/repositories/inbound_references.dart'; part 'src/repositories/instance.dart'; part 'src/repositories/isolate.dart'; +part 'src/repositories/isolate_group.dart'; part 'src/repositories/library.dart'; part 'src/repositories/megamorphiccache.dart'; part 'src/repositories/metric.dart'; diff --git a/runtime/observatory/lib/src/models/objects/isolate_group.dart b/runtime/observatory/lib/src/models/objects/isolate_group.dart new file mode 100644 index 00000000000..b18bbf72fe9 --- /dev/null +++ b/runtime/observatory/lib/src/models/objects/isolate_group.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2019, 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 models; + +abstract class IsolateGroupRef { + /// The id which is passed to the getIsolateGroup RPC to reload this + /// isolate group. + String get id; + + /// A numeric id for this isolate group, represented as a string. Unique. + int get number; + + /// A name identifying this isolate group. Not guaranteed to be unique. + String get name; +} + +abstract class IsolateGroup extends IsolateGroupRef { + /// A list of all isolates in this isolate group. + Iterable get isolates; +} diff --git a/runtime/observatory/lib/src/models/objects/vm.dart b/runtime/observatory/lib/src/models/objects/vm.dart index b2bfd4de2a2..fe25c846790 100644 --- a/runtime/observatory/lib/src/models/objects/vm.dart +++ b/runtime/observatory/lib/src/models/objects/vm.dart @@ -50,6 +50,7 @@ abstract class VM implements VMRef { // A list of isolates running in the VM. Iterable get isolates; + Iterable get isolateGroups; /// Enable the sampling profiler. Future enableProfiler(); diff --git a/runtime/observatory/lib/src/models/repositories/isolate_group.dart b/runtime/observatory/lib/src/models/repositories/isolate_group.dart new file mode 100644 index 00000000000..ccf24227a61 --- /dev/null +++ b/runtime/observatory/lib/src/models/repositories/isolate_group.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2019, 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 models; + +abstract class IsolateGroupRepository { + Future get(IsolateGroupRef isolateGroup); +} diff --git a/runtime/observatory/lib/src/repositories/isolate_group.dart b/runtime/observatory/lib/src/repositories/isolate_group.dart new file mode 100644 index 00000000000..fabc9d78668 --- /dev/null +++ b/runtime/observatory/lib/src/repositories/isolate_group.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2019, 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 repositories; + +class IsolateGroupRepository extends M.IsolateGroupRepository { + final S.VM _vm; + + IsolateGroupRepository(this._vm) : assert(_vm != null); + + Future get(M.IsolateGroupRef i) async { + S.IsolateGroup isolateGroup = i as S.IsolateGroup; + assert(isolateGroup != null); + try { + await isolateGroup.reload(); + } on SC.NetworkRpcException catch (_) { + /* ignore */ + } + return isolateGroup; + } +} diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart index e173438889c..f133eee7bab 100644 --- a/runtime/observatory/lib/src/service/object.dart +++ b/runtime/observatory/lib/src/service/object.dart @@ -229,6 +229,9 @@ abstract class ServiceObject implements M.ObjectRef { case 'Isolate': obj = new Isolate._empty(owner.vm); break; + case 'IsolateGroup': + obj = new IsolateGroup._empty(owner.vm); + break; case 'Library': obj = new Library._empty(owner); break; @@ -661,10 +664,13 @@ abstract class VM extends ServiceObjectOwner implements M.VM { // TODO(johnmccutchan): Ensure that isolates do not end up in _cache. Map _cache = new Map(); final Map _isolateCache = {}; + final Map _isolateGroupCache = {}; // The list of live isolates, ordered by isolate start time. final List isolates = []; + final List isolateGroups = []; + final List services = []; String version = 'unknown'; @@ -771,6 +777,7 @@ abstract class VM extends ServiceObjectOwner implements M.VM { } static final String _isolateIdPrefix = 'isolates/'; + static final String _isolateGroupIdPrefix = 'isolateGroups/'; ServiceObject getFromMap(Map map) { if (map == null) { @@ -784,23 +791,44 @@ abstract class VM extends ServiceObjectOwner implements M.VM { } String id = map['id']; - if ((id != null) && id.startsWith(_isolateIdPrefix)) { - // Check cache. - var isolate = _isolateCache[id]; - if (isolate == null) { - // Add new isolate to the cache. - isolate = new ServiceObject._fromMap(this, map); - _isolateCache[id] = isolate; - _buildIsolateList(); + if ((id != null)) { + if (id.startsWith(_isolateIdPrefix)) { + // Check cache. + var isolate = _isolateCache[id]; + if (isolate == null) { + // Add new isolate to the cache. + isolate = ServiceObject._fromMap(this, map); + _isolateCache[id] = isolate; + _buildIsolateList(); - // Eagerly load the isolate. - isolate.load().catchError((e, stack) { - Logger.root.info('Eagerly loading an isolate failed: $e\n$stack'); - }); - } else { - isolate.updateFromServiceMap(map); + // Eagerly load the isolate. + isolate.load().catchError((e, stack) { + Logger.root.info('Eagerly loading an isolate failed: $e\n$stack'); + }); + } else { + isolate.updateFromServiceMap(map); + } + return isolate; + } + if (id.startsWith(_isolateGroupIdPrefix)) { + // Check cache. + var isolateGroup = _isolateGroupCache[id]; + if (isolateGroup == null) { + // Add new isolate to the cache. + isolateGroup = new ServiceObject._fromMap(this, map); + _isolateGroupCache[id] = isolateGroup; + _buildIsolateGroupList(); + + // Eagerly load the isolate. + isolateGroup.load().catchError((e, stack) { + Logger.root + .info('Eagerly loading an isolate group failed: $e\n$stack'); + }); + } else { + isolateGroup.updateFromServiceMap(map); + } + return isolateGroup; } - return isolate; } // Build the object from the map directly. @@ -817,6 +845,27 @@ abstract class VM extends ServiceObjectOwner implements M.VM { return new Future.value(_isolateCache[isolateId]); } + int _compareIsolateGroups(IsolateGroup a, IsolateGroup b) { + return a.id.compareTo(b.id); + } + + void _buildIsolateGroupList() { + final isolateGroupList = _isolateGroupCache.values.toList(); + isolateGroupList.sort(_compareIsolateGroups); + isolateGroups.clear(); + isolateGroups.addAll(isolateGroupList); + } + + void _removeDeadIsolateGroups(List newIsolateGroups) { + // Build a set of new isolates. + final Set newIsolateGroupSet = + newIsolateGroups.map((iso) => iso.id).toSet(); + + // Remove any old isolates which no longer exist. + _isolateGroupCache.removeWhere((id, _) => !newIsolateGroupSet.contains(id)); + _buildIsolateGroupList(); + } + // Implemented in subclass. Future invokeRpcRaw(String method, Map params); @@ -989,6 +1038,7 @@ abstract class VM extends ServiceObjectOwner implements M.VM { assertsEnabled = map['_assertsEnabled']; typeChecksEnabled = map['_typeChecksEnabled']; _removeDeadIsolates(map['isolates']); + _removeDeadIsolateGroups(map['isolateGroups']); } // Reload all isolates. @@ -1220,6 +1270,77 @@ class HeapSpace implements M.HeapSpace { } } +class IsolateGroup extends ServiceObjectOwner implements M.IsolateGroup { + IsolateGroup._empty(ServiceObjectOwner owner) + : assert(owner is VM), + super._empty(owner); + + @override + void _update(Map map, bool mapIsRef) { + name = map['name']; + vmName = map.containsKey('_vmName') ? map['_vmName'] : name; + number = int.tryParse(map['number']); + if (mapIsRef) { + return; + } + _loaded = true; + isolates.clear(); + isolates.addAll(map['isolates'] as List); + isolates.sort(ServiceObject.LexicalSortName); + vm._buildIsolateGroupList(); + } + + @override + ServiceObject getFromMap(Map map) { + if (map == null) { + return null; + } + final mapType = _stripRef(map['type']); + if (mapType == 'IsolateGroup') { + // There are sometimes isolate group refs in ServiceEvents. + return vm.getFromMap(map); + } + String mapId = map['id']; + var obj = (mapId != null) ? _cache[mapId] : null; + if (obj != null) { + obj.updateFromServiceMap(map); + return obj; + } + // Build the object from the map directly. + obj = new ServiceObject._fromMap(this, map); + if ((obj != null) && obj.canCache) { + _cache[mapId] = obj; + } + return obj; + } + + Future invokeRpcNoUpgrade(String method, Map params) { + params['isolateGroupId'] = id; + return vm.invokeRpcNoUpgrade(method, params); + } + + Future invokeRpc(String method, Map params) { + return invokeRpcNoUpgrade(method, params) + .then((Map response) => getFromMap(response)); + } + + @override + Future _fetchDirect({int count: kDefaultFieldLimit}) { + Map params = { + 'isolateGroupId': id, + }; + return vm.invokeRpcNoUpgrade('getIsolateGroup', params); + } + + @override + final List isolates = []; + + @override + int number; + + final Map _cache = Map(); +} + /// State for a running isolate. class Isolate extends ServiceObjectOwner implements M.Isolate { static const kLoggingStream = 'Logging'; diff --git a/runtime/observatory/observatory_sources.gni b/runtime/observatory/observatory_sources.gni index 1cd8af31e0d..cb47f7cf1aa 100644 --- a/runtime/observatory/observatory_sources.gni +++ b/runtime/observatory/observatory_sources.gni @@ -164,6 +164,7 @@ observatory_sources = [ "lib/src/models/objects/inbound_references.dart", "lib/src/models/objects/instance.dart", "lib/src/models/objects/isolate.dart", + "lib/src/models/objects/isolate_group.dart", "lib/src/models/objects/library.dart", "lib/src/models/objects/local_var_descriptors.dart", "lib/src/models/objects/map_association.dart", @@ -208,6 +209,7 @@ observatory_sources = [ "lib/src/models/repositories/inbound_references.dart", "lib/src/models/repositories/instance.dart", "lib/src/models/repositories/isolate.dart", + "lib/src/models/repositories/isolate_group.dart", "lib/src/models/repositories/library.dart", "lib/src/models/repositories/megamorphiccache.dart", "lib/src/models/repositories/metric.dart", @@ -245,6 +247,7 @@ observatory_sources = [ "lib/src/repositories/inbound_references.dart", "lib/src/repositories/instance.dart", "lib/src/repositories/isolate.dart", + "lib/src/repositories/isolate_group.dart", "lib/src/repositories/library.dart", "lib/src/repositories/megamorphiccache.dart", "lib/src/repositories/metric.dart", diff --git a/runtime/observatory/tests/service/get_isolate_group_memory_usage.dart b/runtime/observatory/tests/service/get_isolate_group_memory_usage.dart new file mode 100644 index 00000000000..8a834d94232 --- /dev/null +++ b/runtime/observatory/tests/service/get_isolate_group_memory_usage.dart @@ -0,0 +1,64 @@ +// Copyright (c) 2019, 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. + +import 'package:observatory/service_io.dart'; +import 'package:unittest/unittest.dart'; + +import 'test_helper.dart'; + +var tests = [ + (VM vm) async { + final params = { + 'isolateGroupId': vm.isolateGroups.first.id, + }; + final result = + await vm.invokeRpcNoUpgrade('getIsolateGroupMemoryUsage', params); + expect(result['type'], equals('MemoryUsage')); + expect(result['heapUsage'], isPositive); + expect(result['heapCapacity'], isPositive); + expect(result['externalUsage'], isPositive); + }, + (VM vm) async { + final params = { + 'isolateGroupId': 'badid', + }; + bool caughtException; + try { + await vm.invokeRpcNoUpgrade('getIsolateGroupMemoryUsage', params); + expect(false, isTrue, reason: 'Unreachable'); + } on ServerRpcException catch (e) { + caughtException = true; + expect(e.code, equals(ServerRpcException.kInvalidParams)); + expect(e.message, + "getIsolateGroupMemoryUsage: invalid 'isolateGroupId' parameter: badid"); + } + expect(caughtException, isTrue); + }, + + // Plausible isolate group id, not found. + (VM vm) async { + final params = { + 'isolateGroupId': 'isolateGroups/9999999999', + }; + final result = + await vm.invokeRpcNoUpgrade('getIsolateGroupMemoryUsage', params); + expect(result['type'], equals('Sentinel')); + expect(result['kind'], equals('Expired')); + expect(result['valueAsString'], equals('')); + }, + + // isolate id was passed instead of isolate group id. + (VM vm) async { + final params = { + 'isolateId': 'isolates/9999999999', + }; + final result = + await vm.invokeRpcNoUpgrade('getIsolateGroupMemoryUsage', params); + expect(result['type'], equals('Sentinel')); + expect(result['kind'], equals('Expired')); + expect(result['valueAsString'], equals('')); + }, +]; + +main(args) async => runVMTests(args, tests); diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart index f062d33c05c..a950dd054c5 100644 --- a/runtime/observatory/tests/service/get_version_rpc_test.dart +++ b/runtime/observatory/tests/service/get_version_rpc_test.dart @@ -12,7 +12,7 @@ var tests = [ var result = await vm.invokeRpcNoUpgrade('getVersion', {}); expect(result['type'], equals('Version')); expect(result['major'], equals(3)); - expect(result['minor'], equals(27)); + expect(result['minor'], equals(28)); expect(result['_privateMajor'], equals(0)); expect(result['_privateMinor'], equals(0)); }, diff --git a/runtime/observatory/tests/service/get_vm_rpc_test.dart b/runtime/observatory/tests/service/get_vm_rpc_test.dart index 9f63409f81a..0e924a38b90 100644 --- a/runtime/observatory/tests/service/get_vm_rpc_test.dart +++ b/runtime/observatory/tests/service/get_vm_rpc_test.dart @@ -24,6 +24,8 @@ var tests = [ expect(result['startTime'], isPositive); expect(result['isolates'].length, isPositive); expect(result['isolates'][0]['type'], equals('@Isolate')); + expect(result['isolateGroups'].length, isPositive); + expect(result['isolateGroups'][0]['type'], equals('@IsolateGroup')); }, ]; diff --git a/runtime/observatory/tests/service/http_get_isolate_group_rpc_common.dart b/runtime/observatory/tests/service/http_get_isolate_group_rpc_common.dart new file mode 100644 index 00000000000..2b28be54855 --- /dev/null +++ b/runtime/observatory/tests/service/http_get_isolate_group_rpc_common.dart @@ -0,0 +1,83 @@ +// Copyright (c) 2019, 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. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io' as io; +import 'package:observatory/service_io.dart' as S; +import 'package:unittest/unittest.dart'; +import 'test_helper.dart'; + +Future getIsolateGroupId( + io.HttpClient httpClient, Uri serverUri) async { + // Build the request. + final pathSegments = []..addAll(serverUri.pathSegments); + const method = 'getVM'; + if (pathSegments.isNotEmpty) { + pathSegments[pathSegments.length - 1] = method; + } else { + pathSegments.add(method); + } + final requestUri = serverUri.replace(pathSegments: pathSegments); + final request = await httpClient.getUrl(requestUri); + final Map response = await (await request.close()) + .cast>() + .transform(utf8.decoder) + .transform(json.decoder) + .first; + final result = response['result']; + return result['isolateGroups'][0]['id']; +} + +Future testeeBefore() async { + print('testee before'); + print(await Service.getInfo()); + // Start the web server. + final ServiceProtocolInfo info = await Service.controlWebServer(enable: true); + expect(info.serverUri, isNotNull); + final httpClient = new io.HttpClient(); + + // Build the request. + final params = { + 'isolateGroupId': await getIsolateGroupId(httpClient, info.serverUri), + }; + + const method = 'getIsolateGroup'; + final pathSegments = []..addAll(info.serverUri.pathSegments); + if (pathSegments.isNotEmpty) { + pathSegments[pathSegments.length - 1] = method; + } else { + pathSegments.add(method); + } + final requestUri = info.serverUri + .replace(pathSegments: pathSegments, queryParameters: params); + + try { + final request = await httpClient.getUrl(requestUri); + final response = await request.close(); + final Map jsonResponse = await response + .cast>() + .transform(utf8.decoder) + .transform(json.decoder) + .first; + final result = jsonResponse['result']; + expect(result['type'], equals('IsolateGroup')); + expect(result['id'], startsWith('isolateGroups/')); + expect(result['number'], new isInstanceOf()); + expect(result['isolates'].length, isPositive); + expect(result['isolates'][0]['type'], equals('@Isolate')); + } catch (e) { + fail('invalid request: $e'); + } +} + +var tests = [ + (S.Isolate isolate) async { + await isolate.reload(); + // Just getting here means that the testee enabled the service protocol + // web server. + expect(true, true); + } +]; diff --git a/runtime/observatory/tests/service/http_get_isolate_group_rpc_test.dart b/runtime/observatory/tests/service/http_get_isolate_group_rpc_test.dart new file mode 100644 index 00000000000..eb382b1916a --- /dev/null +++ b/runtime/observatory/tests/service/http_get_isolate_group_rpc_test.dart @@ -0,0 +1,14 @@ +// Copyright (c) 2019, 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. + +import 'http_get_isolate_group_rpc_common.dart'; +import 'test_helper.dart'; + +main(args) { + runIsolateTests(args, tests, + testeeBefore: testeeBefore, + // the testee is responsible for starting the + // web server. + testeeControlsServer: true); +} diff --git a/runtime/observatory/tests/service/http_get_isolate_rpc_common.dart b/runtime/observatory/tests/service/http_get_isolate_rpc_common.dart index 06c47dc0e63..f49e5a4fc43 100644 --- a/runtime/observatory/tests/service/http_get_isolate_rpc_common.dart +++ b/runtime/observatory/tests/service/http_get_isolate_rpc_common.dart @@ -78,6 +78,7 @@ Future testeeBefore() async { expect(result['breakpoints'].length, isZero); expect(result['_heaps']['new']['type'], equals('HeapSpace')); expect(result['_heaps']['old']['type'], equals('HeapSpace')); + expect(result['isolate_group']['type'], equals('@IsolateGroup')); } catch (e) { fail('invalid request: $e'); } diff --git a/runtime/observatory/tests/service/http_get_vm_rpc_common.dart b/runtime/observatory/tests/service/http_get_vm_rpc_common.dart index 7298b3dd00b..18a0abe586f 100644 --- a/runtime/observatory/tests/service/http_get_vm_rpc_common.dart +++ b/runtime/observatory/tests/service/http_get_vm_rpc_common.dart @@ -46,6 +46,8 @@ Future testeeBefore() async { expect(result['startTime'], isPositive); expect(result['isolates'].length, isPositive); expect(result['isolates'][0]['type'], equals('@Isolate')); + expect(result['isolateGroups'].length, isPositive); + expect(result['isolateGroups'][0]['type'], equals('@IsolateGroup')); } catch (e) { fail('invalid request: $e'); } diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc index 35e6f8d5524..9e33da6239e 100644 --- a/runtime/vm/dart.cc +++ b/runtime/vm/dart.cc @@ -216,6 +216,7 @@ char* Dart::Init(const uint8_t* vm_isolate_snapshot, TimelineDurationScope tds(Timeline::GetVMStream(), "Dart::Init"); #endif Isolate::InitVM(); + IsolateGroup::Init(); PortMap::Init(); FreeListElement::Init(); ForwardingCorpse::Init(); @@ -252,6 +253,7 @@ char* Dart::Init(const uint8_t* vm_isolate_snapshot, new IsolateGroupSource(nullptr, "vm-isolate", nullptr, nullptr, nullptr, nullptr, nullptr, -1, api_flags)); auto group = new IsolateGroup(std::move(source), /*embedder_data=*/nullptr); + IsolateGroup::RegisterIsolateGroup(group); vm_isolate_ = Isolate::InitIsolate("vm-isolate", group, api_flags, is_vm_isolate); group->set_initial_spawn_successful(); @@ -581,6 +583,7 @@ char* Dart::Cleanup() { vm_isolate_ = NULL; ASSERT(Isolate::IsolateListLength() == 0); PortMap::Cleanup(); + IsolateGroup::Cleanup(); ICData::Cleanup(); SubtypeTestCache::Cleanup(); ArgumentsDescriptor::Cleanup(); diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc index 7636aa3f0b9..ec3f932fcc0 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -1236,6 +1236,7 @@ Dart_CreateIsolateGroup(const char* script_uri, script_uri, non_null_name, snapshot_data, snapshot_instructions, shared_data, shared_instructions, nullptr, -1, *flags)); auto group = new IsolateGroup(std::move(source), isolate_group_data); + IsolateGroup::RegisterIsolateGroup(group); Dart_Isolate isolate = CreateIsolate(group, non_null_name, isolate_data, error); if (isolate != nullptr) { @@ -1266,6 +1267,7 @@ Dart_CreateIsolateGroupFromKernel(const char* script_uri, script_uri, non_null_name, nullptr, nullptr, nullptr, nullptr, kernel_buffer, kernel_buffer_size, *flags)); auto group = new IsolateGroup(std::move(source), isolate_group_data); + IsolateGroup::RegisterIsolateGroup(group); Dart_Isolate isolate = CreateIsolate(group, non_null_name, isolate_data, error); if (isolate != nullptr) { diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc index e78124cd076..d233325da80 100644 --- a/runtime/vm/isolate.cc +++ b/runtime/vm/isolate.cc @@ -173,6 +173,7 @@ void IsolateGroup::UnregisterIsolate(Isolate* isolate) { group_shutdown_callback(embedder_data()); } } + UnregisterIsolateGroup(this); delete this; } } @@ -316,6 +317,107 @@ void IsolateGroup::UnscheduleThread(Thread* thread, UnscheduleThreadLocked(&ml, thread, is_mutator, bypass_safepoint); } +#ifndef PRODUCT +void IsolateGroup::PrintJSON(JSONStream* stream, bool ref) { + if (!FLAG_support_service) { + return; + } + JSONObject jsobj(stream); + PrintToJSONObject(&jsobj, ref); +} + +void IsolateGroup::PrintToJSONObject(JSONObject* jsobj, bool ref) { + if (!FLAG_support_service) { + return; + } + jsobj->AddProperty("type", (ref ? "@IsolateGroup" : "IsolateGroup")); + jsobj->AddServiceId(ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING, id()); + + jsobj->AddProperty("name", "isolate_group"); + jsobj->AddPropertyF("number", "%" Pu64 "", id()); + if (ref) { + return; + } + + { + JSONArray isolate_array(jsobj, "isolates"); + for (auto it = isolates_.Begin(); it != isolates_.End(); ++it) { + Isolate* isolate = *it; + isolate_array.AddValue(isolate, /*ref=*/true); + } + } +} + +void IsolateGroup::PrintMemoryUsageJSON(JSONStream* stream) { + if (!FLAG_support_service) { + return; + } + int64_t used = 0; + int64_t capacity = 0; + int64_t external_used = 0; + + for (auto it = isolates_.Begin(); it != isolates_.End(); ++it) { + Isolate* isolate = *it; + used += isolate->heap()->TotalUsedInWords(); + capacity += isolate->heap()->TotalCapacityInWords(); + external_used += isolate->heap()->TotalExternalInWords(); + } + + JSONObject jsobj(stream); + // This is the same "MemoryUsage" that the isolate-specific "getMemoryUsage" + // rpc method returns. + // TODO(dartbug.com/36097): Once the heap moves from Isolate to IsolateGroup + // this code needs to be adjusted to not double-count memory. + jsobj.AddProperty("type", "MemoryUsage"); + jsobj.AddProperty64("heapUsage", used * kWordSize); + jsobj.AddProperty64("heapCapacity", capacity * kWordSize); + jsobj.AddProperty64("externalUsage", external_used * kWordSize); +} +#endif + +void IsolateGroup::ForEach(std::function action) { + ReadRwLocker wl(Thread::Current(), isolate_groups_rwlock_); + for (auto isolate_group : IsolateGroup::isolate_groups_) { + action(isolate_group); + } +} + +void IsolateGroup::RunWithIsolateGroup( + uint64_t id, + std::function action, + std::function not_found) { + ReadRwLocker wl(Thread::Current(), isolate_groups_rwlock_); + for (auto isolate_group : IsolateGroup::isolate_groups_) { + if (isolate_group->id() == id) { + action(isolate_group); + return; + } + } + not_found(); +} + +void IsolateGroup::RegisterIsolateGroup(IsolateGroup* isolate_group) { + WriteRwLocker wl(ThreadState::Current(), isolate_groups_rwlock_); + isolate_groups_.Append(isolate_group); +} + +void IsolateGroup::UnregisterIsolateGroup(IsolateGroup* isolate_group) { + WriteRwLocker wl(ThreadState::Current(), isolate_groups_rwlock_); + isolate_groups_.Remove(isolate_group); +} + +void IsolateGroup::Init() { + ASSERT(IsolateGroup::isolate_groups_rwlock_ == nullptr); + IsolateGroup::isolate_groups_rwlock_ = new RwLock(); + ASSERT(isolate_groups_.IsEmpty()); +} + +void IsolateGroup::Cleanup() { + delete isolate_groups_rwlock_; + isolate_groups_rwlock_ = nullptr; + ASSERT(isolate_groups_.IsEmpty()); +} + bool IsolateVisitor::IsVMInternalIsolate(Isolate* isolate) const { return Isolate::IsVMInternalIsolate(isolate); } @@ -2124,6 +2226,9 @@ Monitor* Isolate::isolates_list_monitor_ = nullptr; Isolate* Isolate::isolates_list_head_ = nullptr; bool Isolate::creation_enabled_ = false; +RwLock* IsolateGroup::isolate_groups_rwlock_; +IntrusiveDList IsolateGroup::isolate_groups_; + void Isolate::VisitObjectPointers(ObjectPointerVisitor* visitor, ValidationPolicy validate_frames) { ASSERT(visitor != nullptr); @@ -2487,6 +2592,11 @@ void Isolate::PrintJSON(JSONStream* stream, bool ref) { } jsobj.AddProperty("_threads", thread_registry()); + + { + JSONObject isolate_group(&jsobj, "isolate_group"); + group()->PrintToJSONObject(&isolate_group, /*ref=*/true); + } } void Isolate::PrintMemoryUsageJSON(JSONStream* stream) { diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h index 701d58f4d9d..a67393700a2 100644 --- a/runtime/vm/isolate.h +++ b/runtime/vm/isolate.h @@ -220,7 +220,7 @@ class IsolateGroupSource { }; // Represents an isolate group and is shared among all isolates within a group. -class IsolateGroup { +class IsolateGroup : public IntrusiveDListEntry { public: IsolateGroup(std::unique_ptr source, void* embedder_data); ~IsolateGroup(); @@ -282,6 +282,29 @@ class IsolateGroup { RunWithStoppedMutators(function, function); } +#ifndef PRODUCT + void PrintJSON(JSONStream* stream, bool ref = true); + void PrintToJSONObject(JSONObject* jsobj, bool ref); + + // Creates an object with the total heap memory usage statistics for this + // isolate group. + void PrintMemoryUsageJSON(JSONStream* stream); +#endif + + uint64_t id() { return id_; } + + static void Init(); + static void Cleanup(); + + static void ForEach(std::function action); + static void RunWithIsolateGroup(uint64_t id, + std::function action, + std::function not_found); + + // Manage list of existing isolate groups. + static void RegisterIsolateGroup(IsolateGroup* isolate_group); + static void UnregisterIsolateGroup(IsolateGroup* isolate_group); + private: void* embedder_data_ = nullptr; @@ -294,6 +317,12 @@ class IsolateGroup { std::unique_ptr source_; std::unique_ptr thread_registry_; std::unique_ptr safepoint_handler_; + + static RwLock* isolate_groups_rwlock_; + static IntrusiveDList isolate_groups_; + + Random isolate_group_random_; + uint64_t id_ = isolate_group_random_.NextUInt64(); }; class Isolate : public BaseIsolate, public IntrusiveDListEntry { diff --git a/runtime/vm/json_stream.cc b/runtime/vm/json_stream.cc index 33a990c78fa..07035fe43ff 100644 --- a/runtime/vm/json_stream.cc +++ b/runtime/vm/json_stream.cc @@ -346,6 +346,11 @@ void JSONStream::PrintValue(Isolate* isolate, bool ref) { isolate->PrintJSON(this, ref); } +void JSONStream::PrintValue(IsolateGroup* isolate_group, bool ref) { + PrintCommaIfNeeded(); + isolate_group->PrintJSON(this, ref); +} + void JSONStream::PrintValue(ThreadRegistry* reg) { PrintCommaIfNeeded(); reg->PrintJSON(this); diff --git a/runtime/vm/json_stream.h b/runtime/vm/json_stream.h index b9fdef7f654..f0ee450f8a9 100644 --- a/runtime/vm/json_stream.h +++ b/runtime/vm/json_stream.h @@ -203,6 +203,7 @@ class JSONStream : ValueObject { void PrintValue(Metric* metric); void PrintValue(MessageQueue* queue); void PrintValue(Isolate* isolate, bool ref = true); + void PrintValue(IsolateGroup* isolate, bool ref = true); void PrintValue(ThreadRegistry* reg); void PrintValue(Thread* thread); void PrintValue(const TimelineEvent* timeline_event); @@ -438,6 +439,9 @@ class JSONArray : public ValueObject { void AddValue(Isolate* isolate, bool ref = true) const { stream_->PrintValue(isolate, ref); } + void AddValue(IsolateGroup* isolate_group, bool ref = true) const { + stream_->PrintValue(isolate_group, ref); + } void AddValue(ThreadRegistry* reg) const { stream_->PrintValue(reg); } void AddValue(Thread* thread) const { stream_->PrintValue(thread); } void AddValue(Breakpoint* bpt) const { stream_->PrintValue(bpt); } diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index 43225fb55f1..39a474fbd51 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -591,6 +591,7 @@ class RunnableIsolateParameter : public MethodParameter { }; #define ISOLATE_PARAMETER new IdParameter("isolateId", true) +#define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true) #define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId") #define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId") @@ -1370,6 +1371,65 @@ static bool GetIsolate(Thread* thread, JSONStream* js) { return true; } +static const MethodParameter* get_isolate_group_params[] = { + ISOLATE_GROUP_PARAMETER, + NULL, +}; + +enum SentinelType { + kCollectedSentinel, + kExpiredSentinel, + kFreeSentinel, +}; + +static void PrintSentinel(JSONStream* js, SentinelType sentinel_type) { + JSONObject jsobj(js); + jsobj.AddProperty("type", "Sentinel"); + switch (sentinel_type) { + case kCollectedSentinel: + jsobj.AddProperty("kind", "Collected"); + jsobj.AddProperty("valueAsString", ""); + break; + case kExpiredSentinel: + jsobj.AddProperty("kind", "Expired"); + jsobj.AddProperty("valueAsString", ""); + break; + case kFreeSentinel: + jsobj.AddProperty("kind", "Free"); + jsobj.AddProperty("valueAsString", ""); + break; + default: + UNIMPLEMENTED(); + break; + } +} + +static void ActOnIsolateGroup(JSONStream* js, + std::function visitor) { + const String& prefix = + String::Handle(String::New(ISOLATE_GROUP_SERVICE_ID_PREFIX)); + + const String& s = + String::Handle(String::New(js->LookupParam("isolateGroupId"))); + if (!s.StartsWith(prefix)) { + PrintInvalidParamError(js, "isolateGroupId"); + return; + } + uint64_t isolate_group_id = UIntParameter::Parse( + String::Handle(String::SubString(s, prefix.Length())).ToCString()); + IsolateGroup::RunWithIsolateGroup( + isolate_group_id, + [&visitor](IsolateGroup* isolate_group) { visitor(isolate_group); }, + /*if_not_found=*/[&js]() { PrintSentinel(js, kExpiredSentinel); }); +} + +static bool GetIsolateGroup(Thread* thread, JSONStream* js) { + ActOnIsolateGroup(js, [&](IsolateGroup* isolate_group) { + isolate_group->PrintJSON(js, false); + }); + return true; +} + static const MethodParameter* get_memory_usage_params[] = { ISOLATE_PARAMETER, NULL, @@ -1380,6 +1440,18 @@ static bool GetMemoryUsage(Thread* thread, JSONStream* js) { return true; } +static const MethodParameter* get_isolate_group_memory_usage_params[] = { + ISOLATE_GROUP_PARAMETER, + NULL, +}; + +static bool GetIsolateGroupMemoryUsage(Thread* thread, JSONStream* js) { + ActOnIsolateGroup(js, [&](IsolateGroup* isolate_group) { + isolate_group->PrintMemoryUsageJSON(js); + }); + return true; +} + static const MethodParameter* get_scripts_params[] = { RUNNABLE_ISOLATE_PARAMETER, NULL, @@ -1936,34 +2008,6 @@ static RawObject* LookupHeapObject(Thread* thread, return Object::sentinel().raw(); } -enum SentinelType { - kCollectedSentinel, - kExpiredSentinel, - kFreeSentinel, -}; - -static void PrintSentinel(JSONStream* js, SentinelType sentinel_type) { - JSONObject jsobj(js); - jsobj.AddProperty("type", "Sentinel"); - switch (sentinel_type) { - case kCollectedSentinel: - jsobj.AddProperty("kind", "Collected"); - jsobj.AddProperty("valueAsString", ""); - break; - case kExpiredSentinel: - jsobj.AddProperty("kind", "Expired"); - jsobj.AddProperty("valueAsString", ""); - break; - case kFreeSentinel: - jsobj.AddProperty("kind", "Free"); - jsobj.AddProperty("valueAsString", ""); - break; - default: - UNIMPLEMENTED(); - break; - } -} - static Breakpoint* LookupBreakpoint(Isolate* isolate, const char* id, ObjectIdRing::LookupResult* result) { @@ -4336,12 +4380,18 @@ void Service::PrintJSONForVM(JSONStream* js, bool ref) { "startTime", OS::GetCurrentTimeMillis() - Dart::UptimeMillis()); MallocHooks::PrintToJSONObject(&jsobj); PrintJSONForEmbedderInformation(&jsobj); - // Construct the isolate list. + // Construct the isolate and isolate_groups list. { JSONArray jsarr(&jsobj, "isolates"); ServiceIsolateVisitor visitor(&jsarr); Isolate::VisitIsolates(&visitor); } + { + JSONArray jsarr_isolate_groups(&jsobj, "isolateGroups"); + IsolateGroup::ForEach([&jsarr_isolate_groups](IsolateGroup* isolate_group) { + jsarr_isolate_groups.AddValue(isolate_group); + }); + } } static bool GetVM(Thread* thread, JSONStream* js) { @@ -4687,8 +4737,12 @@ static const ServiceMethodDescriptor service_methods_[] = { get_instances_params }, { "getIsolate", GetIsolate, get_isolate_params }, + { "getIsolateGroup", GetIsolateGroup, + get_isolate_group_params }, { "getMemoryUsage", GetMemoryUsage, get_memory_usage_params }, + { "getIsolateGroupMemoryUsage", GetIsolateGroupMemoryUsage, + get_isolate_group_memory_usage_params }, { "_getIsolateMetric", GetIsolateMetric, get_isolate_metric_params }, { "_getIsolateMetricList", GetIsolateMetricList, diff --git a/runtime/vm/service.h b/runtime/vm/service.h index d1ce7a770f7..f28362756b4 100644 --- a/runtime/vm/service.h +++ b/runtime/vm/service.h @@ -15,7 +15,7 @@ namespace dart { #define SERVICE_PROTOCOL_MAJOR_VERSION 3 -#define SERVICE_PROTOCOL_MINOR_VERSION 27 +#define SERVICE_PROTOCOL_MINOR_VERSION 28 class Array; class EmbedderServiceHandler; @@ -24,6 +24,7 @@ class GCEvent; class GrowableObjectArray; class Instance; class Isolate; +class IsolateGroup; class JSONStream; class JSONObject; class Object; @@ -44,6 +45,9 @@ class ServiceIdZone { }; #define ISOLATE_SERVICE_ID_FORMAT_STRING "isolates/%" Pd64 "" +#define ISOLATE_GROUP_SERVICE_ID_PREFIX "isolateGroups/" +#define ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING \ + ISOLATE_GROUP_SERVICE_ID_PREFIX "%" Pu64 "" class RingServiceIdZone : public ServiceIdZone { public: diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md index 25b08e96f2f..528c557de68 100644 --- a/runtime/vm/service/service.md +++ b/runtime/vm/service/service.md @@ -37,6 +37,7 @@ The Service Protocol uses [JSON-RPC 2.0][]. - [getInstances](#getinstances) - [getInboundReferences](#getinboundreferences) - [getIsolate](#getisolate) + - [getIsolateGroup](#getisolategroup) - [getMemoryUsage](#getmemoryusage) - [getObject](#getobject) - [getRetainingPath](#getretainingpath) @@ -92,6 +93,7 @@ The Service Protocol uses [JSON-RPC 2.0][]. - [Instance](#instance) - [InstanceSet](#instanceset) - [Isolate](#isolate) + - [IsolateGroup](#isolategroup) - [Library](#library) - [LibraryDependency](#librarydependency) - [LogRecord](#logrecord) @@ -760,6 +762,22 @@ _Collected_ [Sentinel](#sentinel) is returned. See [Isolate](#isolate). +### getIsolateGroup + +``` +IsolateGroup|Sentinel getIsolateGroup(string isolateGroupId) +``` + +The _getIsolateGroup_ RPC is used to lookup an _IsolateGroup_ object by its _id_. + +If _isolateGroupId_ refers to an isolate group which has exited, then the +_Expired_ [Sentinel](#sentinel) is returned. + +_IsolateGroup_ _id_ is an opaque identifier that can be fetched from an + _IsolateGroup_. List of active _IsolateGroup_'s, for example, is available on _VM_ object. + +See [IsolateGroup](#isolategroup), [VM](#vm). + ### getMemoryUsage ``` @@ -774,6 +792,19 @@ _Collected_ [Sentinel](#sentinel) is returned. See [Isolate](#isolate). +### getIsolateGroupMemoryUsage + +``` +MemoryUsage|Sentinel getIsolateGroupMemoryUsage(string isolateGroupId) +``` + +The _getIsolateGroupMemoryUsage_ RPC is used to lookup an isolate +group's memory usage statistics by its _id_. + +If _isolateGroupId_ refers to an isolate group which has exited, then the _Expired_ [Sentinel](#sentinel) is returned. + +See [IsolateGroup](#isolategroup). + ### getScripts ``` @@ -2598,6 +2629,42 @@ class Isolate extends Response { An _Isolate_ object provides information about one isolate in the VM. +### IsolateGroup + +``` +class @IsolateGroup extends Response { + // The id which is passed to the getIsolateGroup RPC to load this isolate group. + string id; + + // A numeric id for this isolate group, represented as a string. Unique. + string number; + + // A name identifying this isolate group. Not guaranteed to be unique. + string name; +} +``` + +_@IsolateGroup_ is a reference to an _IsolateGroup_ object. + +``` +class IsolateGroup extends Response { + // The id which is passed to the getIsolate RPC to reload this + // isolate. + string id; + + // A numeric id for this isolate, represented as a string. Unique. + string number; + + // A name identifying this isolate. Not guaranteed to be unique. + string name; + + // A list of all isolates in this isolate group. + @Isolate[] isolates; +} +``` + +An _Isolate_ object provides information about one isolate in the VM. + ### InboundReferences ``` @@ -3438,6 +3505,9 @@ class VM extends Response { // A list of isolates running in the VM. @Isolate[] isolates; + + // A list of isolate groups running in the VM. + @IsolateGroup[] isolateGroups; } ```