[ 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:
Ben Konyi 2021-11-18 19:45:26 +00:00 committed by commit-bot@chromium.org
parent 1427f15f67
commit 46f9ae9568
26 changed files with 432 additions and 55 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1 +1 @@
version=3.53
version=3.54

View file

@ -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'];
}

View file

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

View file

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

View file

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

View file

@ -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', {});

View file

@ -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);
},

View file

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

View file

@ -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));
},

View file

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

View file

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

View file

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

View file

@ -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();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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