1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6 7import 'base/file_system.dart'; 8import 'base/logger.dart'; 9import 'base/utils.dart'; 10import 'build_info.dart'; 11import 'globals.dart'; 12import 'vmservice.dart'; 13 14// Names of some of the Timeline events we care about. 15const String kFlutterEngineMainEnterEventName = 'FlutterEngineMainEnter'; 16const String kFrameworkInitEventName = 'Framework initialization'; 17const String kFirstFrameBuiltEventName = 'Widgets built first useful frame'; 18const String kFirstFrameRasterizedEventName = 'Rasterized first useful frame'; 19 20class Tracing { 21 Tracing(this.vmService); 22 23 static const String firstUsefulFrameEventName = kFirstFrameRasterizedEventName; 24 25 static Future<Tracing> connect(Uri uri) async { 26 final VMService observatory = await VMService.connect(uri); 27 return Tracing(observatory); 28 } 29 30 final VMService vmService; 31 32 Future<void> startTracing() async { 33 await vmService.vm.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']); 34 await vmService.vm.clearVMTimeline(); 35 } 36 37 /// Stops tracing; optionally wait for first frame. 38 Future<Map<String, dynamic>> stopTracingAndDownloadTimeline({ 39 bool awaitFirstFrame = false, 40 }) async { 41 if (awaitFirstFrame) { 42 final Status status = logger.startProgress( 43 'Waiting for application to render first frame...', 44 timeout: timeoutConfiguration.fastOperation, 45 ); 46 try { 47 final Completer<void> whenFirstFrameRendered = Completer<void>(); 48 (await vmService.onExtensionEvent).listen((ServiceEvent event) { 49 if (event.extensionKind == 'Flutter.FirstFrame') { 50 whenFirstFrameRendered.complete(); 51 } 52 }); 53 bool done = false; 54 for (FlutterView view in vmService.vm.views) { 55 if (await view.uiIsolate.flutterAlreadyPaintedFirstUsefulFrame()) { 56 done = true; 57 break; 58 } 59 } 60 if (!done) 61 await whenFirstFrameRendered.future; 62 } catch (exception) { 63 status.cancel(); 64 rethrow; 65 } 66 status.stop(); 67 } 68 final Map<String, dynamic> timeline = await vmService.vm.getVMTimeline(); 69 await vmService.vm.setVMTimelineFlags(<String>[]); 70 return timeline; 71 } 72} 73 74/// Download the startup trace information from the given observatory client and 75/// store it to build/start_up_info.json. 76Future<void> downloadStartupTrace(VMService observatory, { bool awaitFirstFrame = true }) async { 77 final String traceInfoFilePath = fs.path.join(getBuildDirectory(), 'start_up_info.json'); 78 final File traceInfoFile = fs.file(traceInfoFilePath); 79 80 // Delete old startup data, if any. 81 if (await traceInfoFile.exists()) 82 await traceInfoFile.delete(); 83 84 // Create "build" directory, if missing. 85 if (!(await traceInfoFile.parent.exists())) 86 await traceInfoFile.parent.create(); 87 88 final Tracing tracing = Tracing(observatory); 89 90 final Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline( 91 awaitFirstFrame: awaitFirstFrame, 92 ); 93 94 int extractInstantEventTimestamp(String eventName) { 95 final List<Map<String, dynamic>> events = 96 List<Map<String, dynamic>>.from(timeline['traceEvents']); 97 final Map<String, dynamic> event = events.firstWhere( 98 (Map<String, dynamic> event) => event['name'] == eventName, orElse: () => null, 99 ); 100 return event == null ? null : event['ts']; 101 } 102 103 String message = 'No useful metrics were gathered.'; 104 105 final int engineEnterTimestampMicros = extractInstantEventTimestamp(kFlutterEngineMainEnterEventName); 106 final int frameworkInitTimestampMicros = extractInstantEventTimestamp(kFrameworkInitEventName); 107 108 if (engineEnterTimestampMicros == null) { 109 printTrace('Engine start event is missing in the timeline: $timeline'); 110 throw 'Engine start event is missing in the timeline. Cannot compute startup time.'; 111 } 112 113 final Map<String, dynamic> traceInfo = <String, dynamic>{ 114 'engineEnterTimestampMicros': engineEnterTimestampMicros, 115 }; 116 117 if (frameworkInitTimestampMicros != null) { 118 final int timeToFrameworkInitMicros = frameworkInitTimestampMicros - engineEnterTimestampMicros; 119 traceInfo['timeToFrameworkInitMicros'] = timeToFrameworkInitMicros; 120 message = 'Time to framework init: ${timeToFrameworkInitMicros ~/ 1000}ms.'; 121 } 122 123 if (awaitFirstFrame) { 124 final int firstFrameBuiltTimestampMicros = extractInstantEventTimestamp(kFirstFrameBuiltEventName); 125 final int firstFrameRasterizedTimestampMicros = extractInstantEventTimestamp(kFirstFrameRasterizedEventName); 126 if (firstFrameBuiltTimestampMicros == null || firstFrameRasterizedTimestampMicros == null) { 127 printTrace('First frame events are missing in the timeline: $timeline'); 128 throw 'First frame events are missing in the timeline. Cannot compute startup time.'; 129 } 130 131 // To keep our old benchmarks valid, we'll preserve the 132 // timeToFirstFrameMicros as the firstFrameBuiltTimestampMicros. 133 // Additionally, we add timeToFirstFrameRasterizedMicros for a more accurate 134 // benchmark. 135 traceInfo['timeToFirstFrameRasterizedMicros'] = firstFrameRasterizedTimestampMicros - engineEnterTimestampMicros; 136 final int timeToFirstFrameMicros = firstFrameBuiltTimestampMicros - engineEnterTimestampMicros; 137 traceInfo['timeToFirstFrameMicros'] = timeToFirstFrameMicros; 138 message = 'Time to first frame: ${timeToFirstFrameMicros ~/ 1000}ms.'; 139 if (frameworkInitTimestampMicros != null) 140 traceInfo['timeAfterFrameworkInitMicros'] = firstFrameBuiltTimestampMicros - frameworkInitTimestampMicros; 141 } 142 143 traceInfoFile.writeAsStringSync(toPrettyJson(traceInfo)); 144 145 printStatus(message); 146 printStatus('Saved startup trace info in ${traceInfoFile.path}.'); 147} 148