diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md index cccc7a87c03..55fbb5ef90e 100644 --- a/pkg/dds/CHANGELOG.md +++ b/pkg/dds/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.5 +- Update to new CpuSamplesEvent format for CPU sample caching for improved + performance. + # 2.1.4 - A new library `package:dds/dap.dart` exposes classes required to build a custom DAP debug-adapter on top of the base Dart DAP functionality in DDS. diff --git a/pkg/dds/lib/src/cpu_samples_manager.dart b/pkg/dds/lib/src/cpu_samples_manager.dart index a0c413ace20..108da99d265 100644 --- a/pkg/dds/lib/src/cpu_samples_manager.dart +++ b/pkg/dds/lib/src/cpu_samples_manager.dart @@ -52,21 +52,31 @@ class CpuSamplesRepository extends RingBuffer { int bufferSize = 1000000, ]) : super(bufferSize); - void cacheSamples(CpuSamples samples) { - String getFunctionId(ProfileFunction function) { - final functionObject = function.function; - if (functionObject is NativeFunction) { - return 'native/${functionObject.name}'; - } - return functionObject.id!; - } + ProfileFunction _buildProfileFunction(dynamic function) { + // `kind` and `resolvedUrl` are populated in `populateFunctionDetails()`. + return ProfileFunction( + kind: '', + inclusiveTicks: -1, + exclusiveTicks: -1, + resolvedUrl: '', + function: function, + ); + } + String _getFunctionId(dynamic function) { + if (function is NativeFunction) { + return 'native/${function.name}'; + } + return function.id!; + } + + void cacheSamples(CpuSamplesEvent samples) { // Initialize upon seeing our first samples. if (functions.isEmpty) { samplePeriod = samples.samplePeriod!; maxStackDepth = samples.maxStackDepth!; pid = samples.pid!; - functions.addAll(samples.functions!); + functions.addAll(samples.functions!.map(_buildProfileFunction)); // Build the initial id to function index mapping. This allows for us to // lookup a ProfileFunction in the global function list stored in this @@ -77,7 +87,7 @@ class CpuSamplesRepository extends RingBuffer { // TODO(bkonyi): investigate creating some form of stable ID for // Functions tied to closures. for (int i = 0; i < functions.length; ++i) { - idToFunctionIndex[getFunctionId(functions[i])] = i; + idToFunctionIndex[_getFunctionId(functions[i].function)] = i; } // Clear tick information as we'll need to recalculate these values later @@ -94,14 +104,14 @@ class CpuSamplesRepository extends RingBuffer { // Check to see if we've got a function object we've never seen before. for (int i = 0; i < newFunctions.length; ++i) { - final key = getFunctionId(newFunctions[i]); + final key = _getFunctionId(newFunctions[i]); if (!idToFunctionIndex.containsKey(key)) { idToFunctionIndex[key] = functions.length; // Keep track of the original index and the location of the function // in the master function list so we can update the function indicies // for each sample in this batch. indexMapping[i] = functions.length; - functions.add(newFunctions[i]); + functions.add(_buildProfileFunction(newFunctions[i])); // Reset tick state as we'll recalculate later. functions.last.inclusiveTicks = 0; @@ -159,6 +169,28 @@ class CpuSamplesRepository extends RingBuffer { return evicted; } + Future populateFunctionDetails( + DartDevelopmentServiceImpl dds, String isolateId) async { + final cpuSamples = await dds.vmServiceClient.sendRequest('getCpuSamples', { + 'isolateId': isolateId, + 'timeOriginMicros': 0, + 'timeExtentMicros': 0, + }); + final fullFunctions = cpuSamples['functions']; + for (final func in fullFunctions) { + final profileFunc = ProfileFunction.parse(func)!; + final id = _getFunctionId(profileFunc.function!); + final index = idToFunctionIndex[id]; + if (index == null) { + continue; + } + final result = functions[index]; + result.kind = profileFunc.kind; + result.resolvedUrl = profileFunc.resolvedUrl; + result.function = profileFunc.function; + } + } + Map toJson() { return { 'type': 'CachedCpuSamples', diff --git a/pkg/dds/lib/src/isolate_manager.dart b/pkg/dds/lib/src/isolate_manager.dart index 75221837109..7808b2a54d2 100644 --- a/pkg/dds/lib/src/isolate_manager.dart +++ b/pkg/dds/lib/src/isolate_manager.dart @@ -110,13 +110,14 @@ class _RunningIsolate { /// Should always be called after an isolate is resumed. void clearResumeApprovals() => _resumeApprovalsByName.clear(); - Map getCachedCpuSamples(String userTag) { + Future> getCachedCpuSamples(String userTag) async { final repo = cpuSamplesManager.cpuSamplesCaches[userTag]; if (repo == null) { throw json_rpc.RpcException.invalidParams( 'CPU sample caching is not enabled for tag: "$userTag"', ); } + await repo.populateFunctionDetails(isolateManager.dds, id); return repo.toJson(); } @@ -283,14 +284,15 @@ class IsolateManager { ); } - Map getCachedCpuSamples(json_rpc.Parameters parameters) { + Future> getCachedCpuSamples( + json_rpc.Parameters parameters) async { final isolateId = parameters['isolateId'].asString; if (!isolates.containsKey(isolateId)) { return RPCResponses.collectedSentinel; } final isolate = isolates[isolateId]!; final userTag = parameters['userTag'].asString; - return isolate.getCachedCpuSamples(userTag); + return await isolate.getCachedCpuSamples(userTag); } /// Forwards a `resume` request to the VM service. diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml index e575eaec51c..e323748b32e 100644 --- a/pkg/dds/pubspec.yaml +++ b/pkg/dds/pubspec.yaml @@ -3,7 +3,7 @@ description: >- A library used to spawn the Dart Developer Service, used to communicate with a Dart VM Service instance. -version: 2.1.4 +version: 2.1.5 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds diff --git a/pkg/dds/test/get_cached_cpu_samples_test.dart b/pkg/dds/test/get_cached_cpu_samples_test.dart index 77d2bdbb536..d8e900ef32b 100644 --- a/pkg/dds/test/get_cached_cpu_samples_test.dart +++ b/pkg/dds/test/get_cached_cpu_samples_test.dart @@ -42,8 +42,15 @@ void main() { final availableCaches = await service.getAvailableCachedCpuSamples(); expect(availableCaches.cacheNames.length, 0); - final isolate = (await service.getVM()).isolates!.first; - + IsolateRef isolate; + while (true) { + final vm = await service.getVM(); + if (vm.isolates!.isNotEmpty) { + isolate = vm.isolates!.first; + break; + } + await Future.delayed(const Duration(seconds: 1)); + } try { await service.getCachedCpuSamples(isolate.id!, 'Fake'); fail('Invalid userTag did not cause an exception'); @@ -73,7 +80,18 @@ void main() { expect(availableCaches.cacheNames.length, 1); expect(availableCaches.cacheNames.first, kUserTag); - final isolate = (await service.getVM()).isolates!.first; + IsolateRef isolate; + while (true) { + final vm = await service.getVM(); + if (vm.isolates!.isNotEmpty) { + isolate = vm.isolates!.first; + isolate = await service.getIsolate(isolate.id!); + if ((isolate as Isolate).runnable!) { + break; + } + } + await Future.delayed(const Duration(seconds: 1)); + } final completer = Completer(); int i = 0; diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md index 760c9d9b99c..16605c0630b 100644 --- a/pkg/vm_service/CHANGELOG.md +++ b/pkg/vm_service/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 8.0.0 +- *breaking* Updated type of `Event.cpuSamples` from `CpuSamples` to + `CpuSamplesEvent`, which is less expensive to generate and serialize. +- Added `CpuSamplesEvent` object. + ## 7.5.0 - Update to version `3.53` of the spec. - Added `setIsolatePauseMode` RPC. diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart index f30e7944d9e..aa1b7c79c83 100644 --- a/pkg/vm_service/example/vm_service_assert.dart +++ b/pkg/vm_service/example/vm_service_assert.dart @@ -453,6 +453,20 @@ vms.CpuSamples assertCpuSamples(vms.CpuSamples obj) { return obj; } +vms.CpuSamplesEvent assertCpuSamplesEvent(vms.CpuSamplesEvent obj) { + assertNotNull(obj); + assertInt(obj.samplePeriod!); + assertInt(obj.maxStackDepth!); + assertInt(obj.sampleCount!); + assertInt(obj.timeSpan!); + assertInt(obj.timeOriginMicros!); + assertInt(obj.timeExtentMicros!); + assertInt(obj.pid!); + assertListOfDynamic(obj.functions!); + assertListOfCpuSample(obj.samples!); + return obj; +} + vms.CpuSample assertCpuSample(vms.CpuSample obj) { assertNotNull(obj); assertInt(obj.tid!); diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore index a54ba8c0584..5fe823ee8da 100644 --- a/pkg/vm_service/java/.gitignore +++ b/pkg/vm_service/java/.gitignore @@ -63,6 +63,7 @@ src/org/dartlang/vm/service/element/ContextElement.java src/org/dartlang/vm/service/element/ContextRef.java src/org/dartlang/vm/service/element/CpuSample.java src/org/dartlang/vm/service/element/CpuSamples.java +src/org/dartlang/vm/service/element/CpuSamplesEvent.java src/org/dartlang/vm/service/element/ErrorKind.java src/org/dartlang/vm/service/element/ErrorObj.java src/org/dartlang/vm/service/element/ErrorRef.java diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties index b8e9261ad9e..84daa3bf4c0 100644 --- a/pkg/vm_service/java/version.properties +++ b/pkg/vm_service/java/version.properties @@ -1 +1 @@ -version=3.53 +version=3.54 diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart index 278e6cfffd4..fcdfe9b7b58 100644 --- a/pkg/vm_service/lib/src/vm_service.dart +++ b/pkg/vm_service/lib/src/vm_service.dart @@ -26,7 +26,7 @@ export 'snapshot_graph.dart' HeapSnapshotObjectNoData, HeapSnapshotObjectNullData; -const String vmServiceVersion = '3.53.0'; +const String vmServiceVersion = '3.54.0'; /// @optional const String optional = 'optional'; @@ -123,6 +123,7 @@ Map _typeFactories = { 'Context': Context.parse, 'ContextElement': ContextElement.parse, 'CpuSamples': CpuSamples.parse, + 'CpuSamplesEvent': CpuSamplesEvent.parse, 'CpuSample': CpuSample.parse, '@Error': ErrorRef.parse, 'Error': Error.parse, @@ -3658,6 +3659,92 @@ class CpuSamples extends Response { String toString() => '[CpuSamples]'; } +class CpuSamplesEvent { + static CpuSamplesEvent? parse(Map? json) => + json == null ? null : CpuSamplesEvent._fromJson(json); + + /// The sampling rate for the profiler in microseconds. + int? samplePeriod; + + /// The maximum possible stack depth for samples. + int? maxStackDepth; + + /// The number of samples returned. + int? sampleCount; + + /// The timespan the set of returned samples covers, in microseconds + /// (deprecated). + /// + /// Note: this property is deprecated and will always return -1. Use + /// `timeExtentMicros` instead. + int? timeSpan; + + /// The start of the period of time in which the returned samples were + /// collected. + int? timeOriginMicros; + + /// The duration of time covered by the returned samples. + int? timeExtentMicros; + + /// The process ID for the VM. + int? pid; + + /// A list of references to functions seen in the relevant samples. These + /// references can be looked up using the indicies provided in a `CpuSample` + /// `stack` to determine which function was on the stack. + List? functions; + + /// A list of samples collected in the range `[timeOriginMicros, + /// timeOriginMicros + timeExtentMicros]` + List? samples; + + CpuSamplesEvent({ + required this.samplePeriod, + required this.maxStackDepth, + required this.sampleCount, + required this.timeSpan, + required this.timeOriginMicros, + required this.timeExtentMicros, + required this.pid, + required this.functions, + required this.samples, + }); + + CpuSamplesEvent._fromJson(Map json) { + samplePeriod = json['samplePeriod'] ?? -1; + maxStackDepth = json['maxStackDepth'] ?? -1; + sampleCount = json['sampleCount'] ?? -1; + timeSpan = json['timeSpan'] ?? -1; + timeOriginMicros = json['timeOriginMicros'] ?? -1; + timeExtentMicros = json['timeExtentMicros'] ?? -1; + pid = json['pid'] ?? -1; + functions = List.from( + createServiceObject(json['functions'], const ['dynamic']) as List? ?? + []); + samples = List.from( + createServiceObject(json['samples'], const ['CpuSample']) as List? ?? + []); + } + + Map toJson() { + final json = {}; + json.addAll({ + 'samplePeriod': samplePeriod, + 'maxStackDepth': maxStackDepth, + 'sampleCount': sampleCount, + 'timeSpan': timeSpan, + 'timeOriginMicros': timeOriginMicros, + 'timeExtentMicros': timeExtentMicros, + 'pid': pid, + 'functions': functions?.map((f) => f.toJson()).toList(), + 'samples': samples?.map((f) => f.toJson()).toList(), + }); + return json; + } + + String toString() => '[CpuSamplesEvent]'; +} + /// See [getCpuSamples] and [CpuSamples]. class CpuSample { static CpuSample? parse(Map? json) => @@ -4051,7 +4138,7 @@ class Event extends Response { /// A CPU profile containing recent samples. @optional - CpuSamples? cpuSamples; + CpuSamplesEvent? cpuSamples; /// Binary data associated with the event. /// @@ -4132,8 +4219,9 @@ class Event extends Response { last = json['last']; updatedTag = json['updatedTag']; previousTag = json['previousTag']; - cpuSamples = createServiceObject(json['cpuSamples'], const ['CpuSamples']) - as CpuSamples?; + cpuSamples = + createServiceObject(json['cpuSamples'], const ['CpuSamplesEvent']) + as CpuSamplesEvent?; data = json['data']; } diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml index 8bd5ed5b4a3..d613713aa00 100644 --- a/pkg/vm_service/pubspec.yaml +++ b/pkg/vm_service/pubspec.yaml @@ -3,7 +3,7 @@ description: >- A library to communicate with a service implementing the Dart VM service protocol. -version: 7.5.0 +version: 8.0.0 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service diff --git a/pkg/vm_service/test/common/test_helper.dart b/pkg/vm_service/test/common/test_helper.dart index 8bf30b88873..e16e307da23 100644 --- a/pkg/vm_service/test/common/test_helper.dart +++ b/pkg/vm_service/test/common/test_helper.dart @@ -277,6 +277,7 @@ class _ServiceTesterRunner { vm = await vmServiceConnectUri(serviceWebsocketAddress); print('Done loading VM'); isolate = await getFirstIsolate(vm); + print('Got first isolate'); }); }); @@ -342,6 +343,8 @@ class _ServiceTesterRunner { }); await service.streamListen(EventStreams.kIsolate); + await service.streamListen(EventStreams.kIsolate); + // The isolate may have started before we subscribed. vm = await service.getVM(); if (vmIsolates.isNotEmpty) { diff --git a/pkg/vm_service/tool/dart/generate_dart.dart b/pkg/vm_service/tool/dart/generate_dart.dart index 4f2eb167839..d59b1e7e6a6 100644 --- a/pkg/vm_service/tool/dart/generate_dart.dart +++ b/pkg/vm_service/tool/dart/generate_dart.dart @@ -935,6 +935,10 @@ dynamic assertDynamic(dynamic obj) { return obj; } +List assertListOfDynamic(List list) { + return list; +} + List assertListOfInt(List list) { for (int elem in list) { assertInt(elem); diff --git a/runtime/observatory/tests/service/get_object_rpc_test.dart b/runtime/observatory/tests/service/get_object_rpc_test.dart index d5531385117..a1f05820b95 100644 --- a/runtime/observatory/tests/service/get_object_rpc_test.dart +++ b/runtime/observatory/tests/service/get_object_rpc_test.dart @@ -14,8 +14,12 @@ import 'test_helper.dart'; class _DummyClass { static var dummyVar = 11; final List dummyList = new List.filled(20, ''); + static var dummyVarWithInit = foo(); + late String dummyLateVarWithInit = 'bar'; + late String dummyLateVar; void dummyFunction(int a, [bool b = false]) {} void dummyGenericFunction(K a, {required V param}) {} + static List foo() => List.filled(20, ''); } class _DummySubClass extends _DummyClass {} @@ -982,6 +986,83 @@ var tests = [ expect(result['_guardLength'], isNotNull); }, + // static field initializer + (Isolate isolate) async { + // Call eval to get a class id. + var evalResult = await invoke(isolate, 'getDummyClass'); + var id = "${evalResult['class']['id']}/field_inits/dummyVarWithInit"; + var params = { + 'objectId': id, + }; + var result = await isolate.invokeRpcNoUpgrade('getObject', params); + expect(result['type'], equals('Function')); + expect(result['id'], equals(id)); + expect(result['name'], equals('dummyVarWithInit')); + expect(result['_kind'], equals('FieldInitializer')); + expect(result['static'], equals(true)); + expect(result['const'], equals(false)); + expect(result['implicit'], equals(false)); + expect(result['signature']['typeParameters'], isNull); + expect(result['signature']['returnType'], isNotNull); + expect(result['signature']['parameters'].length, 0); + expect(result['location']['type'], equals('SourceLocation')); + expect(result['code']['type'], equals('@Code')); + expect(result['_optimizable'], equals(true)); + expect(result['_inlinable'], equals(false)); + expect(result['_usageCounter'], isZero); + expect(result['_optimizedCallSiteCount'], isZero); + expect(result['_deoptimizations'], isZero); + }, + + // late field initializer + (Isolate isolate) async { + // Call eval to get a class id. + var evalResult = await invoke(isolate, 'getDummyClass'); + var id = "${evalResult['class']['id']}/field_inits/dummyLateVarWithInit"; + var params = { + 'objectId': id, + }; + var result = await isolate.invokeRpcNoUpgrade('getObject', params); + expect(result['type'], equals('Function')); + expect(result['id'], equals(id)); + expect(result['name'], equals('dummyLateVarWithInit')); + expect(result['_kind'], equals('FieldInitializer')); + expect(result['static'], equals(false)); + expect(result['const'], equals(false)); + expect(result['implicit'], equals(false)); + expect(result['signature']['typeParameters'], isNull); + expect(result['signature']['returnType'], isNotNull); + expect(result['signature']['parameters'].length, 1); + expect(result['location']['type'], equals('SourceLocation')); + expect(result['code']['type'], equals('@Code')); + expect(result['_optimizable'], equals(true)); + expect(result['_inlinable'], equals(false)); + expect(result['_usageCounter'], isZero); + expect(result['_optimizedCallSiteCount'], isZero); + expect(result['_deoptimizations'], isZero); + }, + + // invalid late field initialize. + (Isolate isolate) async { + // Call eval to get a class id. + var evalResult = await invoke(isolate, 'getDummyClass'); + var id = "${evalResult['class']['id']}/field_inits/dummyLateVar"; + var params = { + 'objectId': id, + }; + bool caughtException = false; + try { + await isolate.invokeRpcNoUpgrade('getObject', params); + expect(false, isTrue, reason: 'Unreachable'); + } on ServerRpcException catch (e) { + caughtException = true; + expect(e.code, equals(ServerRpcException.kInvalidParams)); + expect( + e.message, startsWith("getObject: invalid 'objectId' parameter: ")); + } + expect(caughtException, isTrue); + }, + // field with guards (Isolate isolate) async { var result = await isolate.vm.invokeRpcNoUpgrade('getFlagList', {}); diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart index 7b1f2751d3f..59afe6d6949 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 = [ final result = await vm.invokeRpcNoUpgrade('getVersion', {}); expect(result['type'], 'Version'); expect(result['major'], 3); - expect(result['minor'], 53); + expect(result['minor'], 54); expect(result['_privateMajor'], 0); expect(result['_privateMinor'], 0); }, diff --git a/runtime/observatory_2/tests/service_2/get_object_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_object_rpc_test.dart index 44912b3a3e3..9c45a9e17b2 100644 --- a/runtime/observatory_2/tests/service_2/get_object_rpc_test.dart +++ b/runtime/observatory_2/tests/service_2/get_object_rpc_test.dart @@ -14,8 +14,10 @@ import 'test_helper.dart'; class _DummyClass { static var dummyVar = 11; final List dummyList = new List.filled(20, null); + static var dummyVarWithInit = foo(); void dummyFunction(int a, [bool b = false]) {} void dummyGenericFunction(K a, {V param}) {} + static List foo() => List.filled(20, ''); } class _DummySubClass extends _DummyClass {} @@ -1014,6 +1016,34 @@ var tests = [ expect(result['_guardLength'], equals('20')); }, + // static field initializer + (Isolate isolate) async { + // Call eval to get a class id. + var evalResult = await invoke(isolate, 'getDummyClass'); + var id = "${evalResult['class']['id']}/field_inits/dummyVarWithInit"; + var params = { + 'objectId': id, + }; + var result = await isolate.invokeRpcNoUpgrade('getObject', params); + expect(result['type'], equals('Function')); + expect(result['id'], equals(id)); + expect(result['name'], equals('dummyVarWithInit')); + expect(result['_kind'], equals('FieldInitializer')); + expect(result['static'], equals(true)); + expect(result['const'], equals(false)); + expect(result['implicit'], equals(false)); + expect(result['signature']['typeParameters'], isNull); + expect(result['signature']['returnType'], isNotNull); + expect(result['signature']['parameters'].length, 0); + expect(result['location']['type'], equals('SourceLocation')); + expect(result['code']['type'], equals('@Code')); + expect(result['_optimizable'], equals(true)); + expect(result['_inlinable'], equals(false)); + expect(result['_usageCounter'], isZero); + expect(result['_optimizedCallSiteCount'], isZero); + expect(result['_deoptimizations'], isZero); + }, + // invalid field. (Isolate isolate) async { // Call eval to get a class id. diff --git a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart index e9c87c9a855..4daa57abef9 100644 --- a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart +++ b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart @@ -12,7 +12,7 @@ var tests = [ final result = await vm.invokeRpcNoUpgrade('getVersion', {}); expect(result['type'], equals('Version')); expect(result['major'], equals(3)); - expect(result['minor'], equals(53)); + expect(result['minor'], equals(54)); expect(result['_privateMajor'], equals(0)); expect(result['_privateMinor'], equals(0)); }, diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 71924f7cb02..503be807a9f 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -3702,6 +3702,9 @@ class Function : public Object { // element 2 * i + 1 is coverage hit (zero meaning code was not hit) ArrayPtr GetCoverageArray() const; + // Outputs this function's service ID to the provided JSON object. + void AddFunctionServiceId(const JSONObject& obj) const; + // Sets deopt reason in all ICData-s with given deopt_id. void SetDeoptReasonForAll(intptr_t deopt_id, ICData::DeoptReasonId reason); diff --git a/runtime/vm/object_service.cc b/runtime/vm/object_service.cc index f7fe7fe7cc9..42ab4576012 100644 --- a/runtime/vm/object_service.cc +++ b/runtime/vm/object_service.cc @@ -248,22 +248,36 @@ void PatchClass::PrintJSONImpl(JSONStream* stream, bool ref) const { Object::PrintJSONImpl(stream, ref); } -static void AddFunctionServiceId(const JSONObject& jsobj, - const Function& f, - const Class& cls) { - ASSERT(!cls.IsNull()); +void Function::AddFunctionServiceId(const JSONObject& jsobj) const { + Class& cls = Class::Handle(Owner()); // Special kinds of functions use indices in their respective lists. intptr_t id = -1; const char* selector = NULL; - if (f.IsNonImplicitClosureFunction()) { - id = ClosureFunctionsCache::FindClosureIndex(f); + // Regular functions known to their owner use their name (percent-encoded). + String& name = String::Handle(this->name()); + + if (IsNonImplicitClosureFunction()) { + id = ClosureFunctionsCache::FindClosureIndex(*this); selector = "closures"; - } else if (f.IsImplicitClosureFunction()) { - id = cls.FindImplicitClosureFunctionIndex(f); + } else if (IsImplicitClosureFunction()) { + id = cls.FindImplicitClosureFunctionIndex(*this); selector = "implicit_closures"; - } else if (f.IsNoSuchMethodDispatcher() || f.IsInvokeFieldDispatcher()) { - id = cls.FindInvocationDispatcherFunctionIndex(f); + } else if (IsNoSuchMethodDispatcher() || IsInvokeFieldDispatcher()) { + id = cls.FindInvocationDispatcherFunctionIndex(*this); selector = "dispatchers"; + } else if (IsFieldInitializer()) { + name ^= Field::NameFromInit(name); + const char* encoded_field_name = String::EncodeIRI(name); + if (cls.IsTopLevel()) { + const auto& library = Library::Handle(cls.library()); + const auto& private_key = String::Handle(library.private_key()); + jsobj.AddFixedServiceId("libraries/%s/field_inits/%s", + private_key.ToCString(), encoded_field_name); + } else { + jsobj.AddFixedServiceId("classes/%" Pd "/field_inits/%s", cls.id(), + encoded_field_name); + } + return; } if (id != -1) { ASSERT(selector != NULL); @@ -278,10 +292,8 @@ static void AddFunctionServiceId(const JSONObject& jsobj, } return; } - // Regular functions known to their owner use their name (percent-encoded). - String& name = String::Handle(f.name()); Thread* thread = Thread::Current(); - if (Resolver::ResolveFunction(thread->zone(), cls, name) == f.ptr()) { + if (Resolver::ResolveFunction(thread->zone(), cls, name) == ptr()) { const char* encoded_name = String::EncodeIRI(name); if (cls.IsTopLevel()) { const auto& library = Library::Handle(cls.library()); @@ -297,7 +309,7 @@ static void AddFunctionServiceId(const JSONObject& jsobj, // Oddball functions (not known to their owner) fall back to use the object // id ring. Current known examples are signature functions of closures // and stubs like 'megamorphic_call_miss'. - jsobj.AddServiceId(f); + jsobj.AddServiceId(*this); } void Function::PrintJSONImpl(JSONStream* stream, bool ref) const { @@ -308,7 +320,7 @@ void Function::PrintJSONImpl(JSONStream* stream, bool ref) const { ASSERT(err.IsNull()); JSONObject jsobj(stream); AddCommonObjectProperties(&jsobj, "Function", ref); - AddFunctionServiceId(jsobj, *this, cls); + AddFunctionServiceId(jsobj); const char* user_name = UserVisibleNameCString(); const String& vm_name = String::Handle(name()); AddNameProperties(&jsobj, user_name, vm_name.ToCString()); diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc index 58cab0e37ee..f1f721bd05c 100644 --- a/runtime/vm/profiler.cc +++ b/runtime/vm/profiler.cc @@ -250,7 +250,6 @@ void SampleBlockBuffer::ProcessCompletedBlocks() { ProcessedSampleBuffer* SampleBlockListProcessor::BuildProcessedSampleBuffer( SampleFilter* filter, ProcessedSampleBuffer* buffer) { - ASSERT(filter != NULL); Thread* thread = Thread::Current(); Zone* zone = thread->zone(); @@ -267,7 +266,6 @@ ProcessedSampleBuffer* SampleBlockListProcessor::BuildProcessedSampleBuffer( ProcessedSampleBuffer* SampleBlockBuffer::BuildProcessedSampleBuffer( SampleFilter* filter, ProcessedSampleBuffer* buffer) { - ASSERT(filter != NULL); Thread* thread = Thread::Current(); Zone* zone = thread->zone(); diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc index c82c0f91aec..1ae3a466b0a 100644 --- a/runtime/vm/profiler_service.cc +++ b/runtime/vm/profiler_service.cc @@ -170,7 +170,19 @@ void ProfileFunction::PrintToJSONObject(JSONObject* func) { func->AddProperty("_kind", KindToCString(kind())); } -void ProfileFunction::PrintToJSONArray(JSONArray* functions) { +void ProfileFunction::PrintToJSONArray(JSONArray* functions, + bool print_only_ids) { + if (print_only_ids) { + JSONObject obj(functions); + if (kind() == kDartFunction) { + ASSERT(!function_.IsNull()); + obj.AddProperty("type", "@Object"); + function_.AddFunctionServiceId(obj); + } else { + PrintToJSONObject(&obj); + } + return; + } JSONObject obj(functions); obj.AddProperty("type", "ProfileFunction"); obj.AddProperty("kind", KindToCString(kind())); @@ -1728,10 +1740,16 @@ void Profile::PrintProfileJSON(JSONStream* stream, bool include_code_samples) { PrintProfileJSON(&obj, include_code_samples); } -void Profile::PrintProfileJSON(JSONObject* obj, bool include_code_samples) { +void Profile::PrintProfileJSON(JSONObject* obj, + bool include_code_samples, + bool is_event) { ScopeTimer sw("Profile::PrintProfileJSON", FLAG_trace_profiler); Thread* thread = Thread::Current(); - obj->AddProperty("type", "CpuSamples"); + if (is_event) { + obj->AddProperty("type", "CpuSamplesEvent"); + } else { + obj->AddProperty("type", "CpuSamples"); + } PrintHeaderJSON(obj); if (include_code_samples) { JSONArray codes(obj, "_codes"); @@ -1760,7 +1778,7 @@ void Profile::PrintProfileJSON(JSONObject* obj, bool include_code_samples) { for (intptr_t i = 0; i < functions_->length(); i++) { ProfileFunction* function = functions_->At(i); ASSERT(function != NULL); - function->PrintToJSONArray(&functions); + function->PrintToJSONArray(&functions, is_event); thread->CheckForSafepoint(); } } diff --git a/runtime/vm/profiler_service.h b/runtime/vm/profiler_service.h index a9df89b717d..13a29fa411e 100644 --- a/runtime/vm/profiler_service.h +++ b/runtime/vm/profiler_service.h @@ -171,7 +171,7 @@ class ProfileFunction : public ZoneAllocated { static const char* KindToCString(Kind kind); - void PrintToJSONArray(JSONArray* functions); + void PrintToJSONArray(JSONArray* functions, bool print_only_ids = false); // Returns true if the call was successful and |pfsp| is set. bool GetSinglePosition(ProfileFunctionSourcePosition* pfsp); @@ -385,7 +385,9 @@ class Profile : public ValueObject { ProfileCode* GetCodeFromPC(uword pc, int64_t timestamp); void PrintProfileJSON(JSONStream* stream, bool include_code_samples); - void PrintProfileJSON(JSONObject* obj, bool include_code_samples); + void PrintProfileJSON(JSONObject* obj, + bool include_code_samples, + bool is_event = false); ProfileFunction* FindFunction(const Function& function); diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index dba544400ae..209387dd3a5 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -1791,6 +1791,18 @@ static ObjectPtr LookupClassMembers(Thread* thread, } return field.ptr(); } + if (strcmp(parts[2], "field_inits") == 0) { + // Field initializer ids look like: "classes/17/field_inits/name" + const auto& field = Field::Handle(klass.LookupField(id)); + if (field.IsNull() || (field.is_late() && !field.has_initializer())) { + return Object::sentinel().ptr(); + } + const auto& function = Function::Handle(field.EnsureInitializerFunction()); + if (function.IsNull()) { + return Object::sentinel().ptr(); + } + return function.ptr(); + } if (strcmp(parts[2], "functions") == 0) { // Function ids look like: "classes/17/functions/name" @@ -1883,6 +1895,10 @@ static ObjectPtr LookupHeapObjectLibraries(IsolateGroup* isolate_group, // Library field ids look like: "libraries/17/fields/name" return LookupClassMembers(Thread::Current(), klass, parts, num_parts); } + if (strcmp(parts[2], "field_inits") == 0) { + // Library field ids look like: "libraries/17/field_inits/name" + return LookupClassMembers(Thread::Current(), klass, parts, num_parts); + } if (strcmp(parts[2], "functions") == 0) { // Library function ids look like: "libraries/17/functions/name" return LookupClassMembers(Thread::Current(), klass, parts, num_parts); @@ -1952,6 +1968,9 @@ static ObjectPtr LookupHeapObjectClasses(Thread* thread, if (strcmp(parts[2], "closures") == 0) { // Closure ids look like: "classes/17/closures/11" return LookupClassMembers(thread, cls, parts, num_parts); + } else if (strcmp(parts[2], "field_inits") == 0) { + // Field initializer ids look like: "classes/17/field_inits/name" + return LookupClassMembers(thread, cls, parts, num_parts); } else if (strcmp(parts[2], "fields") == 0) { // Field ids look like: "classes/17/fields/name" return LookupClassMembers(thread, cls, parts, num_parts); diff --git a/runtime/vm/service.h b/runtime/vm/service.h index 59e9e244e15..c31b191490e 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 53 +#define SERVICE_PROTOCOL_MINOR_VERSION 54 class Array; class EmbedderServiceHandler; diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md index 0865d158c85..6a04228e50a 100644 --- a/runtime/vm/service/service.md +++ b/runtime/vm/service/service.md @@ -1,8 +1,8 @@ -# Dart VM Service Protocol 3.53 +# Dart VM Service Protocol 3.54 > Please post feedback to the [observatory-discuss group][discuss-list] -This document describes of _version 3.53_ of the Dart VM Service Protocol. This +This document describes of _version 3.54_ of the Dart VM Service Protocol. This protocol is used to communicate with a running Dart Virtual Machine. To use the Service Protocol, start the VM with the *--observe* flag. @@ -1987,6 +1987,46 @@ class CpuSamples extends Response { See [getCpuSamples](#getcpusamples) and [CpuSample](#cpusample). +### CpuSamplesEvent + +``` +class CpuSamplesEvent { + // The sampling rate for the profiler in microseconds. + int samplePeriod; + + // The maximum possible stack depth for samples. + int maxStackDepth; + + // The number of samples returned. + int sampleCount; + + // The timespan the set of returned samples covers, in microseconds (deprecated). + // + // Note: this property is deprecated and will always return -1. Use `timeExtentMicros` + // instead. + int timeSpan; + + // The start of the period of time in which the returned samples were + // collected. + int timeOriginMicros; + + // The duration of time covered by the returned samples. + int timeExtentMicros; + + // The process ID for the VM. + int pid; + + // A list of references to functions seen in the relevant samples. These references can + // be looked up using the indicies provided in a `CpuSample` `stack` to determine + // which function was on the stack. + (@Object|NativeFunction)[] functions; + + // A list of samples collected in the range + // `[timeOriginMicros, timeOriginMicros + timeExtentMicros]` + CpuSample[] samples; +} +``` + ### CpuSample ``` @@ -2251,7 +2291,7 @@ class Event extends Response { string previousTag [optional]; // A CPU profile containing recent samples. - CpuSamples cpuSamples [optional]; + CpuSamplesEvent cpuSamples [optional]; } ``` @@ -4263,4 +4303,6 @@ version | comments 3.51 | Added optional `reportLines` parameter to `getSourceReport` RPC. 3.52 | Added `lookupResolvedPackageUris` and `lookupPackageUris` RPCs and `UriList` type. 3.53 | Added `setIsolatePauseMode` RPC. +3.54 | Added `CpuSamplesEvent`, updated `cpuSamples` property on `Event` to have type `CpuSamplesEvent`. + [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss diff --git a/runtime/vm/service_event.cc b/runtime/vm/service_event.cc index 85740cf5d2a..6ef5b54b056 100644 --- a/runtime/vm/service_event.cc +++ b/runtime/vm/service_event.cc @@ -307,7 +307,8 @@ void ServiceEvent::PrintJSON(JSONStream* js) const { if (kind() == kCpuSamples) { JSONObject cpu_profile(&jsobj, "cpuSamples"); - cpu_profile_->PrintProfileJSON(&cpu_profile, false); + cpu_profile_->PrintProfileJSON(&cpu_profile, /*include_code_samples=*/false, + /*is_event=*/true); } }