• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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