Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 89de1e8

Browse files
bkonyicommit-bot@chromium.org
authored andcommitted
[ VM / Service ] Add optional 'limit' parameter to getStack RPC
Allows for clients to request the top N frames of the current stack. Fixes dart-lang/sdk#43826 Change-Id: Ia8d38e6317373024382a5e857faff73c89a0d2c0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/169980 Reviewed-by: Ryan Macnak <rmacnak@google.com> Reviewed-by: Gary Roumanis <grouma@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com>
1 parent 7784843 commit 89de1e8

File tree

14 files changed

+314
-22
lines changed

14 files changed

+314
-22
lines changed

pkg/vm_service/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 5.5.0
4+
- Update to version `3.42.0` of the spec.
5+
- Added optional `limit` parameter to `getStack` RPC.
6+
37
## 5.4.0
48
- Update to version `3.41.0` of the spec.
59
- Added `PortList` class.

pkg/vm_service/example/vm_service_assert.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,7 @@ vms.Stack assertStack(vms.Stack obj) {
11051105
assertString(obj.type);
11061106
assertListOfFrame(obj.frames);
11071107
assertListOfMessage(obj.messages);
1108+
assertBool(obj.truncated);
11081109
return obj;
11091110
}
11101111

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=3.41
1+
version=3.42

pkg/vm_service/lib/src/vm_service.dart

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export 'snapshot_graph.dart'
2828
HeapSnapshotObjectNoData,
2929
HeapSnapshotObjectNullData;
3030

31-
const String vmServiceVersion = '3.41.0';
31+
const String vmServiceVersion = '3.42.0';
3232

3333
/// @optional
3434
const String optional = 'optional';
@@ -733,14 +733,20 @@ abstract class VmServiceInterface {
733733
/// The `getStack` RPC is used to retrieve the current execution stack and
734734
/// message queue for an isolate. The isolate does not need to be paused.
735735
///
736+
/// If `limit` is provided, up to `limit` frames from the top of the stack
737+
/// will be returned. If the stack depth is smaller than `limit` the entire
738+
/// stack is returned. Note: this limit also applies to the
739+
/// `asyncCausalFrames` and `awaiterFrames` stack representations in the
740+
/// `Stack` response.
741+
///
736742
/// If `isolateId` refers to an isolate which has exited, then the `Collected`
737743
/// [Sentinel] is returned.
738744
///
739745
/// See [Stack].
740746
///
741747
/// This method will throw a [SentinelException] in the case a [Sentinel] is
742748
/// returned.
743-
Future<Stack> getStack(String isolateId);
749+
Future<Stack> getStack(String isolateId, {int limit});
744750

745751
/// The `getSupportedProtocols` RPC is used to determine which protocols are
746752
/// supported by the current server.
@@ -1344,6 +1350,7 @@ class VmServerConnection {
13441350
case 'getStack':
13451351
response = await _serviceImplementation.getStack(
13461352
params['isolateId'],
1353+
limit: params['limit'],
13471354
);
13481355
break;
13491356
case 'getSupportedProtocols':
@@ -1798,8 +1805,10 @@ class VmService implements VmServiceInterface {
17981805
_call('getProcessMemoryUsage');
17991806

18001807
@override
1801-
Future<Stack> getStack(String isolateId) =>
1802-
_call('getStack', {'isolateId': isolateId});
1808+
Future<Stack> getStack(String isolateId, {int limit}) => _call('getStack', {
1809+
'isolateId': isolateId,
1810+
if (limit != null) 'limit': limit,
1811+
});
18031812

18041813
@override
18051814
Future<ProtocolList> getSupportedProtocols() =>
@@ -6664,23 +6673,40 @@ class SourceReportRange {
66646673
'compiled: ${compiled}]';
66656674
}
66666675

6676+
/// The `Stack` class represents the various components of a Dart stack trace
6677+
/// for a given isolate.
6678+
///
6679+
/// See [getStack].
66676680
class Stack extends Response {
66686681
static Stack parse(Map<String, dynamic> json) =>
66696682
json == null ? null : Stack._fromJson(json);
66706683

6684+
/// A list of frames that make up the synchronous stack, rooted at the message
6685+
/// loop (i.e., the frames since the last asynchronous gap or the isolate's
6686+
/// entrypoint).
66716687
List<Frame> frames;
66726688

6689+
/// A list of frames representing the asynchronous path. Comparable to
6690+
/// `awaiterFrames`, if provided, although some frames may be different.
66736691
@optional
66746692
List<Frame> asyncCausalFrames;
66756693

6694+
/// A list of frames representing the asynchronous path. Comparable to
6695+
/// `asyncCausalFrames`, if provided, although some frames may be different.
66766696
@optional
66776697
List<Frame> awaiterFrames;
66786698

6699+
/// A list of messages in the isolate's message queue.
66796700
List<Message> messages;
66806701

6702+
/// Specifies whether or not this stack is complete or has been artificially
6703+
/// truncated.
6704+
bool truncated;
6705+
66816706
Stack({
66826707
@required this.frames,
66836708
@required this.messages,
6709+
@required this.truncated,
66846710
this.asyncCausalFrames,
66856711
this.awaiterFrames,
66866712
});
@@ -6698,6 +6724,7 @@ class Stack extends Response {
66986724
createServiceObject(json['awaiterFrames'], const ['Frame']));
66996725
messages = List<Message>.from(
67006726
createServiceObject(json['messages'], const ['Message']) ?? []);
6727+
truncated = json['truncated'];
67016728
}
67026729

67036730
@override
@@ -6707,6 +6734,7 @@ class Stack extends Response {
67076734
json.addAll({
67086735
'frames': frames.map((f) => f.toJson()).toList(),
67096736
'messages': messages.map((f) => f.toJson()).toList(),
6737+
'truncated': truncated,
67106738
});
67116739
_setIfNotNull(json, 'asyncCausalFrames',
67126740
asyncCausalFrames?.map((f) => f?.toJson())?.toList());
@@ -6715,8 +6743,9 @@ class Stack extends Response {
67156743
return json;
67166744
}
67176745

6718-
String toString() =>
6719-
'[Stack type: ${type}, frames: ${frames}, messages: ${messages}]';
6746+
String toString() => '[Stack ' //
6747+
'type: ${type}, frames: ${frames}, messages: ${messages}, ' //
6748+
'truncated: ${truncated}]';
67206749
}
67216750

67226751
/// The `Success` type is used to indicate that an operation completed

pkg/vm_service/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: vm_service
22
description: >-
33
A library to communicate with a service implementing the Dart VM
44
service protocol.
5-
version: 5.4.0
5+
version: 5.5.0
66

77
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
88

runtime/observatory/lib/src/service/object.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,8 +1917,10 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
19171917
return invokeRpc('setExceptionPauseMode', {'mode': mode});
19181918
}
19191919

1920-
Future<ServiceMap> getStack() {
1921-
return invokeRpc('getStack', {}).then((response) => response as ServiceMap);
1920+
Future<ServiceMap> getStack({int? limit}) {
1921+
return invokeRpc('getStack', {
1922+
if (limit != null) 'limit': limit,
1923+
}).then((response) => response as ServiceMap);
19221924
}
19231925

19241926
Future<ObjectStore> getObjectStore() {
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:developer';
7+
8+
import 'package:observatory/models.dart' as M;
9+
import 'package:observatory/service_io.dart';
10+
import 'package:test/test.dart';
11+
import 'service_test_common.dart';
12+
import 'test_helper.dart';
13+
14+
bar(int depth) {
15+
if (depth == 21) {
16+
debugger();
17+
return;
18+
}
19+
foo(depth + 1);
20+
}
21+
22+
foo(int depth) {
23+
bar(depth + 1);
24+
}
25+
26+
testMain() {
27+
foo(0);
28+
}
29+
30+
verifyStack(List frames, int numFrames) {
31+
for (int i = 0; i < frames.length && i < numFrames; ++i) {
32+
final frame = frames[i];
33+
if (i < 22) {
34+
expect(frame.function!.qualifiedName, (i % 2) == 0 ? 'bar' : 'foo');
35+
} else if (i == 22) {
36+
expect(frame.function!.qualifiedName, 'testMain');
37+
break;
38+
}
39+
}
40+
}
41+
42+
var tests = <IsolateTest>[
43+
(Isolate isolate) async {
44+
await hasStoppedAtBreakpoint(isolate);
45+
// Sanity check.
46+
expect(isolate.pauseEvent is M.PauseBreakpointEvent, isTrue);
47+
},
48+
49+
// Get stack
50+
(Isolate isolate) async {
51+
var stack = await isolate.getStack();
52+
// Sanity check.
53+
var frames = stack['frames'];
54+
var asyncFrames = stack['asyncCausalFrames'];
55+
var awaiterFrames = stack['awaiterFrames'];
56+
expect(frames.length, greaterThanOrEqualTo(20));
57+
expect(asyncFrames.length, greaterThan(frames.length));
58+
expect(awaiterFrames.length, greaterThan(frames.length));
59+
expect(stack['truncated'], false);
60+
61+
verifyStack(frames, frames.length);
62+
63+
final fullStackLength = frames.length;
64+
65+
// Try a limit > actual stack depth and expect to get the full stack with
66+
// truncated async stacks.
67+
stack = await isolate.getStack(limit: fullStackLength + 1);
68+
frames = stack['frames'];
69+
asyncFrames = stack['asyncCausalFrames'];
70+
awaiterFrames = stack['awaiterFrames'];
71+
72+
expect(frames.length, fullStackLength);
73+
expect(asyncFrames.length, fullStackLength + 1);
74+
expect(asyncFrames.length, fullStackLength + 1);
75+
expect(stack['truncated'], true);
76+
verifyStack(frames, fullStackLength);
77+
78+
// Try a limit < actual stack depth and expect to get a stack of depth
79+
// 'limit'.
80+
stack = await isolate.getStack(limit: 10);
81+
frames = stack['frames'];
82+
asyncFrames = stack['asyncCausalFrames'];
83+
awaiterFrames = stack['awaiterFrames'];
84+
85+
expect(frames.length, 10);
86+
expect(asyncFrames.length, 10);
87+
expect(awaiterFrames.length, 10);
88+
expect(stack['truncated'], true);
89+
verifyStack(frames, 10);
90+
},
91+
// Invalid limit
92+
(Isolate isolate) async {
93+
try {
94+
await isolate.getStack(limit: -1);
95+
fail('Invalid parameter of -1 successful');
96+
} on ServerRpcException {
97+
// Expected.
98+
}
99+
}
100+
];
101+
102+
main(args) => runIsolateTests(args, tests, testeeConcurrent: testMain);

runtime/observatory/tests/service/get_version_rpc_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ var tests = <VMTest>[
1212
final result = await vm.invokeRpcNoUpgrade('getVersion', {});
1313
expect(result['type'], 'Version');
1414
expect(result['major'], 3);
15-
expect(result['minor'], 41);
15+
expect(result['minor'], 42);
1616
expect(result['_privateMajor'], 0);
1717
expect(result['_privateMinor'], 0);
1818
},

runtime/observatory_2/lib/src/service/object.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,8 +1927,10 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
19271927
return invokeRpc('setExceptionPauseMode', {'mode': mode});
19281928
}
19291929

1930-
Future<ServiceMap> getStack() {
1931-
return invokeRpc('getStack', {}).then((response) => response as ServiceMap);
1930+
Future<ServiceMap> getStack({int limit}) {
1931+
return invokeRpc('getStack', {
1932+
if (limit != null) 'limit': limit,
1933+
}).then((response) => response as ServiceMap);
19321934
}
19331935

19341936
Future<ObjectStore> getObjectStore() {
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:developer';
7+
8+
import 'package:observatory_2/models.dart' as M;
9+
import 'package:observatory_2/service_io.dart';
10+
import 'package:test/test.dart';
11+
import 'service_test_common.dart';
12+
import 'test_helper.dart';
13+
14+
bar(int depth) {
15+
if (depth == 21) {
16+
debugger();
17+
return;
18+
}
19+
foo(depth + 1);
20+
}
21+
22+
foo(int depth) {
23+
bar(depth + 1);
24+
}
25+
26+
testMain() {
27+
foo(0);
28+
}
29+
30+
verifyStack(List frames, int numFrames) {
31+
for (int i = 0; i < frames.length && i < numFrames; ++i) {
32+
final frame = frames[i];
33+
if (i < 22) {
34+
expect(frame.function.qualifiedName, (i % 2) == 0 ? 'bar' : 'foo');
35+
} else if (i == 22) {
36+
expect(frame.function.qualifiedName, 'testMain');
37+
break;
38+
}
39+
}
40+
}
41+
42+
var tests = <IsolateTest>[
43+
(Isolate isolate) async {
44+
await hasStoppedAtBreakpoint(isolate);
45+
// Sanity check.
46+
expect(isolate.pauseEvent is M.PauseBreakpointEvent, isTrue);
47+
},
48+
49+
// Get stack
50+
(Isolate isolate) async {
51+
var stack = await isolate.getStack();
52+
// Sanity check.
53+
var frames = stack['frames'];
54+
var asyncFrames = stack['asyncCausalFrames'];
55+
var awaiterFrames = stack['awaiterFrames'];
56+
expect(frames.length, greaterThanOrEqualTo(20));
57+
expect(asyncFrames.length, greaterThan(frames.length));
58+
expect(awaiterFrames.length, greaterThan(frames.length));
59+
expect(stack['truncated'], false);
60+
61+
verifyStack(frames, frames.length);
62+
63+
final fullStackLength = frames.length;
64+
65+
// Try a limit > actual stack depth and expect to get the full stack with
66+
// truncated async stacks.
67+
stack = await isolate.getStack(limit: fullStackLength + 1);
68+
frames = stack['frames'];
69+
asyncFrames = stack['asyncCausalFrames'];
70+
awaiterFrames = stack['awaiterFrames'];
71+
72+
expect(frames.length, fullStackLength);
73+
expect(asyncFrames.length, fullStackLength + 1);
74+
expect(asyncFrames.length, fullStackLength + 1);
75+
expect(stack['truncated'], true);
76+
verifyStack(frames, fullStackLength);
77+
78+
// Try a limit < actual stack depth and expect to get a stack of depth
79+
// 'limit'.
80+
stack = await isolate.getStack(limit: 10);
81+
frames = stack['frames'];
82+
asyncFrames = stack['asyncCausalFrames'];
83+
awaiterFrames = stack['awaiterFrames'];
84+
85+
expect(frames.length, 10);
86+
expect(asyncFrames.length, 10);
87+
expect(awaiterFrames.length, 10);
88+
expect(stack['truncated'], true);
89+
verifyStack(frames, 10);
90+
},
91+
// Invalid limit
92+
(Isolate isolate) async {
93+
try {
94+
await isolate.getStack(limit: -1);
95+
fail('Invalid parameter of -1 successful');
96+
} on ServerRpcException {
97+
// Expected.
98+
}
99+
}
100+
];
101+
102+
main(args) => runIsolateTests(args, tests, testeeConcurrent: testMain);

0 commit comments

Comments
 (0)