Reland "[ package:dds ] Add support for caching CPU samples based on UserTag"

This reverts commit ada4278fd5.

TEST=pkg/dds/test/get_cached_cpu_samples_test.dart

Change-Id: I771410c8647fc1eb721c5aeb325c7a595430435c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209120
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
This commit is contained in:
Ben Konyi 2021-08-25 00:04:04 +00:00 committed by commit-bot@chromium.org
parent 3aab52f498
commit c7fe9ada2f
28 changed files with 1056 additions and 254 deletions

View file

@ -2,6 +2,9 @@
- Fix another possibility of `LateInitializationError` being thrown when trying to
cleanup after an error during initialization.
# 2.1.0
- Added getAvailableCachedCpuSamples and getCachedCpuSamples.
# 2.0.2
- Fix possibility of `LateInitializationError` being thrown when trying to
cleanup after an error during initialization.

View file

@ -1,6 +1,6 @@
# Dart Development Service Protocol 1.2
# Dart Development Service Protocol 1.3
This document describes _version 1.2_ of the Dart Development Service Protocol.
This document describes _version 1.3_ of the Dart Development Service Protocol.
This protocol is an extension of the Dart VM Service Protocol and implements it
in it's entirety. For details on the VM Service Protocol, see the [Dart VM Service Protocol Specification][service-protocol].
@ -67,6 +67,29 @@ event being sent to the subscribing client for each existing service extension.
The DDS Protocol supports all [public RPCs defined in the VM Service protocol][service-protocol-public-rpcs].
### getAvailableCachedCpuSamples
```
AvailableCachedCpuSamples getAvailableCachedCpuSamples();
```
The _getAvailableCachedCpuSamples_ RPC is used to determine which caches of CPU samples
are available. Caches are associated with individual _UserTag_ names and are specified
when DDS is started via the _cachedUserTags_ parameter.
See [AvailableCachedCpuSamples](#availablecachedcpusamples).
### getCachedCpuSamples
```
CachedCpuSamples getCachedCpuSamples(string isolateId, string userTag);
```
The _getCachedCpuSamples_ RPC is used to retrieve a cache of CPU samples collected
under a _UserTag_ with name _userTag_.
See [CachedCpuSamples](#cachedcpusamples).
### getClientName
```
@ -181,6 +204,37 @@ See [Success](#success).
The DDS Protocol supports all [public types defined in the VM Service protocol][service-protocol-public-types].
### AvailableCachedCpuSamples
```
class AvailableCachedCpuSamples extends Response {
// A list of UserTag names associated with CPU sample caches.
string[] cacheNames;
}
```
A collection of [UserTag] names associated with caches of CPU samples.
See [getAvailableCachedCpuSamples](#getavailablecachedcpusamples).
### CachedCpuSamples
```
class CachedCpuSamples extends CpuSamples {
// The name of the UserTag associated with this cache of samples.
string userTag;
// Provided if the CPU sample cache has filled and older samples have been
// dropped.
bool truncated [optional];
}
```
An extension of [CpuSamples](#cpu-samples) which represents a set of cached
samples, associated with a particular [UserTag] name.
See [getCachedCpuSamples](#getcachedcpusamples).
### ClientName
```
@ -220,10 +274,12 @@ version | comments
1.0 | Initial revision
1.1 | Added `getDartDevelopmentServiceVersion` RPC.
1.2 | Added `getStreamHistory` RPC.
1.3 | Added `getAvailableCachedCpuSamples` and `getCachedCpuSamples` RPCs.
[resume]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#resume
[success]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#success
[version]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#version
[cpu-samples]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#cpusamples
[service-protocol]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md
[service-protocol-rpcs-requests-and-responses]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#rpcs-requests-and-responses

View file

@ -42,6 +42,7 @@ abstract class DartDevelopmentService {
Uri? serviceUri,
bool enableAuthCodes = true,
bool ipv6 = false,
List<String> cachedUserTags = const [],
DevToolsConfiguration? devToolsConfiguration,
bool logRequests = false,
}) async {
@ -79,6 +80,7 @@ abstract class DartDevelopmentService {
remoteVmServiceUri,
serviceUri,
enableAuthCodes,
cachedUserTags,
ipv6,
devToolsConfiguration,
logRequests,
@ -136,9 +138,13 @@ abstract class DartDevelopmentService {
/// requests.
bool get isRunning;
/// The list of [UserTag]s used to determine which CPU samples are cached by
/// DDS.
List<String> get cachedUserTags;
/// The version of the DDS protocol supported by this [DartDevelopmentService]
/// instance.
static const String protocolVersion = '1.2';
static const String protocolVersion = '1.3';
}
class DartDevelopmentServiceException implements Exception {

View file

@ -206,6 +206,19 @@ class DartDevelopmentServiceClient {
return supportedProtocols;
});
_clientPeer.registerMethod(
'getAvailableCachedCpuSamples',
(_) => {
'type': 'AvailableCachedCpuSamples',
'cacheNames': dds.cachedUserTags,
},
);
_clientPeer.registerMethod(
'getCachedCpuSamples',
dds.isolateManager.getCachedCpuSamples,
);
// `evaluate` and `evaluateInFrame` actually consist of multiple RPC
// invocations, including a call to `compileExpression` which can be
// overridden by clients which provide their own implementation (e.g.,

View file

@ -0,0 +1,68 @@
// Copyright (c) 2021, 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 'dart:math';
class RingBuffer<T> {
RingBuffer(this._bufferSize) {
_buffer = List<T?>.filled(
_bufferSize,
null,
);
}
Iterable<T> call() sync* {
for (int i = _size - 1; i >= 0; --i) {
yield _buffer[(_count - i - 1) % _bufferSize]!;
}
}
/// Inserts a new element into the [RingBuffer].
///
/// Returns the element evicted as a result of adding the new element if the
/// buffer is as max capacity, null otherwise.
T? add(T e) {
if (_buffer.isEmpty) {
return null;
}
T? evicted;
final index = _count % _bufferSize;
if (index < _count) {
evicted = _buffer[index];
}
_buffer[index] = e;
_count++;
return evicted;
}
void resize(int size) {
assert(size >= 0);
if (size == _bufferSize) {
return;
}
final resized = List<T?>.filled(
size,
null,
);
int count = 0;
if (size > 0) {
for (final e in this()) {
resized[count++ % size] = e;
}
}
_count = count;
_bufferSize = size;
_buffer = resized;
}
bool get isTruncated => _count % bufferSize < _count;
int get bufferSize => _bufferSize;
int get _size => min(_count, _bufferSize);
int _bufferSize;
int _count = 0;
late List<T?> _buffer;
}

View file

@ -0,0 +1,201 @@
// Copyright (c) 2021, 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:dds/src/common/ring_buffer.dart';
import 'package:vm_service/vm_service.dart';
import 'dds_impl.dart';
/// Manages CPU sample caches for an individual [Isolate].
class CpuSamplesManager {
CpuSamplesManager(this.dds, this.isolateId) {
for (final userTag in dds.cachedUserTags) {
cpuSamplesCaches[userTag] = CpuSamplesRepository(userTag);
}
}
void handleUserTagEvent(Event event) {
assert(event.kind! == EventKind.kUserTagChanged);
_currentTag = event.updatedTag!;
final previousTag = event.previousTag!;
if (cpuSamplesCaches.containsKey(previousTag)) {
_lastCachedTag = previousTag;
}
}
void handleCpuSamplesEvent(Event event) {
assert(event.kind! == EventKind.kCpuSamples);
// There might be some samples left in the buffer for the previously set
// user tag. We'll check for them here and then close out the cache.
if (_lastCachedTag != null) {
cpuSamplesCaches[_lastCachedTag]!.cacheSamples(
event.cpuSamples!,
);
_lastCachedTag = null;
}
cpuSamplesCaches[_currentTag]?.cacheSamples(event.cpuSamples!);
}
final DartDevelopmentServiceImpl dds;
final String isolateId;
final cpuSamplesCaches = <String, CpuSamplesRepository>{};
String _currentTag = '';
String? _lastCachedTag;
}
class CpuSamplesRepository extends RingBuffer<CpuSample> {
// TODO(#46978): math to figure out proper buffer sizes.
CpuSamplesRepository(
this.tag, [
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!;
}
// Initialize upon seeing our first samples.
if (functions.isEmpty) {
samplePeriod = samples.samplePeriod!;
maxStackDepth = samples.maxStackDepth!;
pid = samples.pid!;
functions.addAll(samples.functions!);
// Build the initial id to function index mapping. This allows for us to
// lookup a ProfileFunction in the global function list stored in this
// cache. This works since most ProfileFunction objects will have an
// associated function with a *typically* stable service ID that we can
// use as a key.
//
// 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;
}
// Clear tick information as we'll need to recalculate these values later
// when a request for samples from this repository is received.
for (final f in functions) {
f.inclusiveTicks = 0;
f.exclusiveTicks = 0;
}
_firstSampleTimestamp = samples.timeOriginMicros!;
} else {
final newFunctions = samples.functions!;
final indexMapping = <int, int>{};
// 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]);
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]);
// Reset tick state as we'll recalculate later.
functions.last.inclusiveTicks = 0;
functions.last.exclusiveTicks = 0;
}
}
// Update the indicies into the function table for functions that were
// newly processed in the most recent event.
for (final sample in samples.samples!) {
final stack = sample.stack!;
for (int i = 0; i < stack.length; ++i) {
if (indexMapping.containsKey(stack[i])) {
stack[i] = indexMapping[stack[i]]!;
}
}
}
}
final relevantSamples = samples.samples!.where((s) => s.userTag == tag);
for (final sample in relevantSamples) {
add(sample);
}
}
@override
CpuSample? add(CpuSample sample) {
final evicted = super.add(sample);
void updateTicksForSample(CpuSample sample, int increment) {
final stack = sample.stack!;
for (int i = 0; i < stack.length; ++i) {
final function = functions[stack[i]];
function.inclusiveTicks = function.inclusiveTicks! + increment;
if (i + 1 == stack.length) {
function.exclusiveTicks = function.exclusiveTicks! + increment;
}
}
}
if (evicted != null) {
// If a sample is evicted from the cache, we need to decrement the tick
// counters for each function in the sample's stack.
updateTicksForSample(sample, -1);
// We also need to change the first timestamp to that of the next oldest
// sample.
_firstSampleTimestamp = call().first.timestamp!;
}
_lastSampleTimestamp = sample.timestamp!;
// Update function ticks to include the new sample.
updateTicksForSample(sample, 1);
return evicted;
}
Map<String, dynamic> toJson() {
return {
'type': 'CachedCpuSamples',
'userTag': tag,
'truncated': isTruncated,
if (functions.isNotEmpty) ...{
'samplePeriod': samplePeriod,
'maxStackDepth': maxStackDepth,
},
'timeOriginMicros': _firstSampleTimestamp,
'timeExtentMicros': _lastSampleTimestamp - _firstSampleTimestamp,
'functions': [
// TODO(bkonyi): remove functions with no ticks and update sample stacks.
for (final f in functions) f.toJson(),
],
'sampleCount': call().length,
'samples': [
for (final s in call()) s.toJson(),
]
};
}
/// The UserTag associated with all samples stored in this repository.
final String tag;
/// The list of function references with corresponding profiler tick data.
/// ** NOTE **: The tick values here need to be updated as new CpuSamples
/// events are delivered.
final functions = <ProfileFunction>[];
final idToFunctionIndex = <String, int>{};
/// Assume sample period and max stack depth won't change.
late final int samplePeriod;
late final int maxStackDepth;
late final int pid;
int _firstSampleTimestamp = 0;
int _lastSampleTimestamp = 0;
}

View file

@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
@ -54,6 +55,7 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
this._remoteVmServiceUri,
this._uri,
this._authCodesEnabled,
this._cachedUserTags,
this._ipv6,
this._devToolsConfiguration,
this.shouldLogRequests,
@ -388,6 +390,9 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
final DevToolsConfiguration? _devToolsConfiguration;
List<String> get cachedUserTags => UnmodifiableListView(_cachedUserTags);
final List<String> _cachedUserTags;
Future<void> get done => _done.future;
Completer _done = Completer<void>();
bool _initializationComplete = false;

View file

@ -2,12 +2,14 @@
// 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:dds/src/utils/mutex.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'package:vm_service/vm_service.dart';
import 'client.dart';
import 'constants.dart';
import 'cpu_samples_manager.dart';
import 'dds_impl.dart';
import 'utils/mutex.dart';
/// This file contains functionality used to track the running state of
/// all isolates in a given Dart process.
@ -36,7 +38,11 @@ enum _IsolateState {
}
class _RunningIsolate {
_RunningIsolate(this.isolateManager, this.id, this.name);
_RunningIsolate(this.isolateManager, this.id, this.name)
: cpuSamplesManager = CpuSamplesManager(
isolateManager.dds,
id,
);
// State setters.
void pausedOnExit() => _state = _IsolateState.pauseExit;
@ -104,6 +110,29 @@ class _RunningIsolate {
/// Should always be called after an isolate is resumed.
void clearResumeApprovals() => _resumeApprovalsByName.clear();
Map<String, dynamic> getCachedCpuSamples(String userTag) {
final repo = cpuSamplesManager.cpuSamplesCaches[userTag];
if (repo == null) {
throw json_rpc.RpcException.invalidParams(
'CPU sample caching is not enabled for tag: "$userTag"',
);
}
return repo.toJson();
}
void handleEvent(Event event) {
switch (event.kind) {
case EventKind.kUserTagChanged:
cpuSamplesManager.handleUserTagEvent(event);
return;
case EventKind.kCpuSamples:
cpuSamplesManager.handleCpuSamplesEvent(event);
return;
default:
return;
}
}
int get _isolateStateMask => isolateStateToMaskMapping[_state] ?? 0;
static const isolateStateToMaskMapping = {
@ -113,6 +142,7 @@ class _RunningIsolate {
};
final IsolateManager isolateManager;
final CpuSamplesManager cpuSamplesManager;
final String name;
final String id;
final Set<String?> _resumeApprovalsByName = {};
@ -123,20 +153,25 @@ class IsolateManager {
IsolateManager(this.dds);
/// Handles state changes for isolates.
void handleIsolateEvent(json_rpc.Parameters parameters) {
final event = parameters['event'];
final eventKind = event['kind'].asString;
void handleIsolateEvent(Event event) {
// There's no interesting information about isolate state associated with
// and IsolateSpawn event.
if (eventKind == ServiceEvents.isolateSpawn) {
// TODO(bkonyi): why isn't IsolateSpawn in package:vm_service
if (event.kind! == ServiceEvents.isolateSpawn) {
return;
}
final isolateData = event['isolate'];
final id = isolateData['id'].asString;
final name = isolateData['name'].asString;
_updateIsolateState(id, name, eventKind);
final isolateData = event.isolate!;
final id = isolateData.id!;
final name = isolateData.name!;
_updateIsolateState(id, name, event.kind!);
}
void routeEventToIsolate(Event event) {
final isolateId = event.isolate!.id!;
if (isolates.containsKey(isolateId)) {
isolates[isolateId]!.handleEvent(event);
}
}
void _updateIsolateState(String id, String name, String eventKind) {
@ -248,6 +283,16 @@ class IsolateManager {
);
}
Map<String, dynamic> getCachedCpuSamples(json_rpc.Parameters parameters) {
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);
}
/// Forwards a `resume` request to the VM service.
Future<Map<String, dynamic>> _sendResumeRequest(
String isolateId,

View file

@ -2,17 +2,16 @@
// 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 'dart:math';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'client.dart';
import 'common/ring_buffer.dart';
/// [LoggingRepository] is used to store historical log messages from the
/// target VM service. Clients which connect to DDS and subscribe to the
/// `Logging` stream will be sent all messages contained within this repository
/// upon initial subscription.
class LoggingRepository extends _RingBuffer<Map<String, dynamic>> {
class LoggingRepository extends RingBuffer<Map<String, dynamic>> {
LoggingRepository([int logHistoryLength = 10000]) : super(logHistoryLength) {
// TODO(bkonyi): enforce log history limit when DartDevelopmentService
// allows for this to be set via Dart code.
@ -45,54 +44,3 @@ class LoggingRepository extends _RingBuffer<Map<String, dynamic>> {
final Set<DartDevelopmentServiceClient> _sentHistoricLogsClientSet = {};
static const int _kMaxLogBufferSize = 100000;
}
// TODO(bkonyi): move to standalone file if we decide to use this elsewhere.
class _RingBuffer<T> {
_RingBuffer(this._bufferSize) {
_buffer = List<T?>.filled(
_bufferSize,
null,
);
}
Iterable<T> call() sync* {
for (int i = _size - 1; i >= 0; --i) {
yield _buffer[(_count - i - 1) % _bufferSize]!;
}
}
void add(T e) {
if (_buffer.isEmpty) {
return;
}
_buffer[_count++ % _bufferSize] = e;
}
void resize(int size) {
assert(size >= 0);
if (size == _bufferSize) {
return;
}
final resized = List<T?>.filled(
size,
null,
);
int count = 0;
if (size > 0) {
for (final e in this()) {
resized[count++ % size] = e;
}
}
_count = count;
_bufferSize = size;
_buffer = resized;
}
int get bufferSize => _bufferSize;
int get _size => min(_count, _bufferSize);
int _bufferSize;
int _count = 0;
late List<T?> _buffer;
}

View file

@ -5,6 +5,7 @@
import 'dart:typed_data';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'package:vm_service/vm_service.dart';
import 'client.dart';
import 'dds_impl.dart';
@ -108,18 +109,31 @@ class StreamManager {
// Stdout and Stderr streams may not exist.
}
}
if (dds.cachedUserTags.isNotEmpty) {
await streamListen(null, EventStreams.kProfiler);
}
dds.vmServiceClient.registerMethod(
'streamNotify',
(parameters) {
(json_rpc.Parameters parameters) {
final streamId = parameters['streamId'].asString;
final event =
Event.parse(parameters['event'].asMap.cast<String, dynamic>())!;
// Forward events from the streams IsolateManager subscribes to.
if (isolateManagerStreams.contains(streamId)) {
dds.isolateManager.handleIsolateEvent(parameters);
dds.isolateManager.handleIsolateEvent(event);
}
// Keep a history of messages to send to clients when they first
// subscribe to a stream with an event history.
if (loggingRepositories.containsKey(streamId)) {
loggingRepositories[streamId]!.add(parameters.asMap);
loggingRepositories[streamId]!.add(
parameters.asMap.cast<String, dynamic>(),
);
}
// If the event contains an isolate, forward the event to the
// corresponding isolate to be handled.
if (event.isolate != null) {
dds.isolateManager.routeEventToIsolate(event);
}
streamNotify(streamId, parameters.value);
},
@ -262,6 +276,7 @@ class StreamManager {
static const kExtensionStream = 'Extension';
static const kIsolateStream = 'Isolate';
static const kLoggingStream = 'Logging';
static const kProfilerStream = 'Profiler';
static const kStderrStream = 'Stderr';
static const kStdoutStream = 'Stdout';
@ -283,6 +298,12 @@ class StreamManager {
kStdoutStream,
};
// Never cancel the profiler stream as `CpuSampleRepository` requires
// `UserTagChanged` events to enable/disable sample caching.
static const cpuSampleRepositoryStreams = <String>{
kProfilerStream,
};
// The set of streams that DDS requires to function.
static final ddsCoreStreams = <String>{
...isolateManagerStreams,

View file

@ -13,18 +13,46 @@ extension DdsExtension on VmService {
static bool _factoriesRegistered = false;
static Version? _ddsVersion;
/// The _getDartDevelopmentServiceVersion_ RPC is used to determine what version of
/// The [getDartDevelopmentServiceVersion] RPC is used to determine what version of
/// the Dart Development Service Protocol is served by a DDS instance.
///
/// The result of this call is cached for subsequent invocations.
Future<Version> getDartDevelopmentServiceVersion() async {
if (_ddsVersion == null) {
_ddsVersion =
await _callHelper<Version>('getDartDevelopmentServiceVersion');
_ddsVersion = await _callHelper<Version>(
'getDartDevelopmentServiceVersion',
);
}
return _ddsVersion!;
}
/// The [getCachedCpuSamples] RPC is used to retrieve a cache of CPU samples
/// collected under a [UserTag] with name `userTag`.
Future<CachedCpuSamples> getCachedCpuSamples(
String isolateId, String userTag) async {
if (!(await _versionCheck(1, 3))) {
throw UnimplementedError('getCachedCpuSamples requires DDS version 1.3');
}
return _callHelper<CachedCpuSamples>('getCachedCpuSamples', args: {
'isolateId': isolateId,
'userTag': userTag,
});
}
/// The [getAvailableCachedCpuSamples] RPC is used to determine which caches of CPU samples
/// are available. Caches are associated with individual [UserTag] names and are specified
/// when DDS is started via the `cachedUserTags` parameter.
Future<AvailableCachedCpuSamples> getAvailableCachedCpuSamples() async {
if (!(await _versionCheck(1, 3))) {
throw UnimplementedError(
'getAvailableCachedCpuSamples requires DDS version 1.3',
);
}
return _callHelper<AvailableCachedCpuSamples>(
'getAvailableCachedCpuSamples',
);
}
/// Retrieve the event history for `stream`.
///
/// If `stream` does not have event history collected, a parameter error is
@ -126,6 +154,11 @@ extension DdsExtension on VmService {
static void _registerFactories() {
addTypeFactory('StreamHistory', StreamHistory.parse);
addTypeFactory(
'AvailableCachedCpuSamples',
AvailableCachedCpuSamples.parse,
);
addTypeFactory('CachedCpuSamples', CachedCpuSamples.parse);
_factoriesRegistered = true;
}
}
@ -154,3 +187,86 @@ class StreamHistory extends Response {
List<Event> get history => UnmodifiableListView(_history);
final List<Event> _history;
}
/// An extension of [CpuSamples] which represents a set of cached samples,
/// associated with a particular [UserTag] name.
class CachedCpuSamples extends CpuSamples {
static CachedCpuSamples? parse(Map<String, dynamic>? json) =>
json == null ? null : CachedCpuSamples._fromJson(json);
CachedCpuSamples({
required this.userTag,
this.truncated,
required int? samplePeriod,
required int? maxStackDepth,
required int? sampleCount,
required int? timeSpan,
required int? timeOriginMicros,
required int? timeExtentMicros,
required int? pid,
required List<ProfileFunction>? functions,
required List<CpuSample>? samples,
}) : super(
samplePeriod: samplePeriod,
maxStackDepth: maxStackDepth,
sampleCount: sampleCount,
timeSpan: timeSpan,
timeOriginMicros: timeOriginMicros,
timeExtentMicros: timeExtentMicros,
pid: pid,
functions: functions,
samples: samples,
);
CachedCpuSamples._fromJson(Map<String, dynamic> json)
: userTag = json['userTag']!,
truncated = json['truncated'],
super(
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<ProfileFunction>.from(
createServiceObject(json['functions'], const ['ProfileFunction'])
as List? ??
[],
),
samples: List<CpuSample>.from(
createServiceObject(json['samples'], const ['CpuSample'])
as List? ??
[],
),
);
@override
String get type => 'CachedCpuSamples';
/// The name of the [UserTag] associated with this cache of [CpuSamples].
final String userTag;
/// Provided if the CPU sample cache has filled and older samples have been
/// dropped.
final bool? truncated;
}
/// A collection of [UserTag] names associated with caches of CPU samples.
class AvailableCachedCpuSamples extends Response {
static AvailableCachedCpuSamples? parse(Map<String, dynamic>? json) =>
json == null ? null : AvailableCachedCpuSamples._fromJson(json);
AvailableCachedCpuSamples({
required this.cacheNames,
});
AvailableCachedCpuSamples._fromJson(Map<String, dynamic> json)
: cacheNames = List<String>.from(json['cacheNames']);
@override
String get type => 'AvailableCachedUserTagCpuSamples';
/// A [List] of [UserTag] names associated with CPU sample caches.
final List<String> cacheNames;
}

View file

@ -25,7 +25,7 @@ dependencies:
shelf_web_socket: ^1.0.0
sse: ^4.0.0
stream_channel: ^2.0.0
vm_service: ^7.0.0
vm_service: ^7.2.0
web_socket_channel: ^2.0.0
dev_dependencies:

View file

@ -0,0 +1,21 @@
// Copyright (c) 2021, 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 'dart:developer';
fib(int n) {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
void main() {
UserTag('Testing').makeCurrent();
int i = 5;
while (true) {
++i;
fib(i);
}
}

View file

@ -0,0 +1,124 @@
// Copyright (c) 2021, 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 'dart:async';
import 'dart:io';
import 'package:dds/dds.dart';
import 'package:dds/src/utils/mutex.dart';
import 'package:dds/vm_service_extensions.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';
import 'package:vm_service/vm_service_io.dart';
import 'common/test_helper.dart';
void main() {
late Process process;
late DartDevelopmentService dds;
setUp(() async {
process = await spawnDartProcess(
'get_cached_cpu_samples_script.dart',
);
});
tearDown(() async {
await dds.shutdown();
process.kill();
});
test(
'No UserTags to cache',
() async {
dds = await DartDevelopmentService.startDartDevelopmentService(
remoteVmServiceUri,
);
expect(dds.isRunning, true);
final service = await vmServiceConnectUri(dds.wsUri.toString());
// We didn't provide `cachedUserTags` when starting DDS, so we shouldn't
// be caching anything.
final availableCaches = await service.getAvailableCachedCpuSamples();
expect(availableCaches.cacheNames.length, 0);
final isolate = (await service.getVM()).isolates!.first;
try {
await service.getCachedCpuSamples(isolate.id!, 'Fake');
fail('Invalid userTag did not cause an exception');
} on RPCError catch (e) {
expect(
e.message,
'CPU sample caching is not enabled for tag: "Fake"',
);
}
},
timeout: Timeout.none,
);
test(
'Cache CPU samples for provided UserTag name',
() async {
const kUserTag = 'Testing';
dds = await DartDevelopmentService.startDartDevelopmentService(
remoteVmServiceUri,
cachedUserTags: [kUserTag],
);
expect(dds.isRunning, true);
final service = await vmServiceConnectUri(dds.wsUri.toString());
// Ensure we're caching results for samples under the 'Testing' UserTag.
final availableCaches = await service.getAvailableCachedCpuSamples();
expect(availableCaches.cacheNames.length, 1);
expect(availableCaches.cacheNames.first, kUserTag);
final isolate = (await service.getVM()).isolates!.first;
final completer = Completer<void>();
int i = 0;
int count = 0;
final mutex = Mutex();
late StreamSubscription sub;
sub = service.onProfilerEvent.listen(
(event) async {
// Process one event at a time to prevent racey updates to count.
await mutex.runGuarded(
() async {
if (event.kind == EventKind.kCpuSamples &&
event.isolate!.id! == isolate.id!) {
++i;
if (i > 3) {
if (!completer.isCompleted) {
await sub.cancel();
completer.complete();
}
return;
}
// Ensure the number of CPU samples in the CpuSample event is
// is consistent with the number of samples in the cache.
expect(event.cpuSamples, isNotNull);
count += event.cpuSamples!.samples!
.where((e) => e.userTag == kUserTag)
.length;
final cache = await service.getCachedCpuSamples(
isolate.id!,
availableCaches.cacheNames.first,
);
// DDS may have processed more sample blocks than we've had a chance
// to, so just ensure we have at least as many samples in the cache
// as we've seen.
expect(cache.sampleCount! >= count, true);
}
},
);
},
);
await service.streamListen(EventStreams.kProfiler);
await service.resume(isolate.id!);
await completer.future;
},
timeout: Timeout.none,
);
}

View file

@ -27,7 +27,6 @@ sigquit_starts_service_test: SkipByDesign # Spawns a secondary process using Pla
coverage_optimized_function_test: Pass, Slow
evaluate_activation_test/instance: RuntimeError # http://dartbug.com/20047
evaluate_activation_test/scope: RuntimeError # http://dartbug.com/20047
get_source_report_test: RuntimeError # Should pass again when constant evaluation is relanded, see http://dartbug.com/36600
pause_on_exception_from_slow_path_test: Pass, Slow
pause_on_unhandled_async_exceptions2_test: Pass, Slow

View file

@ -27,7 +27,6 @@ sigquit_starts_service_test: SkipByDesign # Spawns a secondary process using Pla
coverage_optimized_function_test: Pass, Slow
evaluate_activation_test/instance: RuntimeError # http://dartbug.com/20047
evaluate_activation_test/scope: RuntimeError # http://dartbug.com/20047
get_source_report_test: RuntimeError # Should pass again when constant evaluation is relanded, see http://dartbug.com/36600
pause_on_exception_from_slow_path_test: Pass, Slow
pause_on_unhandled_async_exceptions2_test: Pass, Slow

View file

@ -184,17 +184,17 @@ static constexpr dart::compiler::target::word ICData_entries_offset = 12;
static constexpr dart::compiler::target::word ICData_owner_offset = 20;
static constexpr dart::compiler::target::word ICData_state_bits_offset = 28;
static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 20;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 24;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 28;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 24;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 28;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 32;
static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
20;
static constexpr dart::compiler::target::word
IsolateGroup_shared_class_table_offset = 8;
static constexpr dart::compiler::target::word
IsolateGroup_cached_class_table_table_offset = 16;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 36;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 16;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 40;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 20;
static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 12;
static constexpr dart::compiler::target::word
LinkedHashBase_deleted_keys_offset = 20;
@ -727,17 +727,17 @@ static constexpr dart::compiler::target::word ICData_entries_offset = 24;
static constexpr dart::compiler::target::word ICData_owner_offset = 40;
static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 48;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 56;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 64;
static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
40;
static constexpr dart::compiler::target::word
IsolateGroup_shared_class_table_offset = 16;
static constexpr dart::compiler::target::word
IsolateGroup_cached_class_table_table_offset = 32;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 72;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 80;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 40;
static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 24;
static constexpr dart::compiler::target::word
LinkedHashBase_deleted_keys_offset = 40;
@ -1275,17 +1275,17 @@ static constexpr dart::compiler::target::word ICData_entries_offset = 12;
static constexpr dart::compiler::target::word ICData_owner_offset = 20;
static constexpr dart::compiler::target::word ICData_state_bits_offset = 28;
static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 20;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 24;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 28;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 24;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 28;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 32;
static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
20;
static constexpr dart::compiler::target::word
IsolateGroup_shared_class_table_offset = 8;
static constexpr dart::compiler::target::word
IsolateGroup_cached_class_table_table_offset = 16;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 36;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 16;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 40;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 20;
static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 12;
static constexpr dart::compiler::target::word
LinkedHashBase_deleted_keys_offset = 20;
@ -1815,17 +1815,17 @@ static constexpr dart::compiler::target::word ICData_entries_offset = 24;
static constexpr dart::compiler::target::word ICData_owner_offset = 40;
static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 48;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 56;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 64;
static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
40;
static constexpr dart::compiler::target::word
IsolateGroup_shared_class_table_offset = 16;
static constexpr dart::compiler::target::word
IsolateGroup_cached_class_table_table_offset = 32;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 72;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 80;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 40;
static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 24;
static constexpr dart::compiler::target::word
LinkedHashBase_deleted_keys_offset = 40;
@ -2364,17 +2364,17 @@ static constexpr dart::compiler::target::word ICData_entries_offset = 24;
static constexpr dart::compiler::target::word ICData_owner_offset = 40;
static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 48;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 56;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 64;
static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
40;
static constexpr dart::compiler::target::word
IsolateGroup_shared_class_table_offset = 16;
static constexpr dart::compiler::target::word
IsolateGroup_cached_class_table_table_offset = 32;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 72;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 80;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 40;
static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 16;
static constexpr dart::compiler::target::word
LinkedHashBase_deleted_keys_offset = 24;
@ -2912,17 +2912,17 @@ static constexpr dart::compiler::target::word ICData_entries_offset = 24;
static constexpr dart::compiler::target::word ICData_owner_offset = 40;
static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
static constexpr dart::compiler::target::word Isolate_current_tag_offset = 48;
static constexpr dart::compiler::target::word Isolate_default_tag_offset = 56;
static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 64;
static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
40;
static constexpr dart::compiler::target::word
IsolateGroup_shared_class_table_offset = 16;
static constexpr dart::compiler::target::word
IsolateGroup_cached_class_table_table_offset = 32;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 72;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
static constexpr dart::compiler::target::word Isolate_single_step_offset = 80;
static constexpr dart::compiler::target::word Isolate_user_tag_offset = 40;
static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 16;
static constexpr dart::compiler::target::word
LinkedHashBase_deleted_keys_offset = 24;
@ -6720,11 +6720,11 @@ static constexpr dart::compiler::target::word AOT_ICData_owner_offset = 16;
static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 20;
static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
20;
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
24;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
28;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
32;
static constexpr dart::compiler::target::word
AOT_IsolateGroup_object_store_offset = 20;
static constexpr dart::compiler::target::word
@ -6732,8 +6732,8 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
AOT_IsolateGroup_cached_class_table_table_offset = 16;
static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
36;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 16;
40;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 20;
static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
12;
static constexpr dart::compiler::target::word
@ -7327,11 +7327,11 @@ static constexpr dart::compiler::target::word AOT_ICData_owner_offset = 32;
static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 40;
static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
40;
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
48;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
56;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
64;
static constexpr dart::compiler::target::word
AOT_IsolateGroup_object_store_offset = 40;
static constexpr dart::compiler::target::word
@ -7339,8 +7339,8 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
AOT_IsolateGroup_cached_class_table_table_offset = 32;
static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
72;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
80;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 40;
static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
24;
static constexpr dart::compiler::target::word
@ -7940,11 +7940,11 @@ static constexpr dart::compiler::target::word AOT_ICData_owner_offset = 32;
static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 40;
static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
40;
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
48;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
56;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
64;
static constexpr dart::compiler::target::word
AOT_IsolateGroup_object_store_offset = 40;
static constexpr dart::compiler::target::word
@ -7952,8 +7952,8 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
AOT_IsolateGroup_cached_class_table_table_offset = 32;
static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
72;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
80;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 40;
static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
24;
static constexpr dart::compiler::target::word
@ -8550,11 +8550,11 @@ static constexpr dart::compiler::target::word AOT_ICData_owner_offset = 32;
static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 40;
static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
40;
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
48;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
56;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
64;
static constexpr dart::compiler::target::word
AOT_IsolateGroup_object_store_offset = 40;
static constexpr dart::compiler::target::word
@ -8562,8 +8562,8 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
AOT_IsolateGroup_cached_class_table_table_offset = 32;
static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
72;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
80;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 40;
static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
16;
static constexpr dart::compiler::target::word
@ -9159,11 +9159,11 @@ static constexpr dart::compiler::target::word AOT_ICData_owner_offset = 32;
static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 40;
static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
40;
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
48;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
56;
static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
64;
static constexpr dart::compiler::target::word
AOT_IsolateGroup_object_store_offset = 40;
static constexpr dart::compiler::target::word
@ -9171,8 +9171,8 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
AOT_IsolateGroup_cached_class_table_table_offset = 32;
static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
72;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
80;
static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 40;
static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
16;
static constexpr dart::compiler::target::word

View file

@ -2364,6 +2364,55 @@ void Isolate::set_current_allocation_sample_block(SampleBlock* current) {
}
current_allocation_sample_block_ = current;
}
void Isolate::FreeSampleBlock(SampleBlock* block) {
if (block == nullptr) {
return;
}
SampleBlock* head;
// We're pushing the freed sample block to the front of the free_block_list_,
// which means the last element of the list will be the oldest freed sample.
do {
head = free_block_list_.load(std::memory_order_acquire);
block->next_free_ = head;
} while (!free_block_list_.compare_exchange_weak(head, block,
std::memory_order_release));
}
void Isolate::ProcessFreeSampleBlocks(Thread* thread) {
SampleBlock* head = free_block_list_.exchange(nullptr);
// Reverse the list before processing so older blocks are streamed and reused
// first.
SampleBlock* reversed_head = nullptr;
while (head != nullptr) {
SampleBlock* next = head->next_free_;
if (reversed_head == nullptr) {
reversed_head = head;
reversed_head->next_free_ = nullptr;
} else {
head->next_free_ = reversed_head;
reversed_head = head;
}
head = next;
}
head = reversed_head;
while (head != nullptr) {
if (Service::profiler_stream.enabled() && !IsSystemIsolate(this)) {
StackZone zone(thread);
HandleScope handle_scope(thread);
Profile profile;
profile.Build(thread, nullptr, head);
ServiceEvent event(this, ServiceEvent::kCpuSamples);
event.set_cpu_profile(&profile);
Service::HandleEvent(&event);
}
SampleBlock* next = head->next_free_;
head->next_free_ = nullptr;
head->evictable_ = true;
Profiler::sample_block_buffer()->FreeBlock(head);
head = next;
}
}
#endif // !defined(PRODUCT)
// static
@ -2464,6 +2513,25 @@ void Isolate::Shutdown() {
ServiceIsolate::SendIsolateShutdownMessage();
#if !defined(PRODUCT)
debugger()->Shutdown();
// Cleanup profiler state.
SampleBlock* cpu_block = current_sample_block();
if (cpu_block != nullptr) {
cpu_block->release_block();
}
SampleBlock* allocation_block = current_allocation_sample_block();
if (allocation_block != nullptr) {
allocation_block->release_block();
}
// Process the previously assigned sample blocks if we're using the
// profiler's sample buffer. Some tests create their own SampleBlockBuffer
// and handle block processing themselves.
if ((cpu_block != nullptr || allocation_block != nullptr) &&
Profiler::sample_block_buffer() != nullptr) {
StackZone zone(thread);
HandleScope handle_scope(thread);
Profiler::sample_block_buffer()->ProcessCompletedBlocks();
}
#endif
}
@ -2522,26 +2590,6 @@ void Isolate::LowLevelCleanup(Isolate* isolate) {
// requests anymore.
Thread::ExitIsolate();
#if !defined(PRODUCT)
// Cleanup profiler state.
SampleBlock* cpu_block = isolate->current_sample_block();
if (cpu_block != nullptr) {
cpu_block->release_block();
}
SampleBlock* allocation_block = isolate->current_allocation_sample_block();
if (allocation_block != nullptr) {
allocation_block->release_block();
}
// Process the previously assigned sample blocks if we're using the
// profiler's sample buffer. Some tests create their own SampleBlockBuffer
// and handle block processing themselves.
if ((cpu_block != nullptr || allocation_block != nullptr) &&
Profiler::sample_block_buffer() != nullptr) {
Profiler::sample_block_buffer()->ProcessCompletedBlocks();
}
#endif // !defined(PRODUCT)
// Now it's safe to delete the isolate.
delete isolate;

View file

@ -1099,6 +1099,13 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
SampleBlock* current_sample_block() const { return current_sample_block_; }
void set_current_sample_block(SampleBlock* current);
void FreeSampleBlock(SampleBlock* block);
void ProcessFreeSampleBlocks(Thread* thread);
bool should_process_blocks() const {
return free_block_list_.load(std::memory_order_relaxed) != nullptr;
}
std::atomic<SampleBlock*> free_block_list_ = nullptr;
// Returns the current SampleBlock used to track Dart allocation samples.
//
// Allocations should only occur on the mutator thread for an isolate, so we

View file

@ -12788,7 +12788,6 @@ static void AddScriptIfUnique(const GrowableObjectArray& scripts,
}
ArrayPtr Library::LoadedScripts() const {
ASSERT(Thread::Current()->IsMutatorThread());
// We compute the list of loaded scripts lazily. The result is
// cached in loaded_scripts_.
if (loaded_scripts() == Array::null()) {

View file

@ -66,6 +66,13 @@ SampleBlockBuffer* Profiler::sample_block_buffer_ = nullptr;
AllocationSampleBuffer* Profiler::allocation_sample_buffer_ = nullptr;
ProfilerCounters Profiler::counters_ = {};
bool SampleBlockProcessor::initialized_ = false;
bool SampleBlockProcessor::shutdown_ = false;
bool SampleBlockProcessor::thread_running_ = false;
ThreadJoinId SampleBlockProcessor::processor_thread_id_ =
OSThread::kInvalidThreadJoinId;
Monitor* SampleBlockProcessor::monitor_ = nullptr;
void Profiler::Init() {
// Place some sane restrictions on user controlled flags.
SetSampleDepth(FLAG_max_profile_depth);
@ -83,6 +90,8 @@ void Profiler::Init() {
}
ThreadInterrupter::Init();
ThreadInterrupter::Startup();
SampleBlockProcessor::Init();
SampleBlockProcessor::Startup();
initialized_ = true;
}
@ -113,6 +122,7 @@ void Profiler::Cleanup() {
}
ASSERT(initialized_);
ThreadInterrupter::Cleanup();
SampleBlockProcessor::Cleanup();
SampleBlockCleanupVisitor visitor;
Isolate::VisitIsolates(&visitor);
initialized_ = false;
@ -223,21 +233,9 @@ SampleBlock* SampleBlockBuffer::ReserveSampleBlock() {
void SampleBlockBuffer::ProcessCompletedBlocks() {
Thread* thread = Thread::Current();
DisableThreadInterruptsScope dtis(thread);
int64_t start = Dart_TimelineGetMicros();
for (intptr_t i = 0; i < capacity_; ++i) {
SampleBlock* block = &blocks_[i];
if (block->is_full() && !block->evictable()) {
if (Service::profiler_stream.enabled()) {
Profile profile(block->owner());
profile.Build(thread, nullptr, block);
ServiceEvent event(block->owner(), ServiceEvent::kCpuSamples);
event.set_cpu_profile(&profile);
Service::HandleEvent(&event);
}
block->evictable_ = true;
FreeBlock(block);
}
}
thread->isolate()->ProcessFreeSampleBlocks(thread);
int64_t end = Dart_TimelineGetMicros();
Dart_TimelineEvent("SampleBlockBuffer::ProcessCompletedBlocks", start, end,
Dart_Timeline_Event_Duration, 0, nullptr, nullptr);
@ -306,12 +304,6 @@ Sample* SampleBlockBuffer::ReserveSampleImpl(Isolate* isolate,
Sample* sample = nullptr;
if (block != nullptr) {
sample = block->ReserveSample();
if (sample != nullptr && block->is_full()) {
// TODO(bkonyi): remove once streaming is re-enabled.
// https://github.com/dart-lang/sdk/issues/46825
block->evictable_ = true;
FreeBlock(block);
}
}
if (sample != nullptr) {
return sample;
@ -326,6 +318,7 @@ Sample* SampleBlockBuffer::ReserveSampleImpl(Isolate* isolate,
return nullptr;
}
isolate->set_current_allocation_sample_block(next);
isolate->FreeSampleBlock(block);
} else {
MutexLocker locker(isolate->current_sample_block_lock());
next = ReserveSampleBlock();
@ -334,13 +327,14 @@ Sample* SampleBlockBuffer::ReserveSampleImpl(Isolate* isolate,
return nullptr;
}
isolate->set_current_sample_block(next);
isolate->FreeSampleBlock(block);
}
next->set_is_allocation_block(allocation_sample);
can_process_block_.store(true);
// TODO(bkonyi): re-enable after block streaming is fixed.
// See https://github.com/dart-lang/sdk/issues/46825
// isolate->mutator_thread()->ScheduleInterrupts(Thread::kVMInterrupt);
bool scheduled = can_process_block_.exchange(true);
if (!scheduled) {
isolate->mutator_thread()->ScheduleInterrupts(Thread::kVMInterrupt);
}
return ReserveSampleImpl(isolate, allocation_sample);
}
@ -1825,6 +1819,94 @@ ProcessedSampleBuffer::ProcessedSampleBuffer()
ASSERT(code_lookup_table_ != NULL);
}
void SampleBlockProcessor::Init() {
ASSERT(!initialized_);
if (monitor_ == nullptr) {
monitor_ = new Monitor();
}
ASSERT(monitor_ != nullptr);
initialized_ = true;
shutdown_ = false;
}
void SampleBlockProcessor::Startup() {
ASSERT(initialized_);
ASSERT(processor_thread_id_ == OSThread::kInvalidThreadJoinId);
MonitorLocker startup_ml(monitor_);
OSThread::Start("Dart Profiler SampleBlockProcessor", ThreadMain, 0);
while (!thread_running_) {
startup_ml.Wait();
}
ASSERT(processor_thread_id_ != OSThread::kInvalidThreadJoinId);
}
void SampleBlockProcessor::Cleanup() {
{
MonitorLocker shutdown_ml(monitor_);
if (shutdown_) {
// Already shutdown.
return;
}
shutdown_ = true;
// Notify.
shutdown_ml.Notify();
ASSERT(initialized_);
}
// Join the thread.
ASSERT(processor_thread_id_ != OSThread::kInvalidThreadJoinId);
OSThread::Join(processor_thread_id_);
processor_thread_id_ = OSThread::kInvalidThreadJoinId;
initialized_ = false;
ASSERT(!thread_running_);
}
class SampleBlockProcessorVisitor : public IsolateVisitor {
public:
SampleBlockProcessorVisitor() = default;
virtual ~SampleBlockProcessorVisitor() = default;
void VisitIsolate(Isolate* isolate) {
if (!isolate->should_process_blocks()) {
return;
}
Thread::EnterIsolateAsHelper(isolate, Thread::kSampleBlockTask);
Thread* thread = Thread::Current();
{
DisableThreadInterruptsScope dtis(thread);
isolate->ProcessFreeSampleBlocks(thread);
}
Thread::ExitIsolateAsHelper();
}
};
void SampleBlockProcessor::ThreadMain(uword parameters) {
ASSERT(initialized_);
{
// Signal to main thread we are ready.
MonitorLocker startup_ml(monitor_);
OSThread* os_thread = OSThread::Current();
ASSERT(os_thread != NULL);
processor_thread_id_ = OSThread::GetCurrentThreadJoinId(os_thread);
thread_running_ = true;
startup_ml.Notify();
}
SampleBlockProcessorVisitor visitor;
MonitorLocker wait_ml(monitor_);
// Wakeup every 100ms.
const int64_t wakeup_interval = 1000 * 100;
while (true) {
wait_ml.WaitMicros(wakeup_interval);
if (shutdown_) {
break;
}
Isolate::VisitIsolates(&visitor);
}
// Signal to main thread we are exiting.
thread_running_ = false;
}
#endif // !PRODUCT
} // namespace dart

View file

@ -73,6 +73,9 @@ class Profiler : public AllStatic {
static SampleBlockBuffer* sample_block_buffer() {
return sample_block_buffer_;
}
static void set_sample_block_buffer(SampleBlockBuffer* buffer) {
sample_block_buffer_ = buffer;
}
static AllocationSampleBuffer* allocation_sample_buffer() {
return allocation_sample_buffer_;
}
@ -408,9 +411,9 @@ class Sample {
kTruncatedTraceBit = 5,
kClassAllocationSampleBit = 6,
kContinuationSampleBit = 7,
kThreadTaskBit = 8, // 6 bits.
kMetadataBit = 14, // 16 bits.
kNextFreeBit = 30,
kThreadTaskBit = 8, // 7 bits.
kMetadataBit = 15, // 16 bits.
kNextFreeBit = 31,
};
class HeadSampleBit : public BitField<uint32_t, bool, kHeadSampleBit, 1> {};
class LeafFrameIsDart
@ -426,7 +429,7 @@ class Sample {
class ContinuationSampleBit
: public BitField<uint32_t, bool, kContinuationSampleBit, 1> {};
class ThreadTaskBit
: public BitField<uint32_t, Thread::TaskKind, kThreadTaskBit, 6> {};
: public BitField<uint32_t, Thread::TaskKind, kThreadTaskBit, 7> {};
class MetadataBits : public BitField<uint32_t, intptr_t, kMetadataBit, 16> {};
int64_t timestamp_;
@ -710,7 +713,7 @@ class SampleBuffer : public ProcessedSampleBufferBuilder {
class SampleBlock : public SampleBuffer {
public:
// The default number of samples per block. Overridden by some tests.
static const intptr_t kSamplesPerBlock = 1000;
static const intptr_t kSamplesPerBlock = 100;
SampleBlock() = default;
virtual ~SampleBlock() = default;
@ -765,13 +768,14 @@ class SampleBlock : public SampleBuffer {
private:
friend class SampleBlockBuffer;
friend class Isolate;
DISALLOW_COPY_AND_ASSIGN(SampleBlock);
};
class SampleBlockBuffer : public ProcessedSampleBufferBuilder {
public:
static const intptr_t kDefaultBlockCount = 60;
static const intptr_t kDefaultBlockCount = 600;
// Creates a SampleBlockBuffer with a predetermined number of blocks.
//
@ -864,6 +868,8 @@ class SampleBlockBuffer : public ProcessedSampleBufferBuilder {
// Sample buffer management.
VirtualMemory* memory_;
Sample* sample_buffer_;
friend class Isolate;
DISALLOW_COPY_AND_ASSIGN(SampleBlockBuffer);
};
@ -1037,6 +1043,24 @@ class ProcessedSampleBuffer : public ZoneAllocated {
DISALLOW_COPY_AND_ASSIGN(ProcessedSampleBuffer);
};
class SampleBlockProcessor : public AllStatic {
public:
static void Init();
static void Startup();
static void Cleanup();
private:
static const intptr_t kMaxThreads = 4096;
static bool initialized_;
static bool shutdown_;
static bool thread_running_;
static ThreadJoinId processor_thread_id_;
static Monitor* monitor_;
static void ThreadMain(uword parameters);
};
} // namespace dart
#endif // RUNTIME_VM_PROFILER_H_

View file

@ -1450,9 +1450,8 @@ class ProfileBuilder : public ValueObject {
ProfileInfoKind info_kind_;
}; // ProfileBuilder.
Profile::Profile(Isolate* isolate)
: isolate_(isolate),
zone_(Thread::Current()->zone()),
Profile::Profile()
: zone_(Thread::Current()->zone()),
samples_(NULL),
live_code_(NULL),
dead_code_(NULL),
@ -1461,9 +1460,7 @@ Profile::Profile(Isolate* isolate)
dead_code_index_offset_(-1),
tag_code_index_offset_(-1),
min_time_(kMaxInt64),
max_time_(0) {
ASSERT(isolate_ != NULL);
}
max_time_(0) {}
void Profile::Build(Thread* thread,
SampleFilter* filter,
@ -1765,14 +1762,12 @@ void ProfilerService::PrintJSONImpl(Thread* thread,
SampleFilter* filter,
ProcessedSampleBufferBuilder* buffer,
bool include_code_samples) {
Isolate* isolate = thread->isolate();
// We should bail out in service.cc if the profiler is disabled.
ASSERT(buffer != nullptr);
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
profile.Build(thread, filter, buffer);
profile.PrintProfileJSON(stream, include_code_samples);
}

View file

@ -364,7 +364,7 @@ class ProfileCodeTable : public ZoneAllocated {
// a zone must be created that lives longer than this object.
class Profile : public ValueObject {
public:
explicit Profile(Isolate* isolate);
Profile();
// Build a filtered model using |filter|.
void Build(Thread* thread,
@ -403,7 +403,6 @@ class Profile : public ValueObject {
intptr_t frame_index);
void PrintSamplesJSON(JSONObject* obj, bool code_samples);
Isolate* isolate_;
Zone* zone_;
ProcessedSampleBuffer* samples_;
ProfileCodeTable* live_code_;

View file

@ -102,9 +102,30 @@ void VisitSamples(SampleBlockBuffer* buffer, SampleVisitor* visitor) {
buffer->VisitSamples(visitor);
}
class SampleBlockBufferOverrideScope {
public:
explicit SampleBlockBufferOverrideScope(SampleBlockBuffer* buffer)
: override_(buffer) {
orig_ = Profiler::sample_block_buffer();
Profiler::set_sample_block_buffer(override_);
}
~SampleBlockBufferOverrideScope() {
Profiler::set_sample_block_buffer(orig_);
delete override_;
}
private:
SampleBlockBuffer* orig_;
SampleBlockBuffer* override_;
};
TEST_CASE(Profiler_SampleBufferWrapTest) {
Isolate* isolate = Isolate::Current();
SampleBlockBuffer* sample_buffer = new SampleBlockBuffer(3, 1);
SampleBlockBufferOverrideScope sbbos(new SampleBlockBuffer(3, 1));
SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();
Dart_Port i = 123;
ProfileSampleBufferTestHelper visitor(i);
@ -142,12 +163,14 @@ TEST_CASE(Profiler_SampleBufferWrapTest) {
MutexLocker ml(isolate->current_sample_block_lock());
isolate->set_current_sample_block(nullptr);
}
delete sample_buffer;
}
TEST_CASE(Profiler_SampleBufferIterateTest) {
Isolate* isolate = Isolate::Current();
SampleBlockBuffer* sample_buffer = new SampleBlockBuffer(3, 1);
SampleBlockBufferOverrideScope sbbos(new SampleBlockBuffer(3, 1));
SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();
Dart_Port i = 123;
ProfileSampleBufferTestHelper visitor(i);
@ -182,7 +205,6 @@ TEST_CASE(Profiler_SampleBufferIterateTest) {
MutexLocker ml(isolate->current_sample_block_lock());
isolate->set_current_sample_block(nullptr);
}
delete sample_buffer;
}
TEST_CASE(Profiler_AllocationSampleTest) {
@ -480,7 +502,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_TrivialRecordAllocation) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
// Filter for the class in the time range.
AllocationFilter filter(isolate->main_port(), class_a.id(),
before_allocations_micros,
@ -510,7 +532,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_TrivialRecordAllocation) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id(),
Dart_TimelineGetMicros(), 16000);
profile.Build(thread, &filter, Profiler::sample_block_buffer());
@ -555,10 +577,9 @@ ISOLATE_UNIT_TEST_CASE(Profiler_NativeAllocation) {
// with each node.
{
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
// Filter for the class in the time range.
NativeAllocationSampleFilter filter(before_allocations_micros,
@ -595,10 +616,9 @@ ISOLATE_UNIT_TEST_CASE(Profiler_NativeAllocation) {
// freed above is marked as free and is no longer reported.
{
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
// Filter for the class in the time range.
NativeAllocationSampleFilter filter(before_allocations_micros,
@ -611,10 +631,9 @@ ISOLATE_UNIT_TEST_CASE(Profiler_NativeAllocation) {
// Query with a time filter where no allocations occurred.
{
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
NativeAllocationSampleFilter filter(Dart_TimelineGetMicros(), 16000);
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples because none occured within
@ -660,7 +679,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ToggleRecordAllocation) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -677,7 +696,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ToggleRecordAllocation) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
@ -707,7 +726,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ToggleRecordAllocation) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
@ -745,7 +764,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_CodeTicks) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -765,7 +784,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_CodeTicks) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have three allocation samples.
@ -820,7 +839,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_FunctionTicks) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -840,7 +859,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_FunctionTicks) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have three allocation samples.
@ -890,7 +909,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_IntrinsicAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), double_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -903,7 +922,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_IntrinsicAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), double_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
@ -925,7 +944,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_IntrinsicAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), double_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
@ -952,7 +971,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ArrayAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), array_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -965,7 +984,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ArrayAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), array_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
@ -987,7 +1006,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ArrayAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), array_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
@ -1009,7 +1028,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ArrayAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), array_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples, since empty
@ -1039,7 +1058,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ContextAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), context_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -1052,7 +1071,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ContextAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), context_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
@ -1072,7 +1091,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ContextAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), context_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
@ -1112,7 +1131,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ClosureAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), closure_class.id());
filter.set_enable_vm_ticks(true);
profile.Build(thread, &filter, Profiler::sample_block_buffer());
@ -1136,7 +1155,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ClosureAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), closure_class.id());
filter.set_enable_vm_ticks(true);
profile.Build(thread, &filter, Profiler::sample_block_buffer());
@ -1167,7 +1186,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_TypedArrayAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), float32_list_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -1180,7 +1199,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_TypedArrayAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), float32_list_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
@ -1202,7 +1221,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_TypedArrayAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), float32_list_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
@ -1215,7 +1234,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_TypedArrayAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), float32_list_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should now have two allocation samples.
@ -1247,7 +1266,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -1260,7 +1279,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
@ -1280,7 +1299,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
@ -1293,7 +1312,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringAllocation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should now have two allocation samples.
@ -1325,7 +1344,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringInterpolation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -1338,7 +1357,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringInterpolation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
@ -1364,7 +1383,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringInterpolation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
@ -1377,7 +1396,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_StringInterpolation) {
{
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should now have two allocation samples.
@ -1433,7 +1452,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_FunctionInline) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -1451,7 +1470,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_FunctionInline) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have 50,000 allocation samples.
@ -1580,7 +1599,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_InliningIntervalBoundry) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
@ -1597,7 +1616,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_InliningIntervalBoundry) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
EXPECT_EQ(1, profile.sample_count());
@ -1673,7 +1692,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_ChainedSamples) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have 1 allocation sample.
@ -1768,7 +1787,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_BasicSourcePosition) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
@ -1850,7 +1869,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_BasicSourcePositionOptimized) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
@ -1928,7 +1947,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_SourcePosition) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
@ -2038,7 +2057,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_SourcePositionOptimized) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
@ -2133,7 +2152,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_BinaryOperatorSourcePosition) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
@ -2251,7 +2270,7 @@ ISOLATE_UNIT_TEST_CASE(Profiler_BinaryOperatorSourcePositionOptimized) {
Isolate* isolate = thread->isolate();
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
Profile profile;
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.

View file

@ -29,7 +29,6 @@ SourceReport::SourceReport(intptr_t report_set, CompileMode compile_mode)
script_(NULL),
start_pos_(TokenPosition::kMinSource),
end_pos_(TokenPosition::kMaxSource),
profile_(Isolate::Current()),
next_script_index_(0) {}
SourceReport::~SourceReport() {

View file

@ -450,11 +450,15 @@ ErrorPtr Thread::HandleInterrupts() {
}
#if !defined(PRODUCT)
// Processes completed SampleBlocks and sends CPU sample events over the
// service protocol when applicable.
SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();
if (sample_buffer != nullptr && sample_buffer->process_blocks()) {
sample_buffer->ProcessCompletedBlocks();
// Don't block system isolates to process CPU samples to avoid blocking
// them during critical tasks (e.g., initial compilation).
if (!Isolate::IsSystemIsolate(isolate())) {
// Processes completed SampleBlocks and sends CPU sample events over the
// service protocol when applicable.
SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();
if (sample_buffer != nullptr && sample_buffer->process_blocks()) {
sample_buffer->ProcessCompletedBlocks();
}
}
#endif // !defined(PRODUCT)
}

View file

@ -273,6 +273,7 @@ class Thread : public ThreadState {
kSweeperTask = 0x8,
kCompactorTask = 0x10,
kScavengerTask = 0x20,
kSampleBlockTask = 0x40,
};
// Converts a TaskKind to its corresponding C-String name.
static const char* TaskKindToCString(TaskKind kind);