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

Add Method Table to CPU profiler #5366

Merged
merged 11 commits into from
Mar 9, 2023
Prev Previous commit
Next Next commit
Add test
  • Loading branch information
kenzieschmoll committed Mar 8, 2023
commit cbf955dad7d9e4fdc383111355825cd8dfb44c1d
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ class CpuStackFrame extends TreeNode<CpuStackFrame>
final String? parentId;

/// The set of ids for all ancesctors of this [CpuStackFrame].
///
///
/// This is late and final, so it will only be created once for performance
/// reasons. This method should only be called when the [CpuStackFrame] is
/// part of a processed CPU profile.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,28 @@ class _MethodGraphState extends State<_MethodGraph> with AutoDisposeMixin {
void initState() {
super.initState();

_selectedGraphNode = widget.methodTableController.selectedNode.value;
_initGraphNodes();
addAutoDisposeListener(widget.methodTableController.selectedNode, () {
setState(() {
_selectedGraphNode = widget.methodTableController.selectedNode.value;
_callers = _selectedGraphNode!.predecessors
.cast<MethodTableGraphNode>()
.toList();
_callees = _selectedGraphNode!.successors
.cast<MethodTableGraphNode>()
.toList();
_initGraphNodes();
});
});
}

void _initGraphNodes() {
_selectedGraphNode = widget.methodTableController.selectedNode.value;
if (_selectedGraphNode == null) {
_callers = <MethodTableGraphNode>[];
_callees = <MethodTableGraphNode>[];
} else {
_callers = _selectedGraphNode!.predecessors
.cast<MethodTableGraphNode>()
.toList();
_callees =
_selectedGraphNode!.successors.cast<MethodTableGraphNode>().toList();
}
}

@override
Widget build(BuildContext context) {
final selectedNode = _selectedGraphNode;
Expand Down Expand Up @@ -185,7 +193,7 @@ class _CallersTable extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlatTable<MethodTableGraphNode>(
keyFactory: (node) => ValueKey(node.id),
keyFactory: (node) => ValueKey('caller-${node.id}'),
data: _callers,
dataKey: 'cpu-profile-method-callers',
columns: columns,
Expand Down Expand Up @@ -221,7 +229,7 @@ class _CalleesTable extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlatTable<MethodTableGraphNode>(
keyFactory: (node) => ValueKey(node.id),
keyFactory: (node) => ValueKey('callee-${node.id}'),
data: _callees,
dataKey: 'cpu-profile-method-callees',
columns: _columns,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,31 +278,31 @@ void main() {
expect(stackFrameB.ancestorIds.toList(), ['id_0', 'cpuProfileRoot']);
expect(
stackFrameC.ancestorIds.toList(),
['id_1','id_0', 'cpuProfileRoot'],
['id_1', 'id_0', 'cpuProfileRoot'],
);
expect(
stackFrameD.ancestorIds.toList(),
['id_1','id_0', 'cpuProfileRoot'],
['id_1', 'id_0', 'cpuProfileRoot'],
);
expect(
stackFrameE.ancestorIds.toList(),
['id_3', 'id_1','id_0', 'cpuProfileRoot'],
['id_3', 'id_1', 'id_0', 'cpuProfileRoot'],
);
expect(
stackFrameF.ancestorIds.toList(),
['id_4', 'id_3', 'id_1','id_0', 'cpuProfileRoot'],
['id_4', 'id_3', 'id_1', 'id_0', 'cpuProfileRoot'],
);
expect(
stackFrameF2.ancestorIds.toList(),
['id_3', 'id_1','id_0', 'cpuProfileRoot'],
['id_3', 'id_1', 'id_0', 'cpuProfileRoot'],
);
expect(
stackFrameC2.ancestorIds.toList(),
['id_5', 'id_4', 'id_3', 'id_1','id_0', 'cpuProfileRoot'],
['id_5', 'id_4', 'id_3', 'id_1', 'id_0', 'cpuProfileRoot'],
);
expect(
stackFrameC3.ancestorIds.toList(),
['id_6', 'id_3', 'id_1','id_0', 'cpuProfileRoot'],
['id_6', 'id_3', 'id_1', 'id_0', 'cpuProfileRoot'],
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,124 @@
// Copyright 2023 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:devtools_app/devtools_app.dart';
import 'package:devtools_app/src/screens/profiler/cpu_profile_transformer.dart';
import 'package:devtools_app/src/screens/profiler/panes/method_table/method_table.dart';
import 'package:devtools_app/src/screens/profiler/panes/method_table/method_table_controller.dart';
import 'package:devtools_app/src/screens/profiler/panes/method_table/method_table_model.dart';
import 'package:devtools_app/src/shared/table/table.dart';
import 'package:devtools_test/devtools_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:vm_service/vm_service.dart';

import '../../test_infra/matchers/_golden_matcher_io.dart';
import '../../test_infra/test_data/cpu_profiler/simple_profile_2.dart';
import '../../test_infra/utils/test_utils.dart';

void main() {
late MethodTableController methodTableController;

setUp(() async {
setCharacterWidthForTables();
setGlobal(ServiceConnectionManager, MockServiceConnectionManager());
setGlobal(IdeTheme, IdeTheme());
final mockScriptManager = MockScriptManager();
when(mockScriptManager.sortedScripts).thenReturn(
ValueNotifier<List<ScriptRef>>([]),
);
when(mockScriptManager.scriptRefForUri(any)).thenReturn(
ScriptRef(
uri: 'package:test/script.dart',
id: 'script.dart',
),
);
setGlobal(ScriptManager, mockScriptManager);
final data = CpuProfileData.parse(simpleCpuProfile2);
await CpuProfileTransformer().processData(data, processId: 'test');
methodTableController = MethodTableController()
..createMethodTableGraph(data);
});

const windowSize = Size(2000.0, 1000.0);

Future<void> pumpMethodTable(WidgetTester tester) async {
await tester.pumpWidget(
wrap(
CpuMethodTable(
methodTableController: methodTableController,
),
),
);
await tester.pumpAndSettle();
expect(find.byType(CpuMethodTable), findsOneWidget);
}

group('$CpuMethodTable', () {
testWidgetsWithWindowSize(
'loads methods with no selection',
windowSize,
(WidgetTester tester) async {
await pumpMethodTable(tester);
expect(find.byType(FlatTable<MethodTableGraphNode>), findsOneWidget);
expect(find.text('Method'), findsOneWidget);
expect(find.text('Total %'), findsOneWidget);
expect(find.text('Self %'), findsOneWidget);
expect(find.text('Caller %'), findsNothing);
expect(find.text('Callee %'), findsNothing);
expect(
find.text('Select a method to view its call graph.'),
findsOneWidget,
);
await expectLater(
find.byType(CpuMethodTable),
matchesDevToolsGolden(
'../../test_infra/goldens/cpu_profiler/method_table_no_selection.png',
),
);
},
);

testWidgetsWithWindowSize(
'loads methods with selection',
windowSize,
(WidgetTester tester) async {
final interestingNode = methodTableController.methods.value.first;
expect(interestingNode.name, 'A');
expect(interestingNode.predecessors, isNotEmpty);
expect(interestingNode.successors, isNotEmpty);

methodTableController.selectedNode.value = interestingNode;
await pumpMethodTable(tester);
expect(find.byType(FlatTable<MethodTableGraphNode>), findsNWidgets(3));
expect(find.text('Method'), findsNWidgets(3));
expect(find.text('Total %'), findsOneWidget);
expect(find.text('Self %'), findsOneWidget);
expect(find.text('Caller %'), findsOneWidget);
expect(find.text('Callee %'), findsOneWidget);
expect(
find.text('Select a method to view its call graph.'),
findsNothing,
);
await expectLater(
find.byType(CpuMethodTable),
matchesDevToolsGolden(
'../../test_infra/goldens/cpu_profiler/method_table_with_selection.png',
),
);

methodTableController.selectedNode.value =
methodTableController.methods.value.last;
await tester.pumpAndSettle();
await expectLater(
find.byType(CpuMethodTable),
matchesDevToolsGolden(
'../../test_infra/goldens/cpu_profiler/method_table_with_selection_2.png',
),
);
},
);
});
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.