Support --start-paused and retain profile history after disconnect/crash. (#2080)
* Support --start-paused, support review profile history after disconnect/crash.
diff --git a/packages/devtools_app/lib/src/connected_app.dart b/packages/devtools_app/lib/src/connected_app.dart
index 8183406..7a8f52e 100644
--- a/packages/devtools_app/lib/src/connected_app.dart
+++ b/packages/devtools_app/lib/src/connected_app.dart
@@ -4,8 +4,8 @@
import 'dart:async';
+import 'eval_on_dart_library.dart';
import 'globals.dart';
-import 'service_extensions.dart' as extensions;
const flutterLibraryUri = 'package:flutter/src/widgets/binding.dart';
const dartHtmlLibraryUri = 'dart:html';
@@ -27,8 +27,10 @@
bool _isFlutterApp;
- Future<bool> get isProfileBuild async =>
- _isProfileBuild ??= await _connectedToProfileBuild();
+ Future<bool> get isProfileBuild async {
+ _isProfileBuild ??= await _connectedToProfileBuild();
+ return _isProfileBuild;
+ }
bool get isProfileBuildNow {
assert(_isProfileBuild != null);
@@ -60,6 +62,16 @@
bool get isDartCliAppNow => isRunningOnDartVM && !isFlutterAppNow;
Future<bool> _connectedToProfileBuild() async {
+ // If eval works we're not a profile build.
+ final io = EvalOnDartLibrary(['dart:io'], serviceManager.service);
+ final value = await io.eval('Platform.isAndroid', isAlive: null);
+ return !(value?.kind == 'Bool');
+
+ // TODO(terry): Disabled below code, it will hang if flutter run --start-paused
+ // see issue https://github.com/flutter/devtools/issues/2082.
+ // Currently, if eval (see above) doesn't work then we're
+ // running in Profile mode.
+ /*
assert(serviceManager.isServiceAvailable);
// Only flutter apps have profile and non-profile builds. If this changes in
// the future (flutter web), we can modify this check.
@@ -71,6 +83,7 @@
final hasDebugExtension = serviceManager.serviceExtensionManager
.isServiceExtensionAvailable(extensions.debugAllowBanner.extension);
return !hasDebugExtension;
+ */
}
Future<void> initializeValues() async {
diff --git a/packages/devtools_app/lib/src/debugger/debugger_controller.dart b/packages/devtools_app/lib/src/debugger/debugger_controller.dart
index 8f46bb5..5094898 100644
--- a/packages/devtools_app/lib/src/debugger/debugger_controller.dart
+++ b/packages/devtools_app/lib/src/debugger/debugger_controller.dart
@@ -292,18 +292,34 @@
_exceptionPauseMode.value = mode;
}
+ /// Flutter starting with '--start-paused'. All subsequent isolates, after
+ /// the first isolate, are in a pauseStart state too. If _resuming, then
+ /// resume any future isolate created with pause start.
+ Future<Success> _resumeIsolatePauseStart(Event event) {
+ assert(event.kind == EventKind.kPauseStart);
+ assert(_resuming.value);
+
+ final id = event.isolate.id;
+ _log.log('resume() $id');
+ return _service.resume(id);
+ }
+
void _handleDebugEvent(Event event) {
_log.log('event: ${event.kind}');
+ // We're resuming and another isolate has started in a paused state,
+ // resume any pauseState isolates.
+ if (_resuming.value &&
+ event.isolate.id != isolateRef?.id &&
+ event.kind == EventKind.kPauseStart) {
+ _resumeIsolatePauseStart(event);
+ }
+
if (event.isolate.id != isolateRef?.id) return;
_hasFrames.value = event.topFrame != null;
_lastEvent = event;
- // Any event we receive here indicates that any resume/step request has been
- // processed.
- _resuming.value = false;
-
switch (event.kind) {
case EventKind.kResume:
_pause(false);
@@ -314,6 +330,9 @@
case EventKind.kPauseInterrupted:
case EventKind.kPauseException:
case EventKind.kPausePostRequest:
+ // Any event we receive here indicates that any resume/step request has been
+ // processed.
+ _resuming.value = false;
_pause(true, pauseEvent: event);
break;
// TODO(djshuckerow): switch the _breakpoints notifier to a 'ListNotifier'
diff --git a/packages/devtools_app/lib/src/initializer.dart b/packages/devtools_app/lib/src/initializer.dart
index 2202596..6063282 100644
--- a/packages/devtools_app/lib/src/initializer.dart
+++ b/packages/devtools_app/lib/src/initializer.dart
@@ -144,6 +144,12 @@
style: theme.textTheme.bodyText2,
),
const Spacer(),
+ RaisedButton(
+ onPressed: () {
+ overlay.remove();
+ },
+ child: const Text('Review History'),
+ ),
],
),
),
diff --git a/packages/devtools_app/lib/src/inspector/inspector_service.dart b/packages/devtools_app/lib/src/inspector/inspector_service.dart
index 024cd2d..49828d4 100644
--- a/packages/devtools_app/lib/src/inspector/inspector_service.dart
+++ b/packages/devtools_app/lib/src/inspector/inspector_service.dart
@@ -380,8 +380,14 @@
Future<Object> invokeServiceMethodDaemonNoGroup(
String methodName, Map<String, Object> args) async {
+ final callMethodName = 'ext.flutter.inspector.$methodName';
+ if (!serviceManager.serviceExtensionManager
+ .isServiceExtensionAvailable(callMethodName)) {
+ return {'result': null};
+ }
+
final r = await vmService.callServiceExtension(
- 'ext.flutter.inspector.$methodName',
+ callMethodName,
isolateId: inspectorLibrary.isolateId,
args: args,
);
@@ -555,7 +561,13 @@
String methodName,
Map<String, Object> params,
) {
- return _callServiceExtension('ext.flutter.inspector.$methodName', params);
+ final callMethodName = 'ext.flutter.inspector.$methodName';
+ if (!serviceManager.serviceExtensionManager
+ .isServiceExtensionAvailable(callMethodName)) {
+ return null;
+ }
+
+ return _callServiceExtension(callMethodName, params);
}
Future<Object> invokeServiceMethodDaemonInspectorRef(
diff --git a/packages/devtools_app/lib/src/memory/memory_controller.dart b/packages/devtools_app/lib/src/memory/memory_controller.dart
index 8862453..5687d7f 100644
--- a/packages/devtools_app/lib/src/memory/memory_controller.dart
+++ b/packages/devtools_app/lib/src/memory/memory_controller.dart
@@ -510,8 +510,8 @@
}
Future<HeapSnapshotGraph> snapshotMemory() async {
- return await serviceManager.service
- .getHeapSnapshotGraph(serviceManager.isolateManager.selectedIsolate);
+ return await serviceManager?.service
+ ?.getHeapSnapshotGraph(serviceManager?.isolateManager?.selectedIsolate);
}
Future<List<ClassHeapDetailStats>> resetAllocationProfile() =>
@@ -538,7 +538,7 @@
}
bool get isConnectedDeviceAndroid {
- return serviceManager.vm.operatingSystem == 'android';
+ return serviceManager?.vm?.operatingSystem == 'android';
}
Future<List<InstanceSummary>> getInstances(
diff --git a/packages/devtools_app/lib/src/memory/memory_heap_tree_view.dart b/packages/devtools_app/lib/src/memory/memory_heap_tree_view.dart
index 299a914..70cf4f7 100644
--- a/packages/devtools_app/lib/src/memory/memory_heap_tree_view.dart
+++ b/packages/devtools_app/lib/src/memory/memory_heap_tree_view.dart
@@ -13,6 +13,7 @@
import '../auto_dispose_mixin.dart';
import '../common_widgets.dart';
import '../config_specific/logger/logger.dart' as logger;
+import '../globals.dart';
import '../table.dart';
import '../table_data.dart';
import '../theme.dart';
@@ -570,6 +571,9 @@
}
void _snapshot() async {
+ // VmService not available (disconnected/crashed).
+ if (serviceManager.service == null) return;
+
setState(() {
snapshotState = SnapshotStatus.streaming;
});
@@ -577,6 +581,10 @@
final snapshotTimestamp = DateTime.now();
final graph = await controller.snapshotMemory();
+
+ // No snapshot collected, disconnected/crash application.
+ if (graph == null) return;
+
final snapshotCollectionTime = DateTime.now();
setState(() {
diff --git a/packages/devtools_app/lib/src/memory/memory_protocol.dart b/packages/devtools_app/lib/src/memory/memory_protocol.dart
index 4afe2b6..8d28855 100644
--- a/packages/devtools_app/lib/src/memory/memory_protocol.dart
+++ b/packages/devtools_app/lib/src/memory/memory_protocol.dart
@@ -55,6 +55,10 @@
}
void _updateLiveDataPolling([bool paused]) {
+ if (service == null) {
+ // A service of null implies we're disconnected - signal paused.
+ memoryController.pauseLiveFeed();
+ }
paused ??= memoryController.paused.value;
if (paused) {
@@ -62,7 +66,7 @@
_gcStreamListener?.cancel();
} else {
_pollingTimer = Timer(Duration.zero, _pollMemory);
- _gcStreamListener = service.onGCEvent.listen(_handleGCEvent);
+ _gcStreamListener = service?.onGCEvent?.listen(_handleGCEvent);
}
}
diff --git a/packages/devtools_app/lib/src/memory/memory_screen.dart b/packages/devtools_app/lib/src/memory/memory_screen.dart
index 4b79b0c..acf4a7a 100644
--- a/packages/devtools_app/lib/src/memory/memory_screen.dart
+++ b/packages/devtools_app/lib/src/memory/memory_screen.dart
@@ -60,7 +60,8 @@
@override
Widget build(BuildContext context) {
- return !serviceManager.connectedApp.isDartWebAppNow
+ final connected = serviceManager?.connectedApp;
+ return connected != null && !connected.isDartWebAppNow
? const MemoryBody()
: const DisabledForWebAppMessage();
}
diff --git a/packages/devtools_app/lib/src/service_manager.dart b/packages/devtools_app/lib/src/service_manager.dart
index 952b7a4..26b1abb 100644
--- a/packages/devtools_app/lib/src/service_manager.dart
+++ b/packages/devtools_app/lib/src/service_manager.dart
@@ -643,7 +643,7 @@
_service,
);
final InstanceRef value = await flutterLibrary.eval(
- 'WidgetsBinding.instance.debugDidSendFirstFrameEvent',
+ 'WidgetsBinding?.instance?.debugDidSendFirstFrameEvent ?? false',
isAlive: null,
);