Print events and views when first frame is taking awhile during tracing (#98957)
diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart
index 8b35765..c896a2b 100644
--- a/packages/flutter_tools/lib/src/tracing.dart
+++ b/packages/flutter_tools/lib/src/tracing.dart
@@ -51,7 +51,10 @@
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
+ final StringBuffer bufferedEvents = StringBuffer();
+ void Function(String) handleBufferedEvent = bufferedEvents.writeln;
vmService.service.onExtensionEvent.listen((vm_service.Event event) {
+ handleBufferedEvent('${event.extensionKind}: ${event.extensionData}');
if (event.extensionKind == 'Flutter.FirstFrame') {
whenFirstFrameRendered.complete();
}
@@ -69,7 +72,19 @@
}
}
if (!done) {
+ final Timer timer = Timer(const Duration(seconds: 10), () {
+ _logger.printStatus('First frame is taking longer than expected...');
+ _logger.printTrace('Views:');
+ for (final FlutterView view in views) {
+ _logger.printTrace('id: ${view.id} isolate: ${view.uiIsolate?.id}');
+ }
+ _logger.printTrace('Received VM events:');
+ _logger.printTrace(bufferedEvents.toString());
+ // Swap to just printing new events instead of buffering.
+ handleBufferedEvent = _logger.printTrace;
+ });
await whenFirstFrameRendered.future;
+ timer.cancel();
}
// The exception is rethrown, so don't catch only Exceptions.
} catch (exception) { // ignore: avoid_catches_without_on_clauses
diff --git a/packages/flutter_tools/test/general.shard/tracing_test.dart b/packages/flutter_tools/test/general.shard/tracing_test.dart
index 3d654c4..fb885f8 100644
--- a/packages/flutter_tools/test/general.shard/tracing_test.dart
+++ b/packages/flutter_tools/test/general.shard/tracing_test.dart
@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
+
+import 'package:fake_async/fake_async.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/file_system.dart';
@@ -181,6 +184,58 @@
), throwsToolExit(message: 'Engine start event is missing in the timeline'));
});
+ testWithoutContext('prints when first frame is taking a long time', () async {
+ final BufferLogger logger = BufferLogger.test();
+ final FileSystem fileSystem = MemoryFileSystem.test();
+ final Completer<void> completer = Completer<void>();
+ await FakeAsync().run((FakeAsync time) {
+ final Map<String, String> extensionData = <String, String>{
+ 'test': 'data',
+ 'renderedErrorText': 'error text',
+ };
+ final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
+ const FakeVmServiceRequest(
+ method: 'streamListen',
+ args: <String, Object>{
+ 'streamId': vm_service.EventKind.kExtension,
+ }
+ ),
+ const FakeVmServiceRequest(
+ method: kListViewsMethod,
+ jsonResponse: <String, Object>{
+ 'views': <Object>[
+ <String, Object?>{
+ 'id': '1',
+ // No isolate, no views.
+ 'isolate': null,
+ }
+ ],
+ },
+ ),
+ FakeVmServiceStreamResponse(
+ streamId: 'Extension',
+ event: vm_service.Event(
+ timestamp: 0,
+ extensionKind: 'Flutter.Error',
+ extensionData: vm_service.ExtensionData.parse(extensionData),
+ kind: vm_service.EventStreams.kExtension,
+ ),
+ ),
+ ]);
+ unawaited(downloadStartupTrace(fakeVmServiceHost.vmService,
+ output: fileSystem.currentDirectory,
+ logger: logger,
+ ));
+ time.elapse(const Duration(seconds: 11));
+ time.flushMicrotasks();
+ completer.complete();
+ return completer.future;
+ });
+ expect(logger.statusText, contains('First frame is taking longer than expected'));
+ expect(logger.traceText, contains('id: 1 isolate: null'));
+ expect(logger.traceText, contains('Flutter.Error: [ExtensionData {test: data, renderedErrorText: error text}]'));
+ });
+
testWithoutContext('throws tool exit if first frame events are missing', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();