Add a new getIsolatePauseEvent method in the VM service.

In Flutter hot reload, the flutter_tools are calling the `getIsolate`
to read the pause state of the isolate, which returns a full list of
libraries loaded in the isolate. The size of the return object grow
linearly to the number of Dart files, which can take ~500ms to transfer
for a large app.

This change adds a new method to only read what is needed.

TEST=pkg/vm_service/test/get_isolate_pause_event_rpc_test.dart

Change-Id: If19910cb3ff5d5057932551ac738afd3c3136fac
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/305362
Reviewed-by: Ben Konyi <bkonyi@google.com>
Auto-Submit: Chingjun Lau <chingjun@google.com>
Commit-Queue: Derek Xu <derekx@google.com>
This commit is contained in:
Chingjun Lau 2023-05-25 21:55:21 +00:00 committed by Commit Queue
parent c1243af736
commit 9304a8dfd1
11 changed files with 150 additions and 48 deletions

View file

@ -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

View file

@ -1 +1 @@
version=4.7
version=4.8

View file

@ -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<String, List<String>> _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<IsolateGroup> 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<Event> 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<IsolateGroup> getIsolateGroup(String isolateGroupId) =>
_call('getIsolateGroup', {'isolateGroupId': isolateGroupId});
@override
Future<Event> getIsolatePauseEvent(String isolateId) =>
_call('getIsolatePauseEvent', {'isolateId': isolateId});
@override
Future<MemoryUsage> getMemoryUsage(String isolateId) =>
_call('getMemoryUsage', {'isolateId': isolateId});

View file

@ -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 = <VMTest>[
(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, '<collected>');
}
},
// 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',
);

View file

@ -12,7 +12,7 @@ var tests = <VMTest>[
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);
},

View file

@ -12,7 +12,7 @@ var tests = <VMTest>[
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));
},

View file

@ -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) {

View file

@ -1268,6 +1268,8 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
// 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)

View file

@ -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,

View file

@ -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;

View file

@ -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