diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart index 0cc8e36800c..8b357650582 100644 --- a/packages/flutter_tools/lib/src/tracing.dart +++ b/packages/flutter_tools/lib/src/tracing.dart @@ -2,11 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 - import 'dart:async'; -import 'package:meta/meta.dart'; import 'package:vm_service/vm_service.dart' as vm_service; import 'base/common.dart'; @@ -24,8 +21,8 @@ const String kFirstFrameRasterizedEventName = 'Rasterized first useful frame'; class Tracing { Tracing({ - @required this.vmService, - @required Logger logger, + required this.vmService, + required Logger logger, }) : _logger = logger; static const String firstUsefulFrameEventName = kFirstFrameRasterizedEventName; @@ -39,7 +36,7 @@ class Tracing { } /// Stops tracing; optionally wait for first frame. - Future> stopTracingAndDownloadTimeline({ + Future> stopTracingAndDownloadTimeline({ bool awaitFirstFrame = false, }) async { if (awaitFirstFrame) { @@ -62,9 +59,10 @@ class Tracing { bool done = false; final List views = await vmService.getFlutterViews(); for (final FlutterView view in views) { - if (await vmService + final String? uiIsolateId = view.uiIsolate?.id; + if (uiIsolateId != null && await vmService .flutterAlreadyPaintedFirstUsefulFrame( - isolateId: view.uiIsolate.id, + isolateId: uiIsolateId, )) { done = true; break; @@ -80,14 +78,15 @@ class Tracing { } status.stop(); } - final vm_service.Response timeline = await vmService.getTimeline(); + final vm_service.Response? timeline = await vmService.getTimeline(); await vmService.setTimelineFlags([]); - if (timeline == null) { + final Map? timelineJson = timeline?.json; + if (timelineJson == null) { throwToolExit( 'The device disconnected before the timeline could be retrieved.', ); } - return timeline.json; + return timelineJson; } } @@ -95,8 +94,8 @@ class Tracing { /// store it to `$output/start_up_info.json`. Future downloadStartupTrace(FlutterVmService vmService, { bool awaitFirstFrame = true, - @required Logger logger, - @required Directory output, + required Logger logger, + required Directory output, }) async { final File traceInfoFile = output.childFile('start_up_info.json'); @@ -110,33 +109,39 @@ Future downloadStartupTrace(FlutterVmService vmService, { final Tracing tracing = Tracing(vmService: vmService, logger: logger); - final Map timeline = await tracing.stopTracingAndDownloadTimeline( + final Map timeline = await tracing.stopTracingAndDownloadTimeline( awaitFirstFrame: awaitFirstFrame, ); final File traceTimelineFile = output.childFile('start_up_timeline.json'); traceTimelineFile.writeAsStringSync(toPrettyJson(timeline)); - int extractInstantEventTimestamp(String eventName) { - final List> events = - List>.from(timeline['traceEvents'] as List); - final Map event = events.firstWhere( - (Map event) => event['name'] == eventName, orElse: () => null, - ); - return event == null ? null : (event['ts'] as int); + int? extractInstantEventTimestamp(String eventName) { + final List? traceEvents = timeline['traceEvents'] as List?; + if (traceEvents == null) { + return null; + } + final List> events = List>.from(traceEvents); + Map? matchedEvent; + for (final Map event in events) { + if (event['name'] == eventName) { + matchedEvent = event; + } + } + return matchedEvent == null ? null : (matchedEvent['ts'] as int?); } String message = 'No useful metrics were gathered.'; - final int engineEnterTimestampMicros = extractInstantEventTimestamp(kFlutterEngineMainEnterEventName); - final int frameworkInitTimestampMicros = extractInstantEventTimestamp(kFrameworkInitEventName); + final int? engineEnterTimestampMicros = extractInstantEventTimestamp(kFlutterEngineMainEnterEventName); + final int? frameworkInitTimestampMicros = extractInstantEventTimestamp(kFrameworkInitEventName); if (engineEnterTimestampMicros == null) { logger.printTrace('Engine start event is missing in the timeline: $timeline'); throwToolExit('Engine start event is missing in the timeline. Cannot compute startup time.'); } - final Map traceInfo = { + final Map traceInfo = { 'engineEnterTimestampMicros': engineEnterTimestampMicros, }; @@ -147,8 +152,8 @@ Future downloadStartupTrace(FlutterVmService vmService, { } if (awaitFirstFrame) { - final int firstFrameBuiltTimestampMicros = extractInstantEventTimestamp(kFirstFrameBuiltEventName); - final int firstFrameRasterizedTimestampMicros = extractInstantEventTimestamp(kFirstFrameRasterizedEventName); + final int? firstFrameBuiltTimestampMicros = extractInstantEventTimestamp(kFirstFrameBuiltEventName); + final int? firstFrameRasterizedTimestampMicros = extractInstantEventTimestamp(kFirstFrameRasterizedEventName); if (firstFrameBuiltTimestampMicros == null || firstFrameRasterizedTimestampMicros == null) { logger.printTrace('First frame events are missing in the timeline: $timeline'); throwToolExit('First frame events are missing in the timeline. Cannot compute startup time.'); diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 678a4f9b4eb..127dd197d26 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -402,7 +402,7 @@ class FlutterView { required this.uiIsolate, }); - factory FlutterView.parse(Map json) { + factory FlutterView.parse(Map json) { final Map? rawIsolate = json['isolate'] as Map?; vm_service.IsolateRef? isolate; if (rawIsolate != null) { @@ -822,11 +822,11 @@ class FlutterVmService { // with cleaning up. return []; } - final List? rawViews = response.json?['views'] as List?; + final List? rawViews = response.json?['views'] as List?; final List views = [ if (rawViews != null) - for (final Object rawView in rawViews) - FlutterView.parse(rawView as Map) + for (final Map rawView in rawViews.whereType>()) + FlutterView.parse(rawView) ]; if (views.isNotEmpty || returnEarly) { return views; diff --git a/packages/flutter_tools/test/general.shard/tracing_test.dart b/packages/flutter_tools/test/general.shard/tracing_test.dart index fa0804fee57..3d654c4a419 100644 --- a/packages/flutter_tools/test/general.shard/tracing_test.dart +++ b/packages/flutter_tools/test/general.shard/tracing_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// @dart = 2.8 - import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/base/file_system.dart'; @@ -90,19 +88,19 @@ void main() { vm_service.TimelineEvent.parse({ 'name': kFlutterEngineMainEnterEventName, 'ts': 0, - }), + })!, vm_service.TimelineEvent.parse({ 'name': kFrameworkInitEventName, 'ts': 1, - }), + })!, vm_service.TimelineEvent.parse({ 'name': kFirstFrameBuiltEventName, 'ts': 2, - }), + })!, vm_service.TimelineEvent.parse({ 'name': kFirstFrameRasterizedEventName, 'ts': 3, - }), + })!, ], ).toJson(), ), @@ -197,11 +195,11 @@ void main() { vm_service.TimelineEvent.parse({ 'name': kFlutterEngineMainEnterEventName, 'ts': 0, - }), + })!, vm_service.TimelineEvent.parse({ 'name': kFrameworkInitEventName, 'ts': 1, - }), + })!, ], ).toJson(), ), @@ -232,11 +230,11 @@ void main() { vm_service.TimelineEvent.parse({ 'name': kFlutterEngineMainEnterEventName, 'ts': 0, - }), + })!, vm_service.TimelineEvent.parse({ 'name': kFrameworkInitEventName, 'ts': 1, - }), + })!, ], ).toJson(), ), @@ -277,19 +275,19 @@ void main() { vm_service.TimelineEvent.parse({ 'name': kFlutterEngineMainEnterEventName, 'ts': 0, - }), + })!, vm_service.TimelineEvent.parse({ 'name': kFrameworkInitEventName, 'ts': 1, - }), + })!, vm_service.TimelineEvent.parse({ 'name': kFirstFrameBuiltEventName, 'ts': 2, - }), + })!, vm_service.TimelineEvent.parse({ 'name': kFirstFrameRasterizedEventName, 'ts': 3, - }), + })!, ], ).toJson(), ), @@ -310,25 +308,25 @@ void main() { logger: logger, ); - final Map expectedTimeline = { + final Map expectedTimeline = { 'type': 'Timeline', - 'traceEvents': [ - { + 'traceEvents': [ + { 'name': 'FlutterEngineMainEnter', 'ts': 0, 'type': 'TimelineEvent', }, - { + { 'name': 'Framework initialization', 'ts': 1, 'type': 'TimelineEvent', }, - { + { 'name': 'Widgets built first useful frame', 'ts': 2, 'type': 'TimelineEvent', }, - { + { 'name': 'Rasterized first useful frame', 'ts': 3, 'type': 'TimelineEvent', diff --git a/packages/flutter_tools/test/src/fake_vm_services.dart b/packages/flutter_tools/test/src/fake_vm_services.dart index b84ecf1d8ac..7a40580daf3 100644 --- a/packages/flutter_tools/test/src/fake_vm_services.dart +++ b/packages/flutter_tools/test/src/fake_vm_services.dart @@ -26,14 +26,14 @@ class FakeVmServiceHost { ), httpAddress: httpAddress, wsAddress: wsAddress); _applyStreamListen(); _output.stream.listen((String data) { - final Map request = json.decode(data) as Map; + final Map request = json.decode(data) as Map; if (_requests.isEmpty) { throw Exception('Unexpected request: $request'); } final FakeVmServiceRequest fakeRequest = _requests.removeAt(0) as FakeVmServiceRequest; - expect(request, isA>() - .having((Map request) => request['method'], 'method', fakeRequest.method) - .having((Map request) => request['params'], 'args', fakeRequest.args) + expect(request, isA>() + .having((Map request) => request['method'], 'method', fakeRequest.method) + .having((Map request) => request['params'], 'args', fakeRequest.args) ); if (fakeRequest.close) { unawaited(_vmService.dispose()); @@ -52,6 +52,7 @@ class FakeVmServiceHost { 'id': request['id'], 'error': { 'code': fakeRequest.errorCode, + 'message': 'error', } })); } @@ -108,7 +109,7 @@ class FakeVmServiceRequest implements VmServiceExpectation { /// standard response. final int? errorCode; final Map? args; - final Map? jsonResponse; + final Map? jsonResponse; @override bool get isRequest => true;