Browse garbage collected items. (#5305)
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart index 871d5f4..776c988 100644 --- a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart +++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart
@@ -83,8 +83,8 @@ class DiffHeapClasses extends HeapClasses<DiffClassStats> with FilterableHeapClasses<DiffClassStats> { DiffHeapClasses(_HeapCouple couple) - : before = couple.younger.data, - after = couple.older.data { + : before = couple.older.data, + after = couple.younger.data { classesByName = subtractMaps<HeapClassName, SingleClassStats, SingleClassStats, DiffClassStats>( from: couple.younger.classes.classesByName, @@ -133,7 +133,10 @@ final result = DiffClassStats._( heapClass: heapClass, - total: ObjectSetDiff(before: before?.objects, after: after?.objects), + total: ObjectSetDiff( + setBefore: before?.objects, + setAfter: after?.objects, + ), statsByPath: subtractMaps<ClassOnlyHeapPath, ObjectSetStats, ObjectSetStats, ObjectSetStats>( from: after?.statsByPath, @@ -152,44 +155,42 @@ /// Comparison between two sets of objects. class ObjectSetDiff { - ObjectSetDiff({ObjectSet? before, ObjectSet? after}) { - before ??= ObjectSet.empty; - after ??= ObjectSet.empty; + ObjectSetDiff({ObjectSet? setBefore, ObjectSet? setAfter}) { + setBefore ??= ObjectSet.empty; + setAfter ??= ObjectSet.empty; - final codesBefore = before.objectsByCodes.keys.toSet(); - final codesAfter = after.objectsByCodes.keys.toSet(); + final allCodes = _unionCodes(setBefore, setAfter); - final allCodes = codesBefore.union(codesAfter); for (var code in allCodes) { - final inBefore = codesBefore.contains(code); - final inAfter = codesAfter.contains(code); + final before = setBefore.objectsByCodes[code]; + final after = setAfter.objectsByCodes[code]; - final object = before.objectsByCodes[code] ?? after.objectsByCodes[code]!; - - if (inAfter && inBefore) { - // We assume that state 'after' is what is most interesting for user + if (before != null && after != null) { + // When an object exists both before and after + // the state 'after' is more interesting for user // about the retained size. final excludeFromRetained = - after.objectsExcludedFromRetainedSize.contains(object.code); + setAfter.objectsExcludedFromRetainedSize.contains(after.code); persisted.countInstance( - object, + after, excludeFromRetained: excludeFromRetained, ); continue; } - if (inBefore) { + if (before != null) { final excludeFromRetained = - before.objectsExcludedFromRetainedSize.contains(object.code); - deleted.countInstance(object, excludeFromRetained: excludeFromRetained); - delta.uncountInstance(object, excludeFromRetained: excludeFromRetained); + setBefore.objectsExcludedFromRetainedSize.contains(before.code); + deleted.countInstance(before, excludeFromRetained: excludeFromRetained); + delta.uncountInstance(before, excludeFromRetained: excludeFromRetained); continue; } - if (inAfter) { + + if (after != null) { final excludeFromRetained = - after.objectsExcludedFromRetainedSize.contains(object.code); - created.countInstance(object, excludeFromRetained: excludeFromRetained); - delta.countInstance(object, excludeFromRetained: excludeFromRetained); + setAfter.objectsExcludedFromRetainedSize.contains(after.code); + created.countInstance(after, excludeFromRetained: excludeFromRetained); + delta.countInstance(after, excludeFromRetained: excludeFromRetained); continue; } @@ -204,6 +205,13 @@ ); } + static Set<IdentityHashCode> _unionCodes(ObjectSet set1, ObjectSet set2) { + final codesBefore = set1.objectsByCodes.keys.toSet(); + final codesAfter = set2.objectsByCodes.keys.toSet(); + + return codesBefore.union(codesAfter); + } + final created = ObjectSet(); final deleted = ObjectSet(); final persisted = ObjectSet();
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/sampler.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/sampler.dart index b26905a..99814a7 100644 --- a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/sampler.dart +++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/sampler.dart
@@ -13,7 +13,8 @@ import '../../../shared/primitives/instance_set_button.dart'; class HeapClassSampler extends ClassSampler { - HeapClassSampler(this.objects, this.heap, this.heapClass); + HeapClassSampler(this.objects, this.heap, this.heapClass) + : assert(objects.objectsByCodes.isNotEmpty); final HeapClassName heapClass; final ObjectSet objects; @@ -77,4 +78,17 @@ isolateRef: _mainIsolateRef, ); } + + @override + Future<void> oneStaticToConsole() async { + final heapObject = objects.objectsByCodes.values.first; + final heapSelection = HeapObjectSelection(heap, object: heapObject); + + // drop to console + serviceManager.consoleService.appendBrowsableInstance( + instanceRef: null, + isolateRef: _mainIsolateRef, + heapSelection: heapSelection, + ); + } }
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart index 8d308f7..f8a2720 100644 --- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart +++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
@@ -168,6 +168,7 @@ data.heapClass, isSelected: isRowSelected, gaContext: gac.MemoryAreas.snapshotDiff, + liveItemsEnabled: dataPart != _DataPart.deleted, ); } }
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/instances.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/instances.dart index c474705..6091219 100644 --- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/instances.dart +++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/instances.dart
@@ -23,6 +23,7 @@ HeapClassName heapClass, { required this.isSelected, required this.gaContext, + this.liveItemsEnabled = true, }) : _showMenu = _shouldShowMenu(isSelected, objects), _sampleObtainer = _shouldShowMenu(isSelected, objects) ? HeapClassSampler(objects, heap, heapClass) @@ -37,6 +38,7 @@ final bool isSelected; final MemoryAreas gaContext; final int _count; + final bool liveItemsEnabled; @override Widget build(BuildContext context) { @@ -52,6 +54,7 @@ gaContext: gaContext, sampleObtainer: _sampleObtainer, showMenu: _showMenu, + liveItemsEnabled: liveItemsEnabled, ), ], );
diff --git a/packages/devtools_app/lib/src/screens/memory/shared/primitives/instance_set_button.dart b/packages/devtools_app/lib/src/screens/memory/shared/primitives/instance_set_button.dart index a79558b..fdcb9cb 100644 --- a/packages/devtools_app/lib/src/screens/memory/shared/primitives/instance_set_button.dart +++ b/packages/devtools_app/lib/src/screens/memory/shared/primitives/instance_set_button.dart
@@ -15,6 +15,9 @@ /// Drop one variable, which exists in static set and still alive in app, to console. Future<void> oneLiveStaticToConsole(); + /// Drop one variable from static set, to console. + Future<void> oneStaticToConsole(); + /// Drop all live instances to console. Future<void> manyLiveToConsole(); @@ -31,6 +34,7 @@ required this.sampleObtainer, required this.showMenu, required this.gaContext, + required this.liveItemsEnabled, }) : assert(showMenu == (sampleObtainer != null)), assert(count >= 0); @@ -40,6 +44,9 @@ final TextStyle? textStyle; final MemoryAreas gaContext; + /// If true, menu items that show live objects, will be enabled. + final bool liveItemsEnabled; + @override Widget build(BuildContext context) { final shouldShowMenu = showMenu && count > 0; @@ -53,7 +60,10 @@ if (shouldShowMenu) ContextMenuButton( style: textStyle, - menu: _menu(sampleObtainer!), + menu: _menu( + sampleObtainer!, + liveItemsEnabled: liveItemsEnabled, + ), ), if (!shouldShowMenu) const SizedBox(width: ContextMenuButton.width), ], @@ -61,33 +71,38 @@ } } -class _StoreAsVariableMenu extends StatelessWidget { - const _StoreAsVariableMenu(this.sampleObtainer); +class _StoreAsOneVariableMenu extends StatelessWidget { + const _StoreAsOneVariableMenu( + this.sampleObtainer, { + required this.liveItemsEnabled, + }); final ClassSampler sampleObtainer; + final bool liveItemsEnabled; @override Widget build(BuildContext context) { final enabled = sampleObtainer.isEvalEnabled; - const menuText = 'Store as a console variable'; - final limit = preferences.memory.refLimit.value; + const menuText = 'Store one instance as a console variable'; if (!enabled) { return const MenuItemButton(child: Text(menuText)); } return SubmenuButton( - // TODO(polina-c): change structure and review texts before opening the feature. menuChildren: <Widget>[ MenuItemButton( - onPressed: sampleObtainer.oneLiveStaticToConsole, + onPressed: sampleObtainer.oneStaticToConsole, child: const Text( - 'One instance that exists in snapshot, and is alive in application', + 'Any from snapshot', ), ), MenuItemButton( - onPressed: sampleObtainer.manyLiveToConsole, - child: Text('Up to $limit instances, currently alive in application'), + onPressed: + liveItemsEnabled ? sampleObtainer.oneLiveStaticToConsole : null, + child: const Text( + 'Any from snapshot, not garbage collected', + ), ), ], child: const Text(menuText), @@ -95,6 +110,19 @@ } } -List<Widget> _menu(ClassSampler sampleObtainer) => [ - _StoreAsVariableMenu(sampleObtainer), - ]; +// TODO(polina-c): review structure/texts and add ga, before opening the feature. +List<Widget> _menu( + ClassSampler sampleObtainer, { + required bool liveItemsEnabled, +}) { + final limit = preferences.memory.refLimit.value; + return [ + _StoreAsOneVariableMenu(sampleObtainer, liveItemsEnabled: liveItemsEnabled), + MenuItemButton( + onPressed: sampleObtainer.manyLiveToConsole, + child: Text( + 'Store up to $limit instances, currently alive in application', + ), + ), + ]; +}
diff --git a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart index 1751cd1..fd254d7 100644 --- a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart +++ b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart
@@ -261,6 +261,7 @@ if (treeInitializeComplete || children.isNotEmpty || childCount > 0) { return children.isNotEmpty || childCount > 0; } + if (ref?.heapSelection != null) return true; final diagnostic = ref?.diagnostic; if (diagnostic != null && ((diagnostic.inlineProperties.isNotEmpty) || diagnostic.hasChildren))
diff --git a/packages/devtools_app/lib/src/shared/diagnostics/tree_builder.dart b/packages/devtools_app/lib/src/shared/diagnostics/tree_builder.dart index 552da74..b3b8c24 100644 --- a/packages/devtools_app/lib/src/shared/diagnostics/tree_builder.dart +++ b/packages/devtools_app/lib/src/shared/diagnostics/tree_builder.dart
@@ -163,12 +163,6 @@ ); if (result is Instance) { - if (FeatureFlags.evalAndBrowse && - ref != null && - ref.heapSelection != null) { - addReferencesRoot(variable, ref); - } - await _addChildrenToInstanceVariable( variable: variable, value: result, @@ -406,6 +400,12 @@ variable.addChild(DartObjectNode.text('error: $ex\n$stack')); } + if (FeatureFlags.evalAndBrowse && + ref.heapSelection != null && + ref is! ObjectReferences) { + addReferencesRoot(variable, ref); + } + await _addDiagnosticChildrenIfNeeded( variable, diagnostic,