Skip to content

Store an instance to console #5049

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

Closed
wants to merge 13 commits into from
Closed
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 @@ -111,13 +111,10 @@ class DiffHeapClasses extends HeapClasses<DiffClassStats>
/// Comparison between two heaps for a class.
class DiffClassStats extends ClassStats {
DiffClassStats._({
required this.heapClass,
required HeapClassName heapClass,
required this.total,
required StatsByPath objectsByPath,
}) : super(objectsByPath);

@override
final HeapClassName heapClass;
}) : super(objectsByPath, heapClass);
Copy link
Member

Choose a reason for hiding this comment

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

these can both be super parameters in the constructor directly

DiffClassStats._({
    required super.heapClass,
    required super.objectsByPath,
    required this.total,
  })


final ObjectSetDiff total;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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:vm_service/vm_service.dart';

import '../../../../../shared/globals.dart';
import '../../../shared/primitives/class_name.dart';
import '../../../shared/primitives/instance_set_view.dart';

class HeapClassSampler extends ClassSampler {
HeapClassSampler(this.className);

final HeapClassName className;

@override
Future<void> oneVariableToConsole() async {
final isolateRef = serviceManager.isolateManager.mainIsolate.value!;
final isolateId = isolateRef.id!;

// It would be great to find out how to avoid full scan of classes.
final theClass = (await serviceManager.service!.getClassList(isolateId))
.classes!
.firstWhere((ref) => className.matches(ref));

final instances = await serviceManager.service!.getInstances(
isolateId,
theClass.id!,
1,
);

final instance = instances.instances!.first as InstanceRef;

// drop to console
serviceManager.consoleService.appendInstanceRef(
value: instance,
diagnostic: null,
isolateRef: isolateRef,
forceScrollIntoView: true,
);

// TODO (polina-c): convert drafts below to separate commands
// before opening the flag.

// eval object
final response1 = await serviceManager.service!
.evaluate(isolateId, instance.id!, 'toString()');
print('!!!! eval without scope: ' + response1.json!['valueAsString']);

// eval object
final response2 = await serviceManager.service!.evaluate(
isolateId,
instance.id!,
'identityHashCode(this)',
scope: {'this': instance.id!},
);
print('!!!! eval with scope: ' + response2.json!['valueAsString']);
Comment on lines +46 to +57
Copy link
Member

Choose a reason for hiding this comment

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

are these supposed to still be there? both the responses and the prints?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

they are under TODO to move them out

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import '../../../shared/heap/heap.dart';
import '../../../shared/primitives/instance_set_view.dart';
import '../../../shared/primitives/simple_elements.dart';
import '../../../shared/shared_memory_widgets.dart';
import '../controller/sampler.dart';

class _ClassNameColumn extends ColumnData<SingleClassStats>
implements
Expand Down Expand Up @@ -104,13 +105,13 @@ class _InstanceColumn extends ColumnData<SingleClassStats>
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InstanceSetView(
InstanceSetButton(
textStyle:
isRowSelected ? theme.selectedTextStyle : theme.regularTextStyle,
count: getValue(data),
gaContext: gac.MemoryAreas.snapshotSingle,
sampleObtainer:
isRowSelected ? () => throw UnimplementedError() : null,
isRowSelected ? HeapClassSampler(data.heapClass) : null,
showMenu: isRowSelected,
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ class AdaptedHeap {
// native.
if (object.retainedSize == null || className.isSentinel) continue;

final singleHeapClass =
result.putIfAbsent(className, () => SingleClassStats(className));
final singleHeapClass = result.putIfAbsent(
className,
() => SingleClassStats(className),
);
singleHeapClass.countInstance(data, i);
}

Expand Down Expand Up @@ -99,7 +101,7 @@ typedef StatsByPath = Map<ClassOnlyHeapPath, ObjectSetStats>;
typedef StatsByPathEntry = MapEntry<ClassOnlyHeapPath, ObjectSetStats>;

abstract class ClassStats with Sealable {
ClassStats(this.statsByPath);
ClassStats(this.statsByPath, this.heapClass);

final StatsByPath statsByPath;
late final List<StatsByPathEntry> statsByPathEntries = _getEntries();
Expand All @@ -108,17 +110,14 @@ abstract class ClassStats with Sealable {
return statsByPath.entries.toList(growable: false);
}

HeapClassName get heapClass;
final HeapClassName heapClass;
}

/// Statistics for a class about a single heap.
class SingleClassStats extends ClassStats {
SingleClassStats(this.heapClass)
SingleClassStats(HeapClassName heapClass)
: objects = ObjectSet(),
super(<ClassOnlyHeapPath, ObjectSetStats>{});

@override
final HeapClassName heapClass;
super(<ClassOnlyHeapPath, ObjectSetStats>{}, heapClass);

final ObjectSet objects;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ class HeapClassName {
RegExp('^${PackagePrefixes.dartInSnapshot}'),
PackagePrefixes.dart,
);

bool matches(ClassRef ref) {
return HeapClassName.fromClassRef(ref) == this;
}
}

/// Packages that are published by dart.dev or flutter.dev.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
// found in the LICENSE file.

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

import '../../../../shared/analytics/constants.dart';
import '../../../../shared/common_widgets.dart';
import '../../../../shared/primitives/utils.dart';

typedef SampleObtainer = InstanceRef Function();
abstract class ClassSampler {
Future<void> oneVariableToConsole();
}

class InstanceSetView extends StatelessWidget {
const InstanceSetView({
/// A button with label '...' to show near count of instances,
/// with drop down menu to explore the instances.
class InstanceSetButton extends StatelessWidget {
const InstanceSetButton({
super.key,
this.textStyle,
required this.count,
Expand All @@ -22,7 +25,7 @@ class InstanceSetView extends StatelessWidget {
}) : assert(showMenu == (sampleObtainer != null));

final int count;
final SampleObtainer? sampleObtainer;
final ClassSampler? sampleObtainer;
final bool showMenu;
final TextStyle? textStyle;
final MemoryAreas gaContext;
Expand All @@ -38,41 +41,46 @@ class InstanceSetView extends StatelessWidget {
if (showMenu)
ContextMenuButton(
style: textStyle,
menu: _menu(),
menu: _menu(sampleObtainer!),
),
if (!showMenu) const SizedBox(width: ContextMenuButton.width),
],
);
}
}

class _MenuForSubset extends StatelessWidget {
const _MenuForSubset(this.menuText);
class _StoreAsVariableMenu extends StatelessWidget {
const _StoreAsVariableMenu(this.menuText, this.sampleObtainer);

final String menuText;
final ClassSampler sampleObtainer;

@override
Widget build(BuildContext context) {
return SubmenuButton(
menuChildren: <Widget>[
MenuItemButton(
onPressed: () => {},
child: const Text('Fields'),
onPressed: sampleObtainer.oneVariableToConsole,
child: const Text('One instance'),
),
const MenuItemButton(
child: Text('Outgoing references'),
child: Text('First 20 instances'),
),
const MenuItemButton(
child: Text('Incoming references'),
child: Text('All instances'),
),
],
child: Text(menuText),
);
}
}

List<Widget> _menu() => [
const _MenuForSubset('Store one instance as a console variable'),
const _MenuForSubset('Store first 100 instances as a console variable'),
const _MenuForSubset('Store all instances as a console variable'),
List<Widget> _menu(ClassSampler sampleObtainer) => [
_StoreAsVariableMenu(
'Store as a console variable',
sampleObtainer,
),
const MenuItemButton(
child: Text('Browse an instance in console'),
Copy link
Member

Choose a reason for hiding this comment

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

maybe this should mention the work "references" to make it clear what you can browse

),
];
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import FlutterMacOS
import Foundation

import file_selector_macos
import shared_preferences_macos
import shared_preferences_foundation
import url_launcher_macos

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
Expand Down
19 changes: 14 additions & 5 deletions packages/devtools_app/macos/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -311,13 +311,13 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/file_selector_macos/file_selector_macos.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences_macos/shared_preferences_macos.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher_macos/url_launcher_macos.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_selector_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_macos.framework",
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -419,7 +419,10 @@
"$(PROJECT_DIR)/Flutter/ephemeral",
);
INFOPLIST_FILE = Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.google.FlutterEmbedderMacExample.FlutterDesktopExample;
PRODUCT_NAME = "Dart DevTools";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -547,7 +550,10 @@
"$(PROJECT_DIR)/Flutter/ephemeral",
);
INFOPLIST_FILE = Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.google.FlutterEmbedderMacExample.FlutterDesktopExample;
PRODUCT_NAME = "Dart DevTools";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand All @@ -570,7 +576,10 @@
"$(PROJECT_DIR)/Flutter/ephemeral",
);
INFOPLIST_FILE = Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.google.FlutterEmbedderMacExample.FlutterDesktopExample;
PRODUCT_NAME = "Dart DevTools";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down