diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore index b3304754f0b..9571fd06f44 100644 --- a/pkg/vm_service/java/.gitignore +++ b/pkg/vm_service/java/.gitignore @@ -19,6 +19,7 @@ src/org/dartlang/vm/service/consumer/GetInstancesConsumer.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/GetIsolatePauseEventConsumer.java src/org/dartlang/vm/service/consumer/GetMemoryUsageConsumer.java src/org/dartlang/vm/service/consumer/GetObjectConsumer.java src/org/dartlang/vm/service/consumer/GetPerfettoCpuSamplesConsumer.java diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties index 1e48711f919..ecaa1cbf4f5 100644 --- a/pkg/vm_service/java/version.properties +++ b/pkg/vm_service/java/version.properties @@ -1 +1 @@ -version=4.7 +version=4.8 diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart index 7ca50bc271e..7c5c9f9507c 100644 --- a/pkg/vm_service/lib/src/vm_service.dart +++ b/pkg/vm_service/lib/src/vm_service.dart @@ -28,7 +28,7 @@ export 'snapshot_graph.dart' HeapSnapshotObjectNoData, HeapSnapshotObjectNullData; -const String vmServiceVersion = '4.7.0'; +const String vmServiceVersion = '4.8.0'; /// @optional const String optional = 'optional'; @@ -216,6 +216,7 @@ Map> _methodReturnTypes = { 'getInstancesAsList': const ['InstanceRef'], 'getIsolate': const ['Isolate'], 'getIsolateGroup': const ['IsolateGroup'], + 'getIsolatePauseEvent': const ['Event'], 'getMemoryUsage': const ['MemoryUsage'], 'getIsolateGroupMemoryUsage': const ['MemoryUsage'], 'getScripts': const ['ScriptList'], @@ -719,6 +720,18 @@ abstract class VmServiceInterface { /// returned. Future getIsolateGroup(String isolateGroupId); + /// The `getIsolatePauseEvent` RPC is used to lookup an isolate's pause event + /// by its `id`. + /// + /// If `isolateId` refers to an isolate which has exited, then the `Collected` + /// [Sentinel] is returned. + /// + /// See [Isolate]. + /// + /// This method will throw a [SentinelException] in the case a [Sentinel] is + /// returned. + Future getIsolatePauseEvent(String isolateId); + /// The `getMemoryUsage` RPC is used to lookup an isolate's memory usage /// statistics by its `id`. /// @@ -1609,6 +1622,11 @@ class VmServerConnection { params!['isolateGroupId'], ); break; + case 'getIsolatePauseEvent': + response = await _serviceImplementation.getIsolatePauseEvent( + params!['isolateId'], + ); + break; case 'getMemoryUsage': response = await _serviceImplementation.getMemoryUsage( params!['isolateId'], @@ -2181,6 +2199,10 @@ class VmService implements VmServiceInterface { Future getIsolateGroup(String isolateGroupId) => _call('getIsolateGroup', {'isolateGroupId': isolateGroupId}); + @override + Future getIsolatePauseEvent(String isolateId) => + _call('getIsolatePauseEvent', {'isolateId': isolateId}); + @override Future getMemoryUsage(String isolateId) => _call('getMemoryUsage', {'isolateId': isolateId}); diff --git a/pkg/vm_service/test/get_isolate_pause_event_rpc_test.dart b/pkg/vm_service/test/get_isolate_pause_event_rpc_test.dart new file mode 100644 index 00000000000..471a2174a78 --- /dev/null +++ b/pkg/vm_service/test/get_isolate_pause_event_rpc_test.dart @@ -0,0 +1,40 @@ +// Copyright (c) 2023, 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.getIsolatePauseEvent(vm.isolates!.first.id!); + expect(result.type, 'Event'); + expect(result.kind, isNotNull); + }, + // Plausible isolate id, not found. + (VmService service) async { + try { + await service.getIsolatePauseEvent('isolates/9999999999'); + fail('successfully got isolate with bad ID'); + } on SentinelException catch (e) { + expect(e.sentinel.kind, 'Collected'); + expect(e.sentinel.valueAsString, ''); + } + }, + // Verify that the returned event is the same as returned from getIsolate() + (VmService service) async { + final vm = await service.getVM(); + final result = await service.getIsolatePauseEvent(vm.isolates!.first.id!); + final isolate = await service.getIsolate(vm.isolates!.first.id!); + expect(result.toJson(), isolate.pauseEvent?.toJson()); + }, +]; + +main(args) async => runVMTests( + args, + tests, + 'get_isolate_pause_event_rpc_test.dart', + ); diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart index 01e43f49698..38125e30de9 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'], 4); - expect(result['minor'], 7); + expect(result['minor'], 8); expect(result['_privateMajor'], 0); expect(result['_privateMinor'], 0); }, 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 e983f9546e6..a0c99a98667 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(4)); - expect(result['minor'], equals(7)); + expect(result['minor'], equals(8)); expect(result['_privateMajor'], equals(0)); expect(result['_privateMinor'], equals(0)); }, diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc index d94860d8d2a..29902db65e1 100644 --- a/runtime/vm/isolate.cc +++ b/runtime/vm/isolate.cc @@ -2836,6 +2836,51 @@ static const char* ExceptionPauseInfoToServiceEnum(Dart_ExceptionPauseInfo pi) { } } +static ServiceEvent IsolatePauseEvent(Isolate* isolate) { + if (!isolate->is_runnable()) { + // Isolate is not yet runnable. + ASSERT((isolate->debugger() == nullptr) || + (isolate->debugger()->PauseEvent() == nullptr)); + return ServiceEvent(isolate, ServiceEvent::kNone); + } else if (isolate->message_handler()->should_pause_on_start()) { + if (isolate->message_handler()->is_paused_on_start()) { + ASSERT((isolate->debugger() == nullptr) || + (isolate->debugger()->PauseEvent() == nullptr)); + return ServiceEvent(isolate, ServiceEvent::kPauseStart); + } else { + // Isolate is runnable but not paused on start. + // Some service clients get confused if they see: + // NotRunnable -> Runnable -> PausedAtStart + // Treat Runnable+ShouldPauseOnStart as NotRunnable so they see: + // NonRunnable -> PausedAtStart + // The should_pause_on_start flag is set to false after resume. + ASSERT((isolate->debugger() == nullptr) || + (isolate->debugger()->PauseEvent() == nullptr)); + return ServiceEvent(isolate, ServiceEvent::kNone); + } + } else if (isolate->message_handler()->is_paused_on_exit() && + ((isolate->debugger() == nullptr) || + (isolate->debugger()->PauseEvent() == nullptr))) { + return ServiceEvent(isolate, ServiceEvent::kPauseExit); + } else if ((isolate->debugger() != nullptr) && + (isolate->debugger()->PauseEvent() != nullptr) && + !isolate->ResumeRequest()) { + return *(isolate->debugger()->PauseEvent()); + } else { + ServiceEvent pause_event(isolate, ServiceEvent::kResume); + + if (isolate->debugger() != nullptr) { + // TODO(turnidge): Don't compute a full stack trace. + DebuggerStackTrace* stack = isolate->debugger()->StackTrace(); + if (stack->Length() > 0) { + pause_event.set_top_frame(stack->FrameAt(0)); + } + } + + return pause_event; + } +} + void Isolate::PrintJSON(JSONStream* stream, bool ref) { JSONObject jsobj(stream); jsobj.AddProperty("type", (ref ? "@Isolate" : "Isolate")); @@ -2887,47 +2932,8 @@ void Isolate::PrintJSON(JSONStream* stream, bool ref) { jsobj.AddProperty("_isReloading", group()->IsReloading()); #endif // !defined(DART_PRECOMPILED_RUNTIME) - if (!is_runnable()) { - // Isolate is not yet runnable. - ASSERT((debugger() == nullptr) || (debugger()->PauseEvent() == nullptr)); - ServiceEvent pause_event(this, ServiceEvent::kNone); - jsobj.AddProperty("pauseEvent", &pause_event); - } else if (message_handler()->should_pause_on_start()) { - if (message_handler()->is_paused_on_start()) { - ASSERT((debugger() == nullptr) || (debugger()->PauseEvent() == nullptr)); - ServiceEvent pause_event(this, ServiceEvent::kPauseStart); - jsobj.AddProperty("pauseEvent", &pause_event); - } else { - // Isolate is runnable but not paused on start. - // Some service clients get confused if they see: - // NotRunnable -> Runnable -> PausedAtStart - // Treat Runnable+ShouldPauseOnStart as NotRunnable so they see: - // NonRunnable -> PausedAtStart - // The should_pause_on_start flag is set to false after resume. - ASSERT((debugger() == nullptr) || (debugger()->PauseEvent() == nullptr)); - ServiceEvent pause_event(this, ServiceEvent::kNone); - jsobj.AddProperty("pauseEvent", &pause_event); - } - } else if (message_handler()->is_paused_on_exit() && - ((debugger() == nullptr) || - (debugger()->PauseEvent() == nullptr))) { - ServiceEvent pause_event(this, ServiceEvent::kPauseExit); - jsobj.AddProperty("pauseEvent", &pause_event); - } else if ((debugger() != nullptr) && (debugger()->PauseEvent() != nullptr) && - !ResumeRequest()) { - jsobj.AddProperty("pauseEvent", debugger()->PauseEvent()); - } else { - ServiceEvent pause_event(this, ServiceEvent::kResume); - - if (debugger() != nullptr) { - // TODO(turnidge): Don't compute a full stack trace. - DebuggerStackTrace* stack = debugger()->StackTrace(); - if (stack->Length() > 0) { - pause_event.set_top_frame(stack->FrameAt(0)); - } - } - jsobj.AddProperty("pauseEvent", &pause_event); - } + ServiceEvent pause_event = IsolatePauseEvent(this); + jsobj.AddProperty("pauseEvent", &pause_event); const Library& lib = Library::Handle(group()->object_store()->root_library()); if (!lib.IsNull()) { @@ -3003,6 +3009,10 @@ void Isolate::PrintMemoryUsageJSON(JSONStream* stream) { group()->heap()->PrintMemoryUsageJSON(stream); } +void Isolate::PrintPauseEventJSON(JSONStream* stream) { + IsolatePauseEvent(this).PrintJSON(stream); +} + #endif void Isolate::set_tag_table(const GrowableObjectArray& value) { diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h index 34479084737..84a49def1da 100644 --- a/runtime/vm/isolate.h +++ b/runtime/vm/isolate.h @@ -1268,6 +1268,8 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry { // Creates an object with the total heap memory usage statistics for this // isolate. void PrintMemoryUsageJSON(JSONStream* stream); + + void PrintPauseEventJSON(JSONStream* stream); #endif #if !defined(PRODUCT) diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index 86b93ed52d8..d53670d52ca 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -1623,6 +1623,15 @@ static void GetIsolateGroupMemoryUsage(Thread* thread, JSONStream* js) { }); } +static const MethodParameter* const get_isolate_pause_event_params[] = { + ISOLATE_PARAMETER, + nullptr, +}; + +static void GetIsolatePauseEvent(Thread* thread, JSONStream* js) { + thread->isolate()->PrintPauseEventJSON(js); +} + static const MethodParameter* const get_scripts_params[] = { RUNNABLE_ISOLATE_PARAMETER, nullptr, @@ -5821,6 +5830,8 @@ static const ServiceMethodDescriptor service_methods_[] = { get_isolate_metric_params }, { "_getIsolateMetricList", GetIsolateMetricList, get_isolate_metric_list_params }, + { "getIsolatePauseEvent", GetIsolatePauseEvent, + get_isolate_pause_event_params }, { "getObject", GetObject, get_object_params }, { "_getObjectStore", GetObjectStore, diff --git a/runtime/vm/service.h b/runtime/vm/service.h index d8126ad48c8..53fc44a1922 100644 --- a/runtime/vm/service.h +++ b/runtime/vm/service.h @@ -17,7 +17,7 @@ namespace dart { #define SERVICE_PROTOCOL_MAJOR_VERSION 4 -#define SERVICE_PROTOCOL_MINOR_VERSION 7 +#define SERVICE_PROTOCOL_MINOR_VERSION 8 class Array; class EmbedderServiceHandler; diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md index 7c7b61c42ea..3deeaf1a635 100644 --- a/runtime/vm/service/service.md +++ b/runtime/vm/service/service.md @@ -1,8 +1,8 @@ -# Dart VM Service Protocol 4.7 +# Dart VM Service Protocol 4.8 > Please post feedback to the [observatory-discuss group][discuss-list] -This document describes of _version 4.7_ of the Dart VM Service Protocol. This +This document describes of _version 4.8_ 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. @@ -49,6 +49,7 @@ The Service Protocol uses [JSON-RPC 2.0][]. - [getIsolate](#getisolate) - [getIsolateGroup](#getisolategroup) - [getMemoryUsage](#getmemoryusage) + - [getIsolatePauseEvent](#getisolatePauseEvent) - [getObject](#getobject) - [getPerfettoCpuSamples](#getperfettocpusamples) - [getPerfettoVMTimeline](#getperfettovmtimeline) @@ -953,6 +954,20 @@ _IsolateGroup_ _id_ is an opaque identifier that can be fetched from an See [IsolateGroup](#isolategroup), [VM](#vm). +### getIsolatePauseEvent + +``` +Event|Sentinel getIsolatePauseEvent(string isolateId) +``` + +The _getIsolatePauseEvent_ RPC is used to lookup an isolate's pause event by its +_id_. + +If _isolateId_ refers to an isolate which has exited, then the +_Collected_ [Sentinel](#sentinel) is returned. + +See [Isolate](#isolate). + ### getMemoryUsage ``` @@ -4676,5 +4691,6 @@ version | comments 4.5 | Added `getPerfettoVMTimeline` RPC. 4.6 | Added `getPerfettoCpuSamples` RPC. Added a deprecation notice to `InstanceKind.TypeRef`. 4.7 | Added a deprecation notice to `Stack.awaiterFrames` field. Added a deprecation notice to `FrameKind.AsyncActivation`. +4.8 | Added `getIsolatePauseEvent` RPC. [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss