Skip to content

Memory > Diff: UX improvements. #5015

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

Merged
merged 22 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class MemoryTabView extends StatelessWidget {
TabRecord(
tab: DevToolsTab.create(
key: MemoryScreenKeys.dartHeapTableProfileTab,
tabName: 'Profile',
tabName: 'Profile Memory',
gaPrefix: _gaPrefix,
),
tabView: KeepAliveWrapper(
Expand All @@ -74,7 +74,7 @@ class MemoryTabView extends StatelessWidget {
tab: DevToolsTab.create(
key: MemoryScreenKeys.diffTab,
gaPrefix: _gaPrefix,
tabName: 'Diff',
tabName: 'Diff Snapshots',
),
tabView: KeepAliveWrapper(
child: DiffPane(
Expand All @@ -85,7 +85,7 @@ class MemoryTabView extends StatelessWidget {
TabRecord(
tab: DevToolsTab.create(
key: MemoryScreenKeys.dartHeapAllocationTracingTab,
tabName: 'Trace',
tabName: 'Trace Instances',
gaPrefix: _gaPrefix,
),
tabView: const KeepAliveWrapper(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';

import '../../../../shared/analytics/constants.dart' as gac;
import '../../../../shared/common_widgets.dart';
import '../../../../shared/config_specific/launch_url/launch_url.dart';
import '../../../../shared/split.dart';
import '../../../../shared/theme.dart';
import '../../shared/primitives/simple_elements.dart';
Expand Down Expand Up @@ -60,7 +61,13 @@ class _SnapshotItemContent extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Expanded(child: Markdown(data: _snapshotDocumentation)),
Expanded(
child: Markdown(
data: _snapshotDocumentation,
onTapLink: (text, url, title) async =>
await launchUrl(url!),
),
),
const SizedBox(height: denseSpacing),
MoreInfoLink(
url: DocLinks.diff.value,
Expand Down Expand Up @@ -107,22 +114,34 @@ class SnapshotInstanceItemPane extends StatelessWidget {

/// `\v` adds vertical space
const _snapshotDocumentation = '''
Take a **heap snapshot** to view current memory allocation:
1. Understand [Dart memory concepts](https://docs.flutter.dev/development/tools/devtools/memory#basic-memory-concepts).

\v

2. Take a **heap snapshot** to view current memory allocation:

a. In the Snapshots panel, click the ● button

b. If you want to refine results, use the **Filter** button

1. In the Snapshots panel, click the ● button
2. Use the **Filter** button to refine the results
3. Select a class from the snapshot table to view its retaining paths
4. View the path detail by selecting from the **Shortest Retaining Paths…** table
c. Select a class from the snapshot table to view its retaining paths

d. View the path detail by selecting from the **Shortest Retaining Paths…** table

\v

Check the **diff** between snapshots to detect allocation issues:
3. Check the **diff** between snapshots to detect allocation issues:

a. Take a **snapshot**

b. Execute the feature in your application

c. Take a second snapshot

d. While viewing the second snapshot, click **Diff with:** and select the first snapshot from the drop-down menu;
the results area will display the diff

e. Use the **Filter** button to refine the diff results, if needed

1. Take a **snapshot**
2. Execute the feature in your application
3. Take a second snapshot
4. While viewing the second snapshot, click **Diff with:** and select the first snapshot from the drop-down menu;
the results area will display the diff
5. Use the **Filter** button to refine the diff results, if needed
6. Select a class from the diff to view its retaining paths, and see which objects hold the references to those instances
f. Select a class from the diff to view its retaining paths, and see which objects hold the references to those instances
''';
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../../../../../shared/analytics/analytics.dart' as ga;
Expand All @@ -15,6 +16,47 @@ import '../../../../../shared/utils.dart';
import '../../../shared/heap/class_filter.dart';
import '../controller/utils.dart';

class ClassFilterButton extends StatelessWidget {
const ClassFilterButton({
required this.filter,
required this.onChanged,
});

final ValueListenable<ClassFilter> filter;
final Function(ClassFilter) onChanged;

@override
Widget build(BuildContext context) {
return ValueListenableBuilder<ClassFilter>(
valueListenable: filter,
builder: (context, filter, _) {
return FilterButton(
onPressed: () {
ga.select(
gac.memory,
gac.MemoryEvent.diffSnapshotFilter,
);

unawaited(
showDialog(
context: context,
builder: (context) => ClassFilterDialog(
filter,
onChanged: onChanged,
),
),
);
},
isFilterActive: !filter.isEmpty,
message: filter.buttonTooltip,
outlined: false,
);
},
);
}
}

@visibleForTesting
class ClassFilterDialog extends StatefulWidget {
const ClassFilterDialog(
this.classFilter, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,19 @@ enum _SizeType {
}

class _ClassNameColumn extends ColumnData<DiffClassStats>
implements ColumnRenderer<DiffClassStats> {
_ClassNameColumn()
implements
ColumnRenderer<DiffClassStats>,
ColumnHeaderRenderer<DiffClassStats> {
_ClassNameColumn(this.classFilterButton)
: super(
'Class',
titleTooltip: 'Class name',
fixedWidthPx: scaleByFontFactor(180.0),
alignment: ColumnAlignment.left,
);

final Widget classFilterButton;

@override
String? getValue(DiffClassStats classStats) => classStats.heapClass.className;

Expand All @@ -62,6 +66,20 @@ class _ClassNameColumn extends ColumnData<DiffClassStats>
isRowSelected ? theme.selectedTextStyle : theme.regularTextStyle,
);
}

@override
Widget? buildHeader(
BuildContext context,
Widget Function() defaultHeaderRenderer,
) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: defaultHeaderRenderer()),
classFilterButton,
],
);
}
}

class _InstanceColumn extends ColumnData<DiffClassStats> {
Expand Down Expand Up @@ -158,11 +176,35 @@ class _SizeColumn extends ColumnData<DiffClassStats> {
bool get numeric => true;
}

class _ClassesTableDiffColumns {
_ClassesTableDiffColumns(this.classFilterButton);

final Widget classFilterButton;

final retainedSizeDeltaColumn =
_SizeColumn(_DataPart.delta, _SizeType.retained);

late final List<ColumnData<DiffClassStats>> columnList =
<ColumnData<DiffClassStats>>[
_ClassNameColumn(classFilterButton),
_InstanceColumn(_DataPart.created),
_InstanceColumn(_DataPart.deleted),
_InstanceColumn(_DataPart.delta),
_SizeColumn(_DataPart.created, _SizeType.shallow),
_SizeColumn(_DataPart.deleted, _SizeType.shallow),
_SizeColumn(_DataPart.delta, _SizeType.shallow),
_SizeColumn(_DataPart.created, _SizeType.retained),
_SizeColumn(_DataPart.deleted, _SizeType.retained),
retainedSizeDeltaColumn,
];
}

class ClassesTableDiff extends StatelessWidget {
const ClassesTableDiff({
Key? key,
required this.classes,
required this.selection,
required this.classFilterButton,
}) : super(key: key);

final List<DiffClassStats> classes;
Expand Down Expand Up @@ -190,31 +232,16 @@ class ClassesTableDiff extends StatelessWidget {
),
];

static final _retainedSizeDeltaColumn =
_SizeColumn(_DataPart.delta, _SizeType.retained);

static late final List<ColumnData<DiffClassStats>> _columns =
<ColumnData<DiffClassStats>>[
_ClassNameColumn(),
_InstanceColumn(_DataPart.created),
_InstanceColumn(_DataPart.deleted),
_InstanceColumn(_DataPart.delta),
_SizeColumn(_DataPart.created, _SizeType.shallow),
_SizeColumn(_DataPart.deleted, _SizeType.shallow),
_SizeColumn(_DataPart.delta, _SizeType.shallow),
_SizeColumn(_DataPart.created, _SizeType.retained),
_SizeColumn(_DataPart.deleted, _SizeType.retained),
_retainedSizeDeltaColumn,
];
final Widget classFilterButton;

@override
Widget build(BuildContext context) {
// We want to preserve the sorting and sort directions for ClassesTableDiff
// no matter what the data passed to it is.
const dataKey = 'ClassesTableDiff';

final columns = _ClassesTableDiffColumns(classFilterButton);
return FlatTable<DiffClassStats>(
columns: _columns,
columns: columns.columnList,
columnGroups: _columnGroups,
data: classes,
dataKey: dataKey,
Expand All @@ -224,7 +251,7 @@ class ClassesTableDiff extends StatelessWidget {
gac.memory,
gac.MemoryEvent.diffClassDiffSelect,
),
defaultSortColumn: _retainedSizeDeltaColumn,
defaultSortColumn: columns.retainedSizeDeltaColumn,
defaultSortDirection: SortDirection.descending,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ import '../../../shared/primitives/simple_elements.dart';
import '../../../shared/shared_memory_widgets.dart';

class _ClassNameColumn extends ColumnData<SingleClassStats>
implements ColumnRenderer<SingleClassStats> {
_ClassNameColumn()
implements
ColumnRenderer<SingleClassStats>,
ColumnHeaderRenderer<SingleClassStats> {
_ClassNameColumn(this.classFilterButton)
: super(
'Class',
titleTooltip: 'Class name',
fixedWidthPx: scaleByFontFactor(180.0),
alignment: ColumnAlignment.left,
);

final Widget classFilterButton;

@override
String? getValue(SingleClassStats classStats) =>
classStats.heapClass.className;
Expand Down Expand Up @@ -54,6 +58,20 @@ class _ClassNameColumn extends ColumnData<SingleClassStats>
isRowSelected ? theme.selectedTextStyle : theme.regularTextStyle,
);
}

@override
Widget? buildHeader(
BuildContext context,
Widget Function() defaultHeaderRenderer,
) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: defaultHeaderRenderer()),
classFilterButton,
],
);
}
}

class _InstanceColumn extends ColumnData<SingleClassStats>
Expand Down Expand Up @@ -157,14 +175,17 @@ class _RetainedSizeColumn extends ColumnData<SingleClassStats> {
}

class _ClassesTableSingleColumns {
_ClassesTableSingleColumns(this.totalSize);
_ClassesTableSingleColumns(this.totalSize, this.classFilterButton);

/// Is needed to calculate percentage.
final int totalSize;

final Widget classFilterButton;

late final retainedSizeColumn = _RetainedSizeColumn(totalSize);

late final columnList = <ColumnData<SingleClassStats>>[
_ClassNameColumn(),
_ClassNameColumn(classFilterButton),
_InstanceColumn(),
_ShallowSizeColumn(),
retainedSizeColumn,
Expand All @@ -177,27 +198,24 @@ class ClassesTableSingle extends StatelessWidget {
required this.classes,
required this.selection,
required this.totalSize,
required this.classFilterButton,
});

final int totalSize;

final Widget classFilterButton;

final List<SingleClassStats> classes;
final ValueNotifier<SingleClassStats?> selection;

static final _columnStore = <String, _ClassesTableSingleColumns>{};
static _ClassesTableSingleColumns _columns(int totalSize) =>
_columnStore.putIfAbsent(
'$totalSize',
() => _ClassesTableSingleColumns(totalSize),
);

@override
Widget build(BuildContext context) {
// We want to preserve the sorting and sort directions for ClassesTableDiff
// no matter what the data passed to it is.
const dataKey = 'ClassesTableSingle';
final columns = _ClassesTableSingleColumns(totalSize, classFilterButton);
return FlatTable<SingleClassStats>(
columns: _columns(totalSize).columnList,
columns: columns.columnList,
data: classes,
dataKey: dataKey,
keyFactory: (e) => Key(e.heapClass.fullName),
Expand All @@ -206,7 +224,7 @@ class ClassesTableSingle extends StatelessWidget {
gac.memory,
gac.MemoryEvent.diffClassSingleSelect,
),
defaultSortColumn: _columns(totalSize).retainedSizeColumn,
defaultSortColumn: columns.retainedSizeColumn,
defaultSortDirection: SortDirection.descending,
);
}
Expand Down
Loading