#!/usr/bin/env dart // 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:io'; import 'dart:async'; import 'dart:convert'; const int ITERATIONS = 5; String runWithLoggingSync(List<String> cmd, { bool checked: true, String workingDirectory }) { ProcessResult results = Process.runSync(cmd[0], cmd.getRange(1, cmd.length).toList(), workingDirectory: workingDirectory); if (results.exitCode != 0) { String errorDescription = 'Error code ${results.exitCode} ' 'returned when attempting to run command: ${cmd.join(' ')}'; print(errorDescription); if (results.stderr.length > 0) print('Errors logged: ${results.stderr.trim()}'); if (checked) throw errorDescription; } if (results.stdout.trim().isNotEmpty) print(results.stdout.trim()); return results.stdout; } double timeToFirstFrame(trace) { // TODO(eseidel): Sort! Events are not guarenteed to be in timestamp order. List events = trace['traceEvents']; int firstTimeStamp = events[0]['ts'].toInt(); var firstSwap = events.firstWhere((e) => e['name'] == 'NativeViewGLSurfaceEGL:RealSwapBuffers'); int swapStart = firstSwap['ts'].toInt(); int swapEnd = swapStart + firstSwap['dur'].toInt(); return (swapEnd - firstTimeStamp) / 1000; // microseconds to milliseconds. } Future<double> test(String tracesDir, String projectPath, int runNumber) async { // If we used package:path we could grab the basename of project_path // and include that in the trace_name. String tracePath = "${tracesDir}/trace_$runNumber.json"; runWithLoggingSync([ 'flutter', 'run', '--no-checked', '--trace-startup' ], workingDirectory: projectPath); await new Future.delayed(const Duration(seconds: 2), () => ""); runWithLoggingSync([ 'flutter', 'trace', '--stop', '--out=${tracePath}' ], workingDirectory: projectPath); JsonDecoder decoder = new JsonDecoder(); String contents = await new File(tracePath).readAsString(); Map data = await decoder.convert(contents); return timeToFirstFrame(data); } // package:statistics has slightly nicer ones of these. double mean(List<double> times) { return times.reduce((a,b) => a + b) / times.length; } double median(List<double> times) { times.sort(); return times[times.length ~/ 2]; } main(List<String> args) async { // We could do much more sophisticated things if we used package:args. if (args.length < 1) { print("Usage: profile_startup.dart PROJECT_PATH\n"); print("PROJECT_PATH required."); return 1; } String projectPath = args[0]; String traces_dir = '/tmp'; List<double> times = []; print("Profiling startup using flutter run --trace-startup."); print("Measuring from first trace event to completion of first frame upload."); print("aka NativeViewGLSurfaceEGL:RealSwapBuffers.\n"); print("NOTE: If device is not on/unlocked tracing may fail.\n"); print("$ITERATIONS runs using $projectPath:"); for (var x = 0; x < ITERATIONS; x++) { int runNumber = x + 1; double time = await test(traces_dir, projectPath, runNumber); print(" ${runNumber.toString().padLeft(2)} $time"); times.add(time); } print("mean: ${mean(times)}"); print("median: ${median(times)}"); }