Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port CPU bottom up table to Flutter. #1659

Merged
merged 4 commits into from
Feb 25, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 32 additions & 24 deletions packages/devtools_app/lib/src/flutter/table.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ class TreeTable<T extends TreeNode<T>> extends StatefulWidget {
Key key,
@required this.columns,
@required this.treeColumn,
@required this.data,
@required this.dataRoots,
kenzieschmoll marked this conversation as resolved.
Show resolved Hide resolved
@required this.keyFactory,
}) : assert(columns.contains(treeColumn)),
assert(columns != null),
assert(keyFactory != null),
assert(data != null),
assert(dataRoots != null),
super(key: key);

/// The columns to show in this table.
Expand All @@ -122,8 +122,8 @@ class TreeTable<T extends TreeNode<T>> extends StatefulWidget {
/// The column of the table to treat as expandable.
final TreeColumnData<T> treeColumn;

/// The tree structure of rows to show in this table.
final T data;
/// The tree structures of rows to show in this table.
final List<T> dataRoots;

/// Factory that creates keys for each row in this table.
final Key Function(T) keyFactory;
Expand All @@ -140,15 +140,16 @@ class _TreeTableState<T extends TreeNode<T>> extends State<TreeTable<T>>
T animatingNode;
List<double> columnWidths;
final Map<T, bool> shouldShowCache = {};
bool rootExpanded = false;
List<bool> rootsExpanded;

/// The number of items to show when animating out the tree table.
static const itemsToShowWhenAnimating = 50;

@override
void initState() {
super.initState();
rootExpanded = widget.data.isExpanded;
rootsExpanded = List.generate(
kenzieschmoll marked this conversation as resolved.
Show resolved Hide resolved
widget.dataRoots.length, (index) => widget.dataRoots[index].isExpanded);
_updateItems();
}

Expand All @@ -168,13 +169,17 @@ class _TreeTableState<T extends TreeNode<T>> extends State<TreeTable<T>>
// TODO(djshuckerow): Handle cases when the root node is expanded and expand
// all is pressed. This will require listening to expansion changes across the
// entire tree.
if (widget.data.isExpanded != rootExpanded) {
if (rootExpanded) {
widget.data.expand();
} else {
widget.data.collapse();
for (int i = 0; i < widget.dataRoots.length; i++) {
final root = widget.dataRoots[i];
final rootExpanded = rootsExpanded[i];
if (root.isExpanded != rootExpanded) {
if (rootExpanded) {
root.expand();
} else {
root.collapse();
}
_onItemPressed(root);
}
_onItemPressed(widget.data);
}
}

Expand All @@ -185,7 +190,7 @@ class _TreeTableState<T extends TreeNode<T>> extends State<TreeTable<T>>

void _updateItems() {
setState(() {
items = _buildFlatList(widget.data);
items = _buildFlatList(widget.dataRoots);
// Leave enough space for the animating children during the animation.
columnWidths = _computeColumnWidths([...items, ...animatingChildren]);
});
Expand All @@ -208,12 +213,12 @@ class _TreeTableState<T extends TreeNode<T>> extends State<TreeTable<T>>
List<T> nodeChildren;
if (node.isExpanded) {
// Compute the children of the expanded node before collapsing.
nodeChildren = _buildFlatList(node);
nodeChildren = _buildFlatList([node]);
node.collapse();
} else {
node.expand();
// Compute the children of the collapsed node after expanding it.
nodeChildren = _buildFlatList(node);
nodeChildren = _buildFlatList([node]);
}
// The first item will be node itself. We will take the next few items
// to generate a convincing expansion animation without creating
Expand All @@ -223,16 +228,17 @@ class _TreeTableState<T extends TreeNode<T>> extends State<TreeTable<T>>
animatingChildrenSet = Set.of(animatingChildren);

// Update the tracked expansion of the root node if needed.
if (node == widget.data) {
rootExpanded = node.isExpanded;
if (widget.dataRoots.contains(node)) {
final rootIndex = widget.dataRoots.indexOf(node);
rootsExpanded[rootIndex] = node.isExpanded;
}
});
_updateItems();
}

List<double> _computeColumnWidths(List<T> flattenedList) {
final root = widget.data;
TreeNode deepest = root;
final firstRoot = widget.dataRoots.first;
TreeNode deepest = firstRoot;
// This will use the width of all rows in the table, even the rows
// that are hidden by nesting.
// We may want to change this to using a flattened list of only
Expand Down Expand Up @@ -272,12 +278,14 @@ class _TreeTableState<T extends TreeNode<T>> extends State<TreeTable<T>>
}
}

List<T> _buildFlatList(T root) {
List<T> _buildFlatList(List<T> roots) {
final flatList = <T>[];
traverse(root, (n) {
flatList.add(n);
return n.isExpanded;
});
for (T root in roots) {
traverse(root, (n) {
flatList.add(n);
return n.isExpanded;
});
}
return flatList;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class CpuProfileTransformer {
/// Process for converting a [CpuStackFrame] into a bottom-up representation of
/// the CPU profile.
class BottomUpProfileTransformer {
List<CpuStackFrame> processData(CpuStackFrame stackFrame) {
static List<CpuStackFrame> processData(CpuStackFrame stackFrame) {
final List<CpuStackFrame> bottomUpRoots = getRoots(stackFrame, null, []);

// Set the bottom up sample counts for each sample.
Expand All @@ -167,7 +167,7 @@ class BottomUpProfileTransformer {
/// will not reflect the bottom up sample counts. These steps will occur later
/// in the bottom-up conversion process.
@visibleForTesting
List<CpuStackFrame> getRoots(
static List<CpuStackFrame> getRoots(
CpuStackFrame node,
CpuStackFrame currentBottomUpRoot,
List<CpuStackFrame> bottomUpRoots,
Expand Down Expand Up @@ -198,7 +198,7 @@ class BottomUpProfileTransformer {
/// bottom-up representation. This is an intermediate step between
/// [getRoots] and [mergeProfileRoots].
@visibleForTesting
void cascadeSampleCounts(CpuStackFrame stackFrame) {
static void cascadeSampleCounts(CpuStackFrame stackFrame) {
stackFrame.inclusiveSampleCount = stackFrame.exclusiveSampleCount;
for (CpuStackFrame child in stackFrame.children) {
child.exclusiveSampleCount = stackFrame.exclusiveSampleCount;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2020 The Chromium Authors. 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:flutter/material.dart';

import '../../flutter/table.dart';
import '../../profiler/cpu_profile_columns.dart';
import '../../profiler/cpu_profile_model.dart';
import '../../table_data.dart';

/// A table of the CPU's bottom-up call tree.
class CpuBottomUpTable extends StatelessWidget {
factory CpuBottomUpTable(List<CpuStackFrame> bottomUpRoots, {Key key}) {
final treeColumn = MethodNameColumn();
final columns = List<ColumnData<CpuStackFrame>>.unmodifiable([
TotalTimeColumn(),
SelfTimeColumn(),
treeColumn,
SourceColumn(),
]);
return CpuBottomUpTable._(key, bottomUpRoots, treeColumn, columns);
}

const CpuBottomUpTable._(
Key key, this.bottomUpRoots, this.treeColumn, this.columns)
: super(key: key);

final TreeColumnData<CpuStackFrame> treeColumn;
final List<ColumnData<CpuStackFrame>> columns;
final List<CpuStackFrame> bottomUpRoots;
@override
Widget build(BuildContext context) {
return TreeTable<CpuStackFrame>(
dataRoots: bottomUpRoots,
columns: columns,
treeColumn: treeColumn,
keyFactory: (frame) => PageStorageKey<String>(frame.id),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import '../../profiler/cpu_profile_columns.dart';
import '../../profiler/cpu_profile_model.dart';
import '../../table_data.dart';

/// A table of the CPU's call tree.
/// A table of the CPU's top-down call tree.
class CpuCallTreeTable extends StatelessWidget {
factory CpuCallTreeTable(CpuProfileData data, {Key key}) {
final treeColumn = MethodNameColumn();
Expand All @@ -21,6 +21,7 @@ class CpuCallTreeTable extends StatelessWidget {
]);
return CpuCallTreeTable._(key, data, treeColumn, columns);
}

const CpuCallTreeTable._(Key key, this.data, this.treeColumn, this.columns)
: super(key: key);

Expand All @@ -31,7 +32,7 @@ class CpuCallTreeTable extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TreeTable<CpuStackFrame>(
data: data.cpuProfileRoot,
dataRoots: [data.cpuProfileRoot],
columns: columns,
treeColumn: treeColumn,
keyFactory: (frame) => PageStorageKey<String>(frame.id),
Expand Down
76 changes: 50 additions & 26 deletions packages/devtools_app/lib/src/profiler/flutter/cpu_profiler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,42 @@ import 'package:flutter/material.dart';

import '../../ui/fake_flutter/_real_flutter.dart';
import '../cpu_profile_model.dart';
import '../cpu_profile_transformer.dart';
import '../cpu_profiler_controller.dart';
import 'cpu_profile_bottom_up.dart';
import 'cpu_profile_call_tree.dart';
import 'cpu_profile_flame_chart.dart';

// TODO(kenz): provide useful UI upon selecting a CPU stack frame.

class CpuProfiler extends StatefulWidget {
const CpuProfiler({@required this.data, @required this.controller});
CpuProfiler({@required this.data, @required this.controller})
: bottomUpRoots = data != null
? BottomUpProfileTransformer.processData(data.cpuProfileRoot)
: [];

final CpuProfileData data;

final CpuProfilerController controller;

final List<CpuStackFrame> bottomUpRoots;

static const Key expandButtonKey = Key('CpuProfiler - Expand Button');
static const Key collapseButtonKey = Key('CpuProfiler - Collapse Button');
static const Key dataProcessingKey = Key('CpuProfiler - data is processing');

// When content of the selected tab from thee tab controller has this key,
// we will not show the expand/collapse buttons.
static const Key _hideExpansionButtons = Key('hide expansion buttons');
static const Key flameChartTab = Key('cpu profile flame chart tab');
static const Key callTreeTab = Key('cpu profile call tree tab');
static const Key bottomUpTab = Key('cpu profile bottom up tab');

// TODO(kenz): the summary tab should be available for UI events in the
// timeline.
static const tabs = [
Tab(key: _hideExpansionButtons, text: 'CPU Flame Chart'),
Tab(text: 'Call Tree'),
Tab(text: 'Bottom Up'),
Tab(key: flameChartTab, text: 'CPU Flame Chart'),
Tab(key: callTreeTab, text: 'Call Tree'),
Tab(key: bottomUpTab, text: 'Bottom Up'),
];

static const emptyCpuProfile = 'No CPU profile data';
Expand Down Expand Up @@ -75,22 +84,10 @@ class _CpuProfilerState extends State<CpuProfiler>
controller: _tabController,
tabs: CpuProfiler.tabs,
),
if (currentTab.key != CpuProfiler._hideExpansionButtons)
if (currentTab.key != CpuProfiler.flameChartTab)
Row(children: [
OutlineButton(
key: CpuProfiler.expandButtonKey,
onPressed: () {
setState(widget.data.cpuProfileRoot.expandCascading);
},
child: const Text('Expand All'),
),
OutlineButton(
key: CpuProfiler.collapseButtonKey,
onPressed: () {
setState(widget.data.cpuProfileRoot.collapseCascading);
},
child: const Text('Collapse All'),
),
_expandAllButton(currentTab),
_collapseAllButton(currentTab),
]),
],
),
Expand Down Expand Up @@ -143,15 +140,42 @@ class _CpuProfilerState extends State<CpuProfiler>
);
},
);

final callTree = CpuCallTreeTable(widget.data);
final bottomUp = CpuBottomUpTable(widget.bottomUpRoots);
// TODO(kenz): make this order configurable.
return [cpuFlameChart, callTree, bottomUp];
}

const bottomUp = Center(
child: Text(
'TODO CPU bottom up',
),
Widget _expandAllButton(Tab currentTab) {
return OutlineButton(
key: CpuProfiler.expandButtonKey,
onPressed: () {
_performOnDataRoots((root) => root.expandCascading(), currentTab);
},
child: const Text('Expand All'),
);
return [cpuFlameChart, callTree, bottomUp];
}

Widget _collapseAllButton(Tab currentTab) {
return OutlineButton(
key: CpuProfiler.collapseButtonKey,
onPressed: () {
_performOnDataRoots((root) => root.collapseCascading(), currentTab);
},
child: const Text('Collapse All'),
);
}

void _performOnDataRoots(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice approach to this problem.

void Function(CpuStackFrame root) callback,
Tab currentTab,
) {
final roots = currentTab.key == CpuProfiler.callTreeTab
? [widget.data.cpuProfileRoot]
: widget.bottomUpRoots;
setState(() {
roots.forEach(callback);
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class HtmlCpuBottomUp extends HtmlCpuProfilerView {
void rebuildView() {
final CpuProfileData data = profileDataProvider();
final List<CpuStackFrame> bottomUpRoots =
BottomUpProfileTransformer().processData(data.cpuProfileRoot);
BottomUpProfileTransformer.processData(data.cpuProfileRoot);
bottomUpTable.model.setRows(bottomUpRoots);
}

Expand Down
Loading