trace.dart 4.72 KB
Newer Older
1 2 3 4 5
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6
import 'dart:convert';
7
import 'dart:io';
8

9
import '../base/common.dart';
10
import '../base/utils.dart';
11
import '../globals.dart';
12
import '../observatory.dart';
13
import '../runner/flutter_command.dart';
14

15 16 17 18 19
// Names of some of the Timeline events we care about.
const String kFlutterEngineMainEnterEventName = 'FlutterEngineMainEnter';
const String kFrameworkInitEventName = 'Framework initialization';
const String kFirstUsefulFrameEventName = 'Widgets completed first useful frame';

20
class TraceCommand extends FlutterCommand {
21 22 23 24 25 26
  TraceCommand() {
    argParser.addFlag('start', negatable: false, help: 'Start tracing.');
    argParser.addFlag('stop', negatable: false, help: 'Stop tracing.');
    argParser.addOption('out', help: 'Specify the path of the saved trace file.');
    argParser.addOption('duration',
        defaultsTo: '10', abbr: 'd', help: 'Duration in seconds to trace.');
27
    argParser.addOption('debug-port',
28
        defaultsTo: kDefaultObservatoryPort.toString(),
29
        help: 'Local port where the observatory is listening.');
30 31
  }

32
  @override
33
  final String name = 'trace';
34 35

  @override
36
  final String description = 'Start and stop tracing for a running Flutter app.';
37 38

  @override
Devon Carew's avatar
Devon Carew committed
39 40 41 42
  final String usageFooter =
    '\`trace\` called with no arguments will automatically start tracing, delay a set amount of\n'
    'time (controlled by --duration), and stop tracing. To explicitly control tracing, call trace\n'
    'with --start and later with --stop.';
43

44
  @override
45 46
  bool get requiresDevice => true;

47
  @override
48
  Future<int> runInProject() async {
49
    int observatoryPort = int.parse(argResults['debug-port']);
50

51 52 53 54 55 56 57 58 59
    Tracing tracing;

    try {
      tracing = await Tracing.connect(observatoryPort);
    } catch (error) {
      printError('Error connecting to observatory: $error');
      return 1;
    }

60 61 62 63
    if ((!argResults['start'] && !argResults['stop']) ||
        (argResults['start'] && argResults['stop'])) {
      // Setting neither flags or both flags means do both commands and wait
      // duration seconds in between.
64
      await tracing.startTracing();
Ian Hickson's avatar
Ian Hickson committed
65
      await new Future<Null>.delayed(
66
        new Duration(seconds: int.parse(argResults['duration'])),
67
        () => _stopTracing(tracing)
68
      );
69
    } else if (argResults['stop']) {
70
      await _stopTracing(tracing);
71
    } else {
72
      await tracing.startTracing();
73
    }
74

75 76 77
    return 0;
  }

78 79
  Future<Null> _stopTracing(Tracing tracing) async {
    Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline();
80
    File localFile;
81 82 83

    if (argResults['out'] != null) {
      localFile = new File(argResults['out']);
84
    } else {
85
      localFile = getUniqueFile(Directory.current, 'trace', 'json');
86
    }
87 88

    await localFile.writeAsString(JSON.encode(timeline));
89

90
    printStatus('Trace file saved to ${localFile.path}');
91 92
  }
}
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149

class Tracing {
  Tracing(this.observatory);

  static Future<Tracing> connect(int port) {
    return Observatory.connect(port).then((Observatory observatory) => new Tracing(observatory));
  }

  final Observatory observatory;

  Future<Null> startTracing() async {
    await observatory.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
    await observatory.clearVMTimeline();
  }

  /// Stops tracing; optionally wait for first frame.
  Future<Map<String, dynamic>> stopTracingAndDownloadTimeline({
    bool waitForFirstFrame: false
  }) async {
    Response timeline;

    if (!waitForFirstFrame) {
      // Stop tracing immediately and get the timeline
      await observatory.setVMTimelineFlags(<String>[]);
      timeline = await observatory.getVMTimeline();
    } else {
      Completer<Null> whenFirstFrameRendered = new Completer<Null>();

      observatory.onTimelineEvent.listen((Event timelineEvent) {
        List<Map<String, dynamic>> events = timelineEvent['timelineEvents'];
        for (Map<String, dynamic> event in events) {
          if (event['name'] == kFirstUsefulFrameEventName)
            whenFirstFrameRendered.complete();
        }
      });
      await observatory.streamListen('Timeline');

      await whenFirstFrameRendered.future.timeout(
        const Duration(seconds: 10),
        onTimeout: () {
          printError(
            'Timed out waiting for the first frame event. Either the '
            'application failed to start, or the event was missed because '
            '"flutter run" took too long to subscribe to timeline events.'
          );
          return null;
        }
      );

      timeline = await observatory.getVMTimeline();

      await observatory.setVMTimelineFlags(<String>[]);
    }

    return timeline.response;
  }
}