blob: 48667cca923fe92ea5ff15b903c4f2d03ddd8626 [file] [log] [blame]
// Copyright (c) 2015, 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.
part of sample_profiler;
abstract class CallTreeNode<NodeT extends M.CallTreeNode>
implements M.CallTreeNode {
final List<NodeT> children;
int get count => _count;
int _count = 0;
int inclusiveNativeAllocations = 0;
int exclusiveNativeAllocations = 0;
double get percentage => _percentage;
double _percentage = 0.0;
final attributes = <String>{};
// Used for building timeline
int frameId = null;
int parentId = null;
// Either a ProfileCode or a ProfileFunction.
Object get profileData;
String get name;
CallTreeNode(this.children,
[this._count = 0,
this.inclusiveNativeAllocations = 0,
this.exclusiveNativeAllocations = 0]) {}
NodeT getChild(int index);
void sortChildren() {
children.sort((a, b) => b.count - a.count);
children.forEach((NodeT child) => child.sortChildren());
}
void tick(Map sample, {bool exclusive = false}) {
++_count;
if (SampleProfile._isNativeAllocationSample(sample)) {
final allocationSize = sample[SampleProfile._kNativeAllocationSizeBytes];
if (exclusive) {
exclusiveNativeAllocations += allocationSize;
}
inclusiveNativeAllocations += allocationSize;
}
}
}
class CodeCallTreeNode extends CallTreeNode<CodeCallTreeNode>
implements M.CodeCallTreeNode {
final ProfileCode profileCode;
final SampleProfile profile;
Object get profileData => profileCode;
String get name => profileCode.code.name;
final attributes = <String>{};
CodeCallTreeNode(this.profileCode, int count, int inclusiveNativeAllocations,
int exclusiveNativeAllocations)
: profile = profileCode.profile,
super(<CodeCallTreeNode>[], count, inclusiveNativeAllocations,
exclusiveNativeAllocations) {
attributes.addAll(profileCode.attributes);
}
CodeCallTreeNode.fromIndex(this.profile, int tableIndex)
: profileCode = profile.codes[tableIndex],
super(<CodeCallTreeNode>[]);
CodeCallTreeNode getChild(int codeTableIndex) {
final length = children.length;
int i = 0;
while (i < length) {
final child = children[i];
final childTableIndex = child.profileCode.tableIndex;
if (childTableIndex == codeTableIndex) {
return child;
}
if (childTableIndex > codeTableIndex) {
break;
}
++i;
}
final child = CodeCallTreeNode.fromIndex(profile, codeTableIndex);
if (i < length) {
children.insert(i, child);
} else {
children.add(child);
}
return child;
}
}
class CallTree<NodeT extends CallTreeNode> {
final bool inclusive;
final NodeT root;
CallTree(this.inclusive, this.root);
}
class CodeCallTree extends CallTree<CodeCallTreeNode>
implements M.CodeCallTree {
CodeCallTree(bool inclusive, CodeCallTreeNode root) : super(inclusive, root) {
if ((root.inclusiveNativeAllocations != null) &&
(root.inclusiveNativeAllocations != 0)) {
_setCodeMemoryPercentage(null, root);
} else {
_setCodePercentage(null, root);
}
}
CodeCallTree filtered(CallTreeNodeFilter filter) {
final treeFilter = _FilteredCodeCallTreeBuilder(filter, this);
treeFilter.build();
if ((treeFilter.filtered.root.inclusiveNativeAllocations != null) &&
(treeFilter.filtered.root.inclusiveNativeAllocations != 0)) {
_setCodeMemoryPercentage(null, treeFilter.filtered.root);
} else {
_setCodePercentage(null, treeFilter.filtered.root);
}
return treeFilter.filtered;
}
_setCodePercentage(CodeCallTreeNode parent, CodeCallTreeNode node) {
assert(node != null);
var parentPercentage = 1.0;
var parentCount = node.count;
if (parent != null) {
parentPercentage = parent._percentage;
parentCount = parent.count;
}
if (inclusive) {
node._percentage = parentPercentage * (node.count / parentCount);
} else {
node._percentage = (node.count / parentCount);
}
for (var child in node.children) {
_setCodePercentage(node, child);
}
}
_setCodeMemoryPercentage(CodeCallTreeNode parent, CodeCallTreeNode node) {
assert(node != null);
var parentPercentage = 1.0;
var parentMemory = node.inclusiveNativeAllocations;
if (parent != null) {
parentPercentage = parent._percentage;
parentMemory = parent.inclusiveNativeAllocations;
}
if (inclusive) {
node._percentage =
parentPercentage * (node.inclusiveNativeAllocations / parentMemory);
} else {
node._percentage = (node.inclusiveNativeAllocations / parentMemory);
}
for (var child in node.children) {
_setCodeMemoryPercentage(node, child);
}
node.children.sort((a, b) {
return b.inclusiveNativeAllocations - a.inclusiveNativeAllocations;
});
}
_recordCallerAndCalleesInner(
CodeCallTreeNode caller, CodeCallTreeNode callee) {
if (caller != null) {
caller.profileCode._recordCallee(callee.profileCode, callee.count);
callee.profileCode._recordCaller(caller.profileCode, caller.count);
}
for (var child in callee.children) {
_recordCallerAndCalleesInner(callee, child);
}
}
_recordCallerAndCallees() {
for (var child in root.children) {
_recordCallerAndCalleesInner(null, child);
}
}
}
class FunctionCallTreeNodeCode {
final ProfileCode code;
final int ticks;
FunctionCallTreeNodeCode(this.code, this.ticks);
}
class FunctionCallTreeNode extends CallTreeNode<FunctionCallTreeNode>
implements M.FunctionCallTreeNode {
final ProfileFunction profileFunction;
final SampleProfile profile;
final codes = <FunctionCallTreeNodeCode>[];
int _totalCodeTicks = 0;
int get totalCodesTicks => _totalCodeTicks;
String get name => M.getFunctionFullName(profileFunction.function);
Object get profileData => profileFunction;
FunctionCallTreeNode(this.profileFunction, int count,
inclusiveNativeAllocations, exclusiveNativeAllocations)
: profile = profileFunction.profile,
super(<FunctionCallTreeNode>[], count, inclusiveNativeAllocations,
exclusiveNativeAllocations) {
profileFunction._addKindBasedAttributes(attributes);
}
FunctionCallTreeNode.fromIndex(this.profile, int tableIndex)
: profileFunction = profile.functions[tableIndex],
super(<FunctionCallTreeNode>[]);
FunctionCallTreeNode getChild(int functionTableIndex) {
final length = children.length;
int i = 0;
while (i < length) {
final child = children[i];
final childTableIndex = child.profileFunction.tableIndex;
if (childTableIndex == functionTableIndex) {
return child;
}
if (childTableIndex > functionTableIndex) {
break;
}
++i;
}
final child = FunctionCallTreeNode.fromIndex(profile, functionTableIndex);
if (i < length) {
children.insert(i, child);
} else {
children.add(child);
}
return child;
}
}
/// Predicate filter function. Returns true if path from root to [node] and all
/// of [node]'s children should be added to the filtered tree.
typedef CallTreeNodeFilter = bool Function(CallTreeNode node);
/// Build a filter version of a FunctionCallTree.
abstract class _FilteredCallTreeBuilder<NodeT extends CallTreeNode> {
/// The filter.
final CallTreeNodeFilter filter;
/// The unfiltered tree.
final CallTree _unfilteredTree;
/// The filtered tree (construct by [build]).
final CallTree filtered;
final List _currentPath = [];
/// Construct a filtered tree builder using [filter] and [tree].
_FilteredCallTreeBuilder(this.filter, CallTree tree, this.filtered)
: _unfilteredTree = tree;
/// Build the filtered tree.
build() {
assert(filtered != null);
assert(filter != null);
assert(_unfilteredTree != null);
_descend(_unfilteredTree.root);
}
CallTreeNode _findInChildren(CallTreeNode current, CallTreeNode needle) {
for (var child in current.children) {
if ((child as CallTreeNode).profileData == needle.profileData) {
return child;
}
}
return null;
}
NodeT _copyNode(NodeT node);
/// Add all nodes in [_currentPath].
FunctionCallTreeNode _addCurrentPath() {
FunctionCallTreeNode current = filtered.root;
// Tree root is always the first element of the current path.
assert(_unfilteredTree.root == _currentPath[0]);
// Assert that unfiltered tree's root and filtered tree's root are different.
assert(_unfilteredTree.root != current);
for (var i = 1; i < _currentPath.length; i++) {
// toAdd is from the unfiltered tree.
var toAdd = _currentPath[i];
// See if we already have a node for toAdd in the filtered tree.
var child = _findInChildren(current, toAdd);
if (child == null) {
// New node.
child = _copyNode(toAdd);
current.children.add(child);
}
current = child;
}
return current;
}
/// Starting at [current] append [next] and all of [next]'s sub-trees
_appendTree(CallTreeNode current, CallTreeNode next) {
if (next == null) {
return;
}
var child = _findInChildren(current, next);
if (child == null) {
child = _copyNode(next);
current.children.add(child);
}
current = child;
for (var nextChild in next.children) {
_appendTree(current, nextChild);
}
}
/// Add path from root to [child], [child], and all of [child]'s sub-trees
/// to filtered tree.
_addTree(CallTreeNode child) {
var current = _addCurrentPath();
_appendTree(current, child);
}
/// Descend further into the tree. [current] is from the unfiltered tree.
_descend(CallTreeNode current) {
if (current == null) {
return;
}
_currentPath.add(current);
if (filter(current)) {
// Filter matched.
if (current.children.length == 0) {
// Have no children. Add this path.
_addTree(null);
} else {
// Add all child trees.
for (var child in current.children) {
_addTree(child);
}
}
} else {
// Did not match, descend to each child.
for (var child in current.children) {
_descend(child);
}
}
var last = _currentPath.removeLast();
assert(current == last);
}
}
class _FilteredFunctionCallTreeBuilder
extends _FilteredCallTreeBuilder<FunctionCallTreeNode> {
_FilteredFunctionCallTreeBuilder(
CallTreeNodeFilter filter, FunctionCallTree tree)
: super(
filter,
tree,
FunctionCallTree(
tree.inclusive,
FunctionCallTreeNode(
tree.root.profileData,
tree.root.count,
tree.root.inclusiveNativeAllocations,
tree.root.exclusiveNativeAllocations)));
_copyNode(FunctionCallTreeNode node) {
return FunctionCallTreeNode(node.profileData, node.count,
node.inclusiveNativeAllocations, node.exclusiveNativeAllocations);
}
}
class _FilteredCodeCallTreeBuilder
extends _FilteredCallTreeBuilder<CodeCallTreeNode> {
_FilteredCodeCallTreeBuilder(CallTreeNodeFilter filter, CodeCallTree tree)
: super(
filter,
tree,
CodeCallTree(
tree.inclusive,
CodeCallTreeNode(
tree.root.profileData,
tree.root.count,
tree.root.inclusiveNativeAllocations,
tree.root.exclusiveNativeAllocations)));
_copyNode(CodeCallTreeNode node) {
return CodeCallTreeNode(node.profileData, node.count,
node.inclusiveNativeAllocations, node.exclusiveNativeAllocations);
}
}
class FunctionCallTree extends CallTree<FunctionCallTreeNode>
implements M.FunctionCallTree {
FunctionCallTree(bool inclusive, FunctionCallTreeNode root)
: super(inclusive, root) {
if ((root.inclusiveNativeAllocations != null) &&
(root.inclusiveNativeAllocations != 0)) {
_setFunctionMemoryPercentage(null, root);
} else {
_setFunctionPercentage(null, root);
}
}
FunctionCallTree filtered(CallTreeNodeFilter filter) {
final treeFilter = _FilteredFunctionCallTreeBuilder(filter, this);
treeFilter.build();
if ((treeFilter.filtered.root.inclusiveNativeAllocations != null) &&
(treeFilter.filtered.root.inclusiveNativeAllocations != 0)) {
_setFunctionMemoryPercentage(null, treeFilter.filtered.root);
} else {
_setFunctionPercentage(null, treeFilter.filtered.root);
}
return treeFilter.filtered;
}
void _setFunctionPercentage(
FunctionCallTreeNode parent, FunctionCallTreeNode node) {
assert(node != null);
var parentPercentage = 1.0;
var parentCount = node.count;
if (parent != null) {
parentPercentage = parent._percentage;
parentCount = parent.count;
}
if (inclusive) {
node._percentage = parentPercentage * (node.count / parentCount);
} else {
node._percentage = (node.count / parentCount);
}
for (var child in node.children) {
_setFunctionPercentage(node, child);
}
}
void _setFunctionMemoryPercentage(
FunctionCallTreeNode parent, FunctionCallTreeNode node) {
assert(node != null);
var parentPercentage = 1.0;
var parentMemory = node.inclusiveNativeAllocations;
if (parent != null) {
parentPercentage = parent._percentage;
parentMemory = parent.inclusiveNativeAllocations;
}
if (inclusive) {
node._percentage =
parentPercentage * (node.inclusiveNativeAllocations / parentMemory);
} else {
node._percentage = (node.inclusiveNativeAllocations / parentMemory);
}
for (var child in node.children) {
_setFunctionMemoryPercentage(node, child);
}
node.children.sort((a, b) {
return b.inclusiveNativeAllocations - a.inclusiveNativeAllocations;
});
}
_markFunctionCallsInner(
FunctionCallTreeNode caller, FunctionCallTreeNode callee) {
if (caller != null) {
caller.profileFunction
._recordCallee(callee.profileFunction, callee.count);
callee.profileFunction
._recordCaller(caller.profileFunction, caller.count);
}
for (var child in callee.children) {
_markFunctionCallsInner(callee, child);
}
}
_markFunctionCalls() {
for (var child in root.children) {
_markFunctionCallsInner(null, child);
}
}
}
class CodeTick {
final int exclusiveTicks;
final int inclusiveTicks;
CodeTick(this.exclusiveTicks, this.inclusiveTicks);
}
class InlineIntervalTick {
final int startAddress;
int _inclusiveTicks = 0;
int get inclusiveTicks => _inclusiveTicks;
int _exclusiveTicks = 0;
int get exclusiveTicks => _exclusiveTicks;
InlineIntervalTick(this.startAddress);
}
class ProfileCode implements M.ProfileCode {
final int tableIndex;
final SampleProfile profile;
final Code code;
int exclusiveTicks;
int inclusiveTicks;
double normalizedExclusiveTicks = 0.0;
double normalizedInclusiveTicks = 0.0;
final addressTicks = <int, CodeTick>{};
final intervalTicks = <int, InlineIntervalTick>{};
String formattedInclusiveTicks = '';
String formattedExclusiveTicks = '';
String formattedExclusivePercent = '';
final Set<String> attributes = <String>{};
final callers = <ProfileCode, int>{};
final callees = <ProfileCode, int>{};
void _processTicks(List<dynamic> profileTicks) {
assert(profileTicks != null);
assert((profileTicks.length % 3) == 0);
for (var i = 0; i < profileTicks.length; i += 3) {
// TODO(observatory): Address is not necessarily representable as a JS
// integer.
var address = int.parse(profileTicks[i] as String, radix: 16);
var exclusive = profileTicks[i + 1] as int;
var inclusive = profileTicks[i + 2] as int;
var tick = CodeTick(exclusive, inclusive);
addressTicks[address] = tick;
var interval = code.findInterval(address);
if (interval != null) {
var intervalTick = intervalTicks[interval.start];
if (intervalTick == null) {
// Insert into map.
intervalTick = InlineIntervalTick(interval.start);
intervalTicks[interval.start] = intervalTick;
}
intervalTick._inclusiveTicks += inclusive;
intervalTick._exclusiveTicks += exclusive;
}
}
}
void clearTicks() {
exclusiveTicks = 0;
inclusiveTicks = 0;
normalizedExclusiveTicks = 0;
normalizedInclusiveTicks = 0;
formattedInclusiveTicks = '';
formattedExclusiveTicks = '';
formattedExclusivePercent = '';
}
ProfileCode.fromMap(this.tableIndex, this.profile, this.code, Map data) {
assert(profile != null);
assert(code != null);
code.profile = this;
if (code.kind == M.CodeKind.stub) {
attributes.add('stub');
} else if (code.kind == M.CodeKind.dart) {
if (code.isNative) {
attributes.add('ffi'); // Not to be confused with a C function.
} else {
attributes.add('dart');
}
if (code.hasIntrinsic) {
attributes.add('intrinsic');
}
if (code.isOptimized) {
attributes.add('optimized');
} else {
attributes.add('unoptimized');
}
} else if (code.kind == M.CodeKind.tag) {
attributes.add('tag');
} else if (code.kind == M.CodeKind.native) {
attributes.add('native');
}
inclusiveTicks = data['inclusiveTicks'];
exclusiveTicks = data['exclusiveTicks'];
normalizedExclusiveTicks = exclusiveTicks / profile.sampleCount;
normalizedInclusiveTicks = inclusiveTicks / profile.sampleCount;
var ticks = data['ticks'];
if (ticks != null) {
_processTicks(ticks);
}
formattedExclusivePercent =
Utils.formatPercent(exclusiveTicks, profile.sampleCount);
formattedInclusiveTicks =
'${Utils.formatPercent(inclusiveTicks, profile.sampleCount)} '
'($inclusiveTicks)';
formattedExclusiveTicks =
'${Utils.formatPercent(exclusiveTicks, profile.sampleCount)} '
'($exclusiveTicks)';
}
_recordCaller(ProfileCode caller, int count) {
var r = callers[caller];
if (r == null) {
r = 0;
}
callers[caller] = r + count;
}
_recordCallee(ProfileCode callee, int count) {
var r = callees[callee];
if (r == null) {
r = 0;
}
callees[callee] = r + count;
}
void _normalizeTicks() {
normalizedExclusiveTicks = exclusiveTicks / profile.sampleCount;
normalizedInclusiveTicks = inclusiveTicks / profile.sampleCount;
formattedExclusivePercent =
Utils.formatPercent(exclusiveTicks, profile.sampleCount);
formattedInclusiveTicks =
'${Utils.formatPercent(inclusiveTicks, profile.sampleCount)} '
'($inclusiveTicks)';
formattedExclusiveTicks =
'${Utils.formatPercent(exclusiveTicks, profile.sampleCount)} '
'($exclusiveTicks)';
}
void tickTag() {
// All functions *except* those for tags are ticked in the VM while
// generating the CpuSamples response.
if (code.kind != M.CodeKind.tag) {
throw StateError('Only tags should be ticked. '
'Attempted to tick: ${code.name}');
}
++exclusiveTicks;
++inclusiveTicks;
_normalizeTicks();
}
}
class ProfileFunction implements M.ProfileFunction {
final int tableIndex;
final SampleProfile profile;
final ServiceFunction function;
final String resolvedUrl;
final callers = <ProfileFunction, int>{};
final callees = <ProfileFunction, int>{};
// Absolute ticks:
int exclusiveTicks = 0;
int inclusiveTicks = 0;
// Global percentages:
double normalizedExclusiveTicks = 0.0;
double normalizedInclusiveTicks = 0.0;
String formattedInclusiveTicks = '';
String formattedExclusiveTicks = '';
String formattedExclusivePercent = '';
final attributes = <String>{};
void clearTicks() {
exclusiveTicks = 0;
inclusiveTicks = 0;
normalizedExclusiveTicks = 0;
normalizedInclusiveTicks = 0;
formattedInclusiveTicks = '';
formattedExclusiveTicks = '';
formattedExclusivePercent = '';
}
void _addKindBasedAttributes(Set<String> attribs) {
if (function.kind == M.FunctionKind.tag) {
attribs.add('tag');
} else if (function.kind == M.FunctionKind.stub) {
attribs.add('stub');
} else if (function.kind == M.FunctionKind.native) {
attribs.add('native');
} else if (M.isSyntheticFunction(function.kind)) {
attribs.add('synthetic');
} else if (function.isNative) {
attribs.add('ffi'); // Not to be confused with a C function.
} else {
attribs.add('dart');
}
if (function.hasIntrinsic == true) {
attribs.add('intrinsic');
}
}
ProfileFunction.fromMap(
this.tableIndex, this.profile, this.function, Map data)
: resolvedUrl = data['resolvedUrl'] {
function.profile = this;
_addKindBasedAttributes(attributes);
exclusiveTicks = data['exclusiveTicks'];
inclusiveTicks = data['inclusiveTicks'];
_normalizeTicks();
}
_recordCaller(ProfileFunction caller, int count) {
var r = callers[caller];
if (r == null) {
r = 0;
}
callers[caller] = r + count;
}
_recordCallee(ProfileFunction callee, int count) {
var r = callees[callee];
if (r == null) {
r = 0;
}
callees[callee] = r + count;
}
void _normalizeTicks() {
normalizedExclusiveTicks = exclusiveTicks / profile.sampleCount;
normalizedInclusiveTicks = inclusiveTicks / profile.sampleCount;
formattedExclusivePercent =
Utils.formatPercent(exclusiveTicks, profile.sampleCount);
formattedInclusiveTicks =
'${Utils.formatPercent(inclusiveTicks, profile.sampleCount)} '
'($inclusiveTicks)';
formattedExclusiveTicks =
'${Utils.formatPercent(exclusiveTicks, profile.sampleCount)} '
'($exclusiveTicks)';
}
void tickTag() {
// All functions *except* those for functions are ticked in the VM while
// generating the CpuSamples response.
if (function.kind != M.FunctionKind.tag) {
throw StateError('Only tags should be ticked. '
'Attempted to tick: ${function.name}');
}
++exclusiveTicks;
++inclusiveTicks;
_normalizeTicks();
}
}
class SampleProfile extends M.SampleProfile {
Isolate isolate;
int sampleCount = 0;
int samplePeriod = 0;
double sampleRate = 0.0;
int pid = 0;
int maxStackDepth = 0;
double timeSpan = 0.0;
M.SampleProfileTag tagOrder = M.SampleProfileTag.none;
final _functionTagMapping = <String, int>{};
final _codeTagMapping = <String, int>{};
final List samples = [];
final codes = <ProfileCode>[];
bool _builtCodeCalls = false;
final functions = <ProfileFunction>[];
bool _builtFunctionCalls = false;
static const String _kCode = 'code';
static const String _kCodes = '_codes';
static const String _kCodeStack = '_codeStack';
static const String _kFunction = 'function';
static const String _kFunctions = 'functions';
static const String _kNativeAllocationSizeBytes =
'_nativeAllocationSizeBytes';
static const String _kPid = 'pid';
static const String _kSampleCount = 'sampleCount';
static const String _kSamplePeriod = 'samplePeriod';
static const String _kSamples = 'samples';
static const String _kStack = 'stack';
static const String _kMaxStackDepth = 'maxStackDepth';
static const String _kTimeSpan = 'timespan';
static const String _kUserTag = 'userTag';
static const String _kVmTag = 'vmTag';
// Names of special tag types.
static const String kNativeTag = 'Native';
static const String kRootTag = 'Root';
static const String kRuntimeTag = 'Runtime';
static const String kTruncatedTag = '[Truncated]';
// Used to stash trie information in samples for timeline processing.
static const String kTimelineFunctionTrie = 'timelineFunctionTrie';
CodeCallTree loadCodeTree(M.ProfileTreeDirection direction) {
switch (direction) {
case M.ProfileTreeDirection.inclusive:
return _loadCodeTree(true);
case M.ProfileTreeDirection.exclusive:
return _loadCodeTree(false);
}
throw Exception('Unknown ProfileTreeDirection');
}
FunctionCallTree loadFunctionTree(M.ProfileTreeDirection direction) {
switch (direction) {
case M.ProfileTreeDirection.inclusive:
return _loadFunctionTree(true);
case M.ProfileTreeDirection.exclusive:
return _loadFunctionTree(false);
}
throw Exception('Unknown ProfileTreeDirection');
}
buildCodeCallerAndCallees() {
if (_builtCodeCalls) {
return;
}
_builtCodeCalls = true;
var tree = loadCodeTree(M.ProfileTreeDirection.inclusive);
tree._recordCallerAndCallees();
}
buildFunctionCallerAndCallees() {
if (_builtFunctionCalls) {
return;
}
_builtFunctionCalls = true;
var tree = loadFunctionTree(M.ProfileTreeDirection.inclusive);
tree._markFunctionCalls();
}
clear() {
pid = -1;
sampleCount = 0;
samplePeriod = 0;
sampleRate = 0.0;
maxStackDepth = 0;
timeSpan = 0.0;
codes.clear();
functions.clear();
_builtCodeCalls = false;
_builtFunctionCalls = false;
}
Future load(ServiceObjectOwner owner, ServiceMap profile) async {
await loadProgress(owner, profile).drain();
}
static Future sleep([Duration duration = const Duration(microseconds: 0)]) =>
Future.delayed(duration);
Future _loadCommon(ServiceObjectOwner owner, ServiceMap profile,
[StreamController<double> progress]) async {
final watch = Stopwatch();
watch.start();
int count = 0;
var needToUpdate = () {
if (progress == null) {
return false;
}
count++;
if (((count % 256) == 0) && (watch.elapsedMilliseconds > 16)) {
watch.reset();
return true;
}
return false;
};
var signal = (double p) {
if (progress == null) {
return null;
}
progress.add(p);
return sleep();
};
try {
clear();
progress?.add(0.0);
if (profile == null) {
return;
}
if ((owner != null) && (owner is Isolate)) {
isolate = owner;
isolate.resetCachedProfileData();
}
pid = profile[_kPid];
sampleCount = profile[_kSampleCount];
samplePeriod = profile[_kSamplePeriod];
sampleRate = (Duration.microsecondsPerSecond / samplePeriod);
maxStackDepth = profile[_kMaxStackDepth];
timeSpan = profile[_kTimeSpan];
num length = 0;
if (profile.containsKey(_kCodes)) {
length += profile[_kCodes].length;
}
length += profile[_kFunctions].length;
// Process code table.
int tableIndex = 0;
if (profile.containsKey(_kCodes)) {
for (var codeRegion in profile[_kCodes]) {
if (needToUpdate()) {
await signal(count * 100.0 / length);
}
Code code = codeRegion[_kCode];
assert(code != null);
codes.add(ProfileCode.fromMap(tableIndex, this, code, codeRegion));
++tableIndex;
}
}
// Process function table.
tableIndex = 0;
for (var profileFunction in profile[_kFunctions]) {
if (needToUpdate()) {
await signal(count * 100 / length);
}
ServiceFunction function = profileFunction[_kFunction];
assert(function != null);
functions.add(ProfileFunction.fromMap(
tableIndex, this, function, profileFunction));
++tableIndex;
}
if (profile.containsKey(_kCodes)) {
_buildCodeTagMapping();
}
_buildFunctionTagMapping();
samples.addAll(profile[_kSamples]);
} finally {
progress?.close();
}
}
Stream<double> loadProgress(ServiceObjectOwner owner, ServiceMap profile) {
Logger.root.info('sampling counters ${profile['_counters']}');
var progress = StreamController<double>.broadcast();
_loadCommon(owner, profile, progress);
return progress.stream;
}
// Helpers for reading optional flags from a sample.
static bool _isNativeEntryTag(Map sample) =>
sample.containsKey('nativeEntryTag');
static bool _isRuntimeEntryTag(Map sample) =>
sample.containsKey('runtimeEntryTag');
static bool _isTruncated(Map sample) => sample.containsKey('truncated');
static bool _isNativeAllocationSample(Map sample) =>
sample.containsKey('_nativeAllocationSizeBytes');
int _getProfileFunctionTagIndex(String tag) {
if (_functionTagMapping.containsKey(tag)) {
return _functionTagMapping[tag];
}
throw ArgumentError('$tag is not a valid tag!');
}
int _getProfileCodeTagIndex(String tag) {
if (_codeTagMapping.containsKey(tag)) {
return _codeTagMapping[tag];
}
throw ArgumentError('$tag is not a valid tag!');
}
void _buildFunctionTagMapping() {
for (int i = 0; i < functions.length; ++i) {
final function = functions[i].function;
if (function.kind == M.FunctionKind.tag) {
_functionTagMapping[function.name] = i;
}
}
}
void _buildCodeTagMapping() {
for (int i = 0; i < codes.length; ++i) {
final code = codes[i].code;
if (code.kind == M.CodeKind.tag) {
_codeTagMapping[code.name] = i;
}
}
}
void _clearProfileFunctionTagTicks() =>
_functionTagMapping.forEach((String name, int tableIndex) {
// Truncated tag is ticked in the VM, so don't clear it.
if (name != kTruncatedTag) {
functions[tableIndex].clearTicks();
}
});
void _clearProfileCodeTagTicks() =>
_codeTagMapping.forEach((String name, int tableIndex) {
// Truncated tag is ticked in the VM, so don't clear it.
if (name != kTruncatedTag) {
codes[tableIndex].clearTicks();
}
});
NodeT _appendUserTag<NodeT extends CallTreeNode>(
String userTag, NodeT current, Map sample) {
bool isCode = (current is CodeCallTreeNode);
try {
final tableIndex = isCode
? _getProfileCodeTagIndex(userTag)
: _getProfileFunctionTagIndex(userTag);
current = current.getChild(tableIndex);
current.tick(sample);
} catch (_) {/* invalid tag */} finally {
return current;
}
}
NodeT _appendTruncatedTag<NodeT extends CallTreeNode>(
NodeT current, Map sample) {
final isCode = (current is CodeCallTreeNode);
try {
final tableIndex = isCode
? _getProfileCodeTagIndex(kTruncatedTag)
: _getProfileFunctionTagIndex(kTruncatedTag);
current = current.getChild(tableIndex);
current.tick(sample);
// We don't need to tick the tag itself since this is done in the VM for
// the truncated tag, unlike other VM and user tags.
} catch (_) {/* invalid tag */} finally {
return current;
}
}
FunctionCallTreeNode _getFunctionTagAndTick(
String tag, FunctionCallTreeNode current, Map sample) {
try {
final tableIndex = _getProfileFunctionTagIndex(tag);
current = current.getChild(tableIndex);
current.tick(sample);
} catch (_) {/* invalid tag */} finally {
return current;
}
}
CodeCallTreeNode _getCodeTagAndTick(
String tag, CodeCallTreeNode current, Map sample) {
try {
final tableIndex = _getProfileCodeTagIndex(tag);
current = current.getChild(tableIndex);
current.tick(sample);
} catch (_) {/* invalid tag */} finally {
return current;
}
}
NodeT _getTagAndTick<NodeT extends CallTreeNode>(
String tag, NodeT current, Map sample) {
if (current is FunctionCallTreeNode) {
return _getFunctionTagAndTick(tag, current, sample) as NodeT;
} else if (current is CodeCallTreeNode) {
return _getCodeTagAndTick(tag, current, sample) as NodeT;
}
throw ArgumentError('Unexpected tree type: $NodeT');
}
NodeT _appendVMTag<NodeT extends CallTreeNode>(
String vmTag, NodeT current, Map sample) {
if (_isNativeEntryTag(sample)) {
return _getTagAndTick(kNativeTag, current, sample);
} else if (_isRuntimeEntryTag(sample)) {
return _getTagAndTick(kRuntimeTag, current, sample);
} else {
return _getTagAndTick(vmTag, current, sample);
}
}
NodeT _appendSpecificNativeRuntimeEntryVMTag<NodeT extends CallTreeNode>(
NodeT current, Map sample) {
// Only Native and Runtime entries have a second VM tag.
if (!_isNativeEntryTag(sample) && !_isRuntimeEntryTag(sample)) {
return current;
}
final vmTag = sample[_kVmTag];
return _getTagAndTick(vmTag, current, sample);
}
NodeT _appendVMTags<NodeT extends CallTreeNode>(
String vmTag, NodeT current, Map sample) {
current = _appendVMTag(vmTag, current, sample);
current = _appendSpecificNativeRuntimeEntryVMTag(current, sample);
return current;
}
void _tickTags(String vmTag, String userTag, bool tickCode) {
if (tickCode) {
final vmTagIndex = _getProfileCodeTagIndex(vmTag);
codes[vmTagIndex].tickTag();
final userTagIndex = _getProfileCodeTagIndex(userTag);
codes[userTagIndex].tickTag();
} else {
final vmTagIndex = _getProfileFunctionTagIndex(vmTag);
functions[vmTagIndex].tickTag();
final userTagIndex = _getProfileFunctionTagIndex(userTag);
functions[userTagIndex].tickTag();
}
}
NodeT _appendTags<NodeT extends CallTreeNode>(
String vmTag, String userTag, NodeT current, Map sample) {
final tickCode = (current is M.CodeCallTreeNode);
_tickTags(vmTag, userTag, tickCode);
if (tagOrder == M.SampleProfileTag.none) {
return current;
}
// User first.
if (tagOrder == M.SampleProfileTag.userVM ||
tagOrder == M.SampleProfileTag.userOnly) {
current = _appendUserTag(userTag, current, sample);
// Only user.
if (tagOrder == M.SampleProfileTag.userOnly) {
return current;
}
return _appendVMTags(vmTag, current, sample);
}
// VM first.
current = _appendVMTags(vmTag, current, sample);
// Only VM.
if (tagOrder == M.SampleProfileTag.vmOnly) {
return current;
}
return _appendUserTag(userTag, current, sample);
}
NodeT _processFrame<NodeT extends CallTreeNode>(NodeT parent, int sampleIndex,
Map sample, List<int> stack, int frameIndex, bool inclusive) {
final child = parent.getChild(stack[frameIndex]);
child.tick(sample, exclusive: (frameIndex == 0));
return child;
}
FunctionCallTreeNode buildFunctionTrie(bool inclusive) {
final root = FunctionCallTreeNode.fromIndex(
this, _getProfileFunctionTagIndex(kRootTag));
for (int sampleIndex = 0; sampleIndex < samples.length; ++sampleIndex) {
final sample = samples[sampleIndex];
FunctionCallTreeNode current = root;
// Tick the root for each sample as we always visit the root node.
root.tick(sample);
// VM + User tags.
final vmTag = sample[_kVmTag];
final userTag = sample[_kUserTag];
final stack = sample[_kStack].cast<int>();
current = _appendTags(vmTag, userTag, current, sample);
if (inclusive) {
if (_isTruncated(sample)) {
current = _appendTruncatedTag(current, sample);
}
for (int frameIndex = stack.length - 1; frameIndex >= 0; --frameIndex) {
current = _processFrame(
current, sampleIndex, sample, stack, frameIndex, true);
}
// Used by the timeline to find the root of each sample.
sample[kTimelineFunctionTrie] = current;
} else {
for (int frameIndex = 0; frameIndex < stack.length; ++frameIndex) {
current = _processFrame(
current, sampleIndex, sample, stack, frameIndex, false);
}
if (_isTruncated(sample)) {
current = _appendTruncatedTag(current, sample);
}
}
}
return root;
}
CodeCallTreeNode buildCodeTrie(bool inclusive) {
final root =
CodeCallTreeNode.fromIndex(this, _getProfileCodeTagIndex(kRootTag));
for (int sampleIndex = 0; sampleIndex < samples.length; ++sampleIndex) {
final sample = samples[sampleIndex];
CodeCallTreeNode current = root;
// Tick the root for each sample as we always visit the root node.
root.tick(sample);
// VM + User tags.
final vmTag = sample[_kVmTag];
final userTag = sample[_kUserTag];
final stack = sample[_kCodeStack].cast<int>();
current = _appendTags(vmTag, userTag, current, sample);
if (inclusive) {
if (_isTruncated(sample)) {
current = _appendTruncatedTag(current, sample);
}
for (int frameIndex = stack.length - 1; frameIndex >= 0; --frameIndex) {
current = _processFrame(
current, sampleIndex, sample, stack, frameIndex, true);
}
} else {
for (int frameIndex = 0; frameIndex < stack.length; ++frameIndex) {
current = _processFrame(
current, sampleIndex, sample, stack, frameIndex, false);
}
if (_isTruncated(sample)) {
current = _appendTruncatedTag(current, sample);
}
}
}
return root;
}
FunctionCallTree _loadFunctionTree(bool inclusive) {
// Since we're only ticking tag functions when building the trie, we need
// to clean up ticks from previous tree builds.
_clearProfileFunctionTagTicks();
// Read the tree, returns the root node.
final root = buildFunctionTrie(inclusive);
root.sortChildren();
return FunctionCallTree(inclusive, root);
}
CodeCallTree _loadCodeTree(bool inclusive) {
// Since we're only ticking tag code when building the trie, we need
// to clean up ticks from previous tree builds.
_clearProfileCodeTagTicks();
// Read the tree, returns the root node.
final root = buildCodeTrie(inclusive);
root.sortChildren();
return CodeCallTree(inclusive, root);
}
int approximateMillisecondsForCount(count) {
return (count * samplePeriod) ~/ Duration.microsecondsPerMillisecond;
}
double approximateSecondsForCount(count) {
return (count * samplePeriod) / Duration.microsecondsPerSecond;
}
}