From 5e70bd13f62db3e7abddd26f6387812ce269573e Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Fri, 4 Dec 2015 11:14:12 -0800 Subject: [PATCH] Add script for profiling startup using flutter trace on Android This will work after the next Engine roll. Currently it requires a fix to tracing which isn't in the released engine. Next steps are to make this work on a bot, and to add iOS support. @abarth @chinmaygarde --- dev/profile_startup.dart | 95 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100755 dev/profile_startup.dart diff --git a/dev/profile_startup.dart b/dev/profile_startup.dart new file mode 100755 index 0000000000..036473edf7 --- /dev/null +++ b/dev/profile_startup.dart @@ -0,0 +1,95 @@ +#!/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 = 3; + +String runWithLoggingSync(List 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 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', + 'start', + '--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 times) { + return times.reduce((a,b) => a + b) / times.length; +} + +double median(List times) { + times.sort(); + return times[times.length ~/ 2]; +} + +main(List 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 times = []; + 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)}"); +}