mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[ VM / Service ] Stream light-weight version of CpuSamples
for CPU
profiler events. `Event.cpuSamples` is now a `CpuSamplesEvent` rather than a `CpuSamples` object, where `CpuSamplesEvent` returns `(@Object|NativeFunction)[]` rather than `(@Func|NativeFunction)[]`, resulting in a smaller JSON payload. TEST=get_object_rpc_test.dart,get_cached_cpu_samples_test.dart Change-Id: I1ad5e3df8840b8c41735d10c6c8669f6503e54a8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/219284 Commit-Queue: Ben Konyi <bkonyi@google.com> Reviewed-by: Siva Annamalai <asiva@google.com>
This commit is contained in:
parent
1427f15f67
commit
46f9ae9568
|
@ -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.
|
||||
|
|
|
@ -52,21 +52,31 @@ class CpuSamplesRepository extends RingBuffer<CpuSample> {
|
|||
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<CpuSample> {
|
|||
// 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<CpuSample> {
|
|||
|
||||
// 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<CpuSample> {
|
|||
return evicted;
|
||||
}
|
||||
|
||||
Future<void> 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<String, dynamic> toJson() {
|
||||
return {
|
||||
'type': 'CachedCpuSamples',
|
||||
|
|
|
@ -110,13 +110,14 @@ class _RunningIsolate {
|
|||
/// Should always be called after an isolate is resumed.
|
||||
void clearResumeApprovals() => _resumeApprovalsByName.clear();
|
||||
|
||||
Map<String, dynamic> getCachedCpuSamples(String userTag) {
|
||||
Future<Map<String, dynamic>> 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<String, dynamic> getCachedCpuSamples(json_rpc.Parameters parameters) {
|
||||
Future<Map<String, dynamic>> 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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<void>();
|
||||
int i = 0;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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!);
|
||||
|
|
1
pkg/vm_service/java/.gitignore
vendored
1
pkg/vm_service/java/.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
version=3.53
|
||||
version=3.54
|
||||
|
|
|
@ -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<String, Function> _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<String, dynamic>? 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<dynamic>? functions;
|
||||
|
||||
/// A list of samples collected in the range `[timeOriginMicros,
|
||||
/// timeOriginMicros + timeExtentMicros]`
|
||||
List<CpuSample>? 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<String, dynamic> 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<dynamic>.from(
|
||||
createServiceObject(json['functions'], const ['dynamic']) as List? ??
|
||||
[]);
|
||||
samples = List<CpuSample>.from(
|
||||
createServiceObject(json['samples'], const ['CpuSample']) as List? ??
|
||||
[]);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
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<String, dynamic>? 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'];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -935,6 +935,10 @@ dynamic assertDynamic(dynamic obj) {
|
|||
return obj;
|
||||
}
|
||||
|
||||
List<dynamic> assertListOfDynamic(List<dynamic> list) {
|
||||
return list;
|
||||
}
|
||||
|
||||
List<int> assertListOfInt(List<int> list) {
|
||||
for (int elem in list) {
|
||||
assertInt(elem);
|
||||
|
|
|
@ -14,8 +14,12 @@ import 'test_helper.dart';
|
|||
class _DummyClass {
|
||||
static var dummyVar = 11;
|
||||
final List<String> dummyList = new List<String>.filled(20, '');
|
||||
static var dummyVarWithInit = foo();
|
||||
late String dummyLateVarWithInit = 'bar';
|
||||
late String dummyLateVar;
|
||||
void dummyFunction(int a, [bool b = false]) {}
|
||||
void dummyGenericFunction<K, V>(K a, {required V param}) {}
|
||||
static List foo() => List<String>.filled(20, '');
|
||||
}
|
||||
|
||||
class _DummySubClass extends _DummyClass {}
|
||||
|
@ -982,6 +986,83 @@ var tests = <IsolateTest>[
|
|||
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', {});
|
||||
|
|
|
@ -12,7 +12,7 @@ var tests = <VMTest>[
|
|||
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);
|
||||
},
|
||||
|
|
|
@ -14,8 +14,10 @@ import 'test_helper.dart';
|
|||
class _DummyClass {
|
||||
static var dummyVar = 11;
|
||||
final List<String> dummyList = new List<String>.filled(20, null);
|
||||
static var dummyVarWithInit = foo();
|
||||
void dummyFunction(int a, [bool b = false]) {}
|
||||
void dummyGenericFunction<K, V>(K a, {V param}) {}
|
||||
static List foo() => List<String>.filled(20, '');
|
||||
}
|
||||
|
||||
class _DummySubClass extends _DummyClass {}
|
||||
|
@ -1014,6 +1016,34 @@ var tests = <IsolateTest>[
|
|||
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.
|
||||
|
|
|
@ -12,7 +12,7 @@ var tests = <VMTest>[
|
|||
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));
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue