| // Copyright (c) 2013, 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:math' as Math; | |
| import 'package:web/web.dart'; | |
| import '../../../models.dart' as M; | |
| import '../code_ref.dart'; | |
| import '../containers/virtual_tree.dart'; | |
| import '../function_ref.dart'; | |
| import '../helpers/custom_element.dart'; | |
| import '../helpers/element_utils.dart'; | |
| import '../helpers/rendering_scheduler.dart'; | |
| import '../../../utils.dart'; | |
| import '../stack_trace_tree_config.dart' show ProfileTreeMode; | |
| export '../stack_trace_tree_config.dart' show ProfileTreeMode; | |
| class CpuProfileVirtualTreeElement extends CustomElement implements Renderable { | |
| late RenderingScheduler<CpuProfileVirtualTreeElement> _r; | |
| Stream<RenderedEvent<CpuProfileVirtualTreeElement>> get onRendered => | |
| _r.onRendered; | |
| late M.ProfileTreeDirection _direction; | |
| late ProfileTreeMode _mode; | |
| late M.SampleProfileType _type; | |
| M.IsolateRef? _isolate; | |
| late M.SampleProfile _profile; | |
| Iterable<M.CallTreeNodeFilter>? _filters; | |
| M.ProfileTreeDirection get direction => _direction; | |
| ProfileTreeMode get mode => _mode; | |
| M.SampleProfileType get type => _type; | |
| M.IsolateRef? get isolate => _isolate; | |
| M.SampleProfile get profile => _profile; | |
| Iterable<M.CallTreeNodeFilter>? get filters => _filters; | |
| set direction(M.ProfileTreeDirection value) => | |
| _direction = _r.checkAndReact(_direction, value); | |
| set mode(ProfileTreeMode value) => _mode = _r.checkAndReact(_mode, value); | |
| set filters(Iterable<M.CallTreeNodeFilter>? value) { | |
| _filters = new List.unmodifiable(value!); | |
| _r.dirty(); | |
| } | |
| factory CpuProfileVirtualTreeElement( | |
| M.IsolateRef? isolate, | |
| M.SampleProfile profile, { | |
| ProfileTreeMode mode = ProfileTreeMode.function, | |
| M.SampleProfileType type = M.SampleProfileType.cpu, | |
| M.ProfileTreeDirection direction = M.ProfileTreeDirection.exclusive, | |
| RenderingQueue? queue, | |
| }) { | |
| CpuProfileVirtualTreeElement e = new CpuProfileVirtualTreeElement.created(); | |
| e._r = new RenderingScheduler<CpuProfileVirtualTreeElement>( | |
| e, | |
| queue: queue, | |
| ); | |
| e._isolate = isolate; | |
| e._profile = profile; | |
| e._mode = mode; | |
| e._type = type; | |
| e._direction = direction; | |
| return e; | |
| } | |
| CpuProfileVirtualTreeElement.created() | |
| : super.created('cpu-profile-virtual-tree'); | |
| @override | |
| attached() { | |
| super.attached(); | |
| _r.enable(); | |
| } | |
| @override | |
| detached() { | |
| super.detached(); | |
| _r.disable(notify: true); | |
| removeChildren(); | |
| } | |
| VirtualTreeElement? _tree; | |
| void render() { | |
| var tree; | |
| var create; | |
| var update; | |
| var search; | |
| switch (type) { | |
| case M.SampleProfileType.cpu: | |
| create = _createCpuRow; | |
| if (mode == ProfileTreeMode.code) { | |
| update = _updateCpuCodeRow; | |
| search = _searchCode; | |
| tree = _profile.loadCodeTree(_direction); | |
| } else if (mode == ProfileTreeMode.function) { | |
| update = _updateCpuFunctionRow; | |
| search = _searchFunction; | |
| tree = _profile.loadFunctionTree(_direction); | |
| } else { | |
| throw new Exception('Unknown ProfileTreeMode: $mode'); | |
| } | |
| break; | |
| case M.SampleProfileType.memory: | |
| create = _createMemoryRow; | |
| if (mode == ProfileTreeMode.code) { | |
| update = _updateMemoryCodeRow; | |
| search = _searchCode; | |
| tree = _profile.loadCodeTree(_direction); | |
| } else if (mode == ProfileTreeMode.function) { | |
| update = _updateMemoryFunctionRow; | |
| search = _searchFunction; | |
| tree = _profile.loadFunctionTree(_direction); | |
| } else { | |
| throw new Exception('Unknown ProfileTreeMode: $mode'); | |
| } | |
| break; | |
| } | |
| if (filters != null) { | |
| tree = filters!.fold(tree, (dynamic tree, filter) { | |
| return tree?.filtered(filter); | |
| }); | |
| } | |
| if (tree == null) { | |
| children = <HTMLElement>[ | |
| new HTMLHeadingElement.h1()..textContent = 'No Results', | |
| ]; | |
| return; | |
| } | |
| _tree = new VirtualTreeElement( | |
| create, | |
| update, | |
| _getChildren, | |
| items: tree.root.children, | |
| search: search, | |
| queue: _r.queue, | |
| ); | |
| if (tree.root.children.length == 0) { | |
| children = <HTMLElement>[ | |
| new HTMLDivElement() | |
| ..className = 'tree-item' | |
| ..appendChild(HTMLHeadingElement.h1()..textContent = 'No Samples'), | |
| ]; | |
| return; | |
| } else if (tree.root.children.length == 1) { | |
| _tree!.expand(tree.root.children.first, autoExpandSingleChildNodes: true); | |
| } | |
| children = <HTMLElement>[_tree!.element]; | |
| } | |
| static HTMLElement _createCpuRow(toggle) { | |
| return new HTMLDivElement() | |
| ..className = 'tree-item' | |
| ..appendChildren(<HTMLElement>[ | |
| new HTMLSpanElement() | |
| ..className = 'inclusive' | |
| ..title = 'global % on stack', | |
| new HTMLSpanElement() | |
| ..className = 'exclusive' | |
| ..title = 'global % executing', | |
| new HTMLSpanElement()..className = 'lines', | |
| new HTMLButtonElement() | |
| ..className = 'expander' | |
| ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), | |
| new HTMLSpanElement() | |
| ..className = 'percentage' | |
| ..title = 'tree node %', | |
| new HTMLSpanElement()..className = 'name', | |
| ]); | |
| } | |
| static HTMLElement _createMemoryRow(toggle) { | |
| return new HTMLDivElement() | |
| ..className = 'tree-item' | |
| ..appendChildren(<HTMLElement>[ | |
| new HTMLSpanElement() | |
| ..className = 'inclusive' | |
| ..title = 'memory allocated from resulting calls: ', | |
| new HTMLSpanElement() | |
| ..className = 'exclusive' | |
| ..title = 'memory allocated during execution: ', | |
| new HTMLSpanElement()..className = 'lines', | |
| new HTMLButtonElement() | |
| ..className = 'expander' | |
| ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), | |
| new HTMLSpanElement() | |
| ..className = 'percentage' | |
| ..title = 'tree node %', | |
| new HTMLSpanElement()..className = 'name', | |
| ]); | |
| } | |
| static Iterable<M.CallTreeNode> _getChildren(nodeDynamic) { | |
| M.CallTreeNode node = nodeDynamic; | |
| return node.children; | |
| } | |
| static const String _expandedIcon = 'â–¼'; | |
| static const String _collapsedIcon = 'â–º'; | |
| void _updateCpuFunctionRow(HTMLElement element, itemDynamic, int depth) { | |
| M.FunctionCallTreeNode item = itemDynamic; | |
| (element.children.item(0) as HTMLElement).textContent = | |
| Utils.formatPercentNormalized( | |
| item.profileFunction.normalizedInclusiveTicks, | |
| ); | |
| (element.children.item(1) as HTMLElement).textContent = | |
| Utils.formatPercentNormalized( | |
| item.profileFunction.normalizedExclusiveTicks, | |
| ); | |
| _updateLines(element.children.item(2) as HTMLElement, depth); | |
| if (item.children.isNotEmpty) { | |
| (element.children.item(3) as HTMLElement).textContent = | |
| _tree!.isExpanded(item) ? _expandedIcon : _collapsedIcon; | |
| } else { | |
| (element.children.item(3) as HTMLElement).textContent = ''; | |
| } | |
| (element.children.item(4) as HTMLElement).textContent = | |
| Utils.formatPercentNormalized(item.percentage); | |
| final old = element.children.item(5)!; | |
| element.insertBefore( | |
| old, | |
| element.appendChild( | |
| (new FunctionRefElement( | |
| _isolate, | |
| item.profileFunction.function!, | |
| queue: _r.queue, | |
| )..className = 'name').element, | |
| ), | |
| ); | |
| element.removeChild(old); | |
| } | |
| void _updateMemoryFunctionRow(HTMLElement element, itemDynamic, int depth) { | |
| M.FunctionCallTreeNode item = itemDynamic; | |
| (element.children.item(0) as HTMLElement).textContent = Utils.formatSize( | |
| item.inclusiveNativeAllocations, | |
| ); | |
| (element.children.item(0) as HTMLElement).title = | |
| 'memory allocated from resulting calls: ' | |
| '${item.inclusiveNativeAllocations}B'; | |
| (element.children.item(1) as HTMLElement).textContent = Utils.formatSize( | |
| item.exclusiveNativeAllocations, | |
| ); | |
| (element.children.item(1) as HTMLElement).title = | |
| 'memory allocated during execution: ' | |
| '${item.exclusiveNativeAllocations}B'; | |
| _updateLines(element.children.item(2)!, depth); | |
| if (item.children.isNotEmpty) { | |
| (element.children.item(3) as HTMLElement).textContent = | |
| _tree!.isExpanded(item) ? _expandedIcon : _collapsedIcon; | |
| } else { | |
| (element.children.item(3) as HTMLElement).textContent = ''; | |
| } | |
| (element.children.item(4) as HTMLElement).textContent = | |
| Utils.formatPercentNormalized(item.percentage); | |
| final old = element.children.item(5)!; | |
| element.insertBefore( | |
| old, | |
| (new FunctionRefElement( | |
| null, | |
| item.profileFunction.function!, | |
| queue: _r.queue, | |
| )..className = 'name').element, | |
| ); | |
| element.removeChild(old); | |
| } | |
| bool _searchFunction(Pattern pattern, itemDynamic) { | |
| M.FunctionCallTreeNode item = itemDynamic; | |
| return M | |
| .getFunctionFullName(item.profileFunction.function!) | |
| .contains(pattern); | |
| } | |
| void _updateCpuCodeRow(HTMLElement element, itemDynamic, int depth) { | |
| M.CodeCallTreeNode item = itemDynamic; | |
| (element.children.item(0) as HTMLElement).textContent = | |
| Utils.formatPercentNormalized( | |
| item.profileCode.normalizedInclusiveTicks, | |
| ); | |
| (element.children.item(1) as HTMLElement).textContent = | |
| Utils.formatPercentNormalized( | |
| item.profileCode.normalizedExclusiveTicks, | |
| ); | |
| _updateLines(element.children.item(2)!, depth); | |
| if (item.children.isNotEmpty) { | |
| (element.children.item(3) as HTMLElement).textContent = | |
| _tree!.isExpanded(item) ? _expandedIcon : _collapsedIcon; | |
| } else { | |
| (element.children.item(3) as HTMLElement).textContent = ''; | |
| } | |
| (element.children.item(4) as HTMLElement).textContent = | |
| Utils.formatPercentNormalized(item.percentage); | |
| final old = element.children.item(5)!; | |
| element.insertBefore( | |
| old, | |
| element.appendChild( | |
| (new CodeRefElement( | |
| _isolate, | |
| item.profileCode.code!, | |
| queue: _r.queue, | |
| )..className = 'name').element, | |
| ), | |
| ); | |
| element.removeChild(old); | |
| } | |
| void _updateMemoryCodeRow(HTMLElement element, itemDynamic, int depth) { | |
| M.CodeCallTreeNode item = itemDynamic; | |
| (element.children.item(0) as HTMLElement).textContent = Utils.formatSize( | |
| item.inclusiveNativeAllocations, | |
| ); | |
| (element.children.item(0) as HTMLElement).title = | |
| 'memory allocated from resulting calls: ' | |
| '${item.inclusiveNativeAllocations}B'; | |
| (element.children.item(1) as HTMLElement).textContent = Utils.formatSize( | |
| item.exclusiveNativeAllocations, | |
| ); | |
| (element.children.item(1) as HTMLElement).title = | |
| 'memory allocated during execution: ' | |
| '${item.exclusiveNativeAllocations}B'; | |
| _updateLines(element.children.item(2)!, depth); | |
| if (item.children.isNotEmpty) { | |
| (element.children.item(3) as HTMLElement).textContent = | |
| _tree!.isExpanded(item) ? _expandedIcon : _collapsedIcon; | |
| } else { | |
| (element.children.item(3) as HTMLElement).textContent = ''; | |
| } | |
| (element.children.item(4) as HTMLElement).textContent = | |
| Utils.formatPercentNormalized(item.percentage); | |
| final old = element.children.item(5)!; | |
| element.insertBefore( | |
| old, | |
| element.appendChild( | |
| (new CodeRefElement( | |
| null, | |
| item.profileCode.code!, | |
| queue: _r.queue, | |
| )..className = 'name').element, | |
| ), | |
| ); | |
| element.removeChild(old); | |
| } | |
| bool _searchCode(Pattern pattern, itemDynamic) { | |
| M.CodeCallTreeNode item = itemDynamic; | |
| return item.profileCode.code!.name!.contains(pattern); | |
| } | |
| static _updateLines(Element element, int n) { | |
| n = Math.max(0, n); | |
| while (element.children.length > n) { | |
| element.removeChild(element.lastChild!); | |
| } | |
| while (element.children.length < n) { | |
| element.appendChild(HTMLSpanElement()); | |
| } | |
| } | |
| } |