From a58d50deb1672fce76aa6456430bf7866ff9d419 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Wed, 29 Apr 2020 13:04:59 -0700 Subject: [PATCH] [flutter_tools] allow pulling performance data from assemble (#55699) --- .../lib/src/build_system/build_system.dart | 14 ++-- .../lib/src/commands/assemble.dart | 29 +++++++++ .../hermetic/assemble_test.dart | 64 +++++++++++++++++++ .../commands/build_aot_test.dart | 4 +- 4 files changed, 102 insertions(+), 9 deletions(-) diff --git a/packages/flutter_tools/lib/src/build_system/build_system.dart b/packages/flutter_tools/lib/src/build_system/build_system.dart index c86da31fdb7..963326f70d9 100644 --- a/packages/flutter_tools/lib/src/build_system/build_system.dart +++ b/packages/flutter_tools/lib/src/build_system/build_system.dart @@ -666,7 +666,7 @@ class _BuildInstance { Future _invokeInternal(Node node) async { final PoolResource resource = await resourcePool.request(); final Stopwatch stopwatch = Stopwatch()..start(); - bool passed = true; + bool succeeded = true; bool skipped = false; // The build system produces a list of aggregate input and output @@ -702,7 +702,7 @@ class _BuildInstance { skipped = true; logger.printTrace('Skipping target: ${node.target.name}'); updateGraph(); - return passed; + return succeeded; } logger.printTrace('${node.target.name}: Starting due to ${node.invalidatedReasons}'); await node.target.build(environment); @@ -741,7 +741,7 @@ class _BuildInstance { // TODO(jonahwilliams): throw specific exception for expected errors to mark // as non-fatal. All others should be fatal. node.target.clearStamp(environment); - passed = false; + succeeded = false; skipped = false; exceptionMeasurements[node.target.name] = ExceptionMeasurement( node.target.name, exception, stackTrace); @@ -752,11 +752,11 @@ class _BuildInstance { target: node.target.name, elapsedMilliseconds: stopwatch.elapsedMilliseconds, skipped: skipped, - passed: passed, + succeeded: succeeded, analyicsName: node.target.analyticsName, ); } - return passed; + return succeeded; } } @@ -781,14 +781,14 @@ class PerformanceMeasurement { @required this.target, @required this.elapsedMilliseconds, @required this.skipped, - @required this.passed, + @required this.succeeded, @required this.analyicsName, }); final int elapsedMilliseconds; final String target; final bool skipped; - final bool passed; + final bool succeeded; final String analyicsName; } diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index c1b2bff085b..ae027b550cd 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -17,6 +17,7 @@ import '../build_system/targets/macos.dart'; import '../build_system/targets/web.dart'; import '../build_system/targets/windows.dart'; import '../cache.dart'; +import '../convert.dart'; import '../globals.dart' as globals; import '../project.dart'; import '../reporting/reporting.dart'; @@ -72,6 +73,10 @@ class AssembleCommand extends FlutterCommand { abbr: 'd', help: 'Allows passing configuration to a target with --define=target=key=value.', ); + argParser.addOption( + 'performance-measurement-file', + help: 'Output individual target performance to a JSON file.' + ); argParser.addMultiOption( 'input', abbr: 'i', @@ -231,6 +236,10 @@ class AssembleCommand extends FlutterCommand { if (argResults.wasParsed('build-outputs')) { writeListIfChanged(result.outputFiles, stringArg('build-outputs')); } + if (argResults.wasParsed('performance-measurement-file')) { + final File outFile = globals.fs.file(argResults['performance-measurement-file']); + writePerformanceData(result.performance.values, outFile); + } if (argResults.wasParsed('depfile')) { final File depfileFile = globals.fs.file(stringArg('depfile')); final Depfile depfile = Depfile(result.inputFiles, result.outputFiles); @@ -262,6 +271,26 @@ void writeListIfChanged(List files, String path) { } } +/// Output performance measurement data in [outFile]. +@visibleForTesting +void writePerformanceData(Iterable measurements, File outFile) { + final Map jsonData = { + 'targets': [ + for (final PerformanceMeasurement measurement in measurements) + { + 'name': measurement.analyicsName, + 'skipped': measurement.skipped, + 'succeeded': measurement.succeeded, + 'elapsedMilliseconds': measurement.elapsedMilliseconds, + } + ] + }; + if (!outFile.parent.existsSync()) { + outFile.parent.createSync(recursive: true); + } + outFile.writeAsStringSync(json.encode(jsonData)); +} + class _CompositeTarget extends Target { _CompositeTarget(this.dependencies); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart index 50ddaf64ab5..91b05950a12 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart @@ -3,11 +3,14 @@ // found in the LICENSE file. import 'package:args/command_runner.dart'; +import 'package:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/assemble.dart'; +import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; import 'package:mockito/mockito.dart'; import 'package:flutter_tools/src/globals.dart' as globals; @@ -97,6 +100,37 @@ void main() { expect(testLogger.errorText, isNot(contains(testStackTrace.toString()))); }); + testbed.test('flutter assemble outputs JSON performance data to provided file', () async { + when(globals.buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig'))) + .thenAnswer((Invocation invocation) async { + return BuildResult(success: true, performance: { + 'hello': PerformanceMeasurement( + target: 'hello', + analyicsName: 'bar', + elapsedMilliseconds: 123, + skipped: false, + succeeded: true, + ), + }); + }); + final CommandRunner commandRunner = createTestCommandRunner(AssembleCommand()); + + await commandRunner.run([ + 'assemble', + '-o Output', + '--performance-measurement-file=out.json', + 'debug_macos_bundle_flutter_assets', + ]); + + expect(globals.fs.file('out.json'), exists); + expect( + json.decode(globals.fs.file('out.json').readAsStringSync()), + containsPair('targets', contains( + containsPair('name', 'bar'), + )), + ); + }); + testbed.test('flutter assemble does not inject engine revision with local-engine', () async { Environment environment; when(globals.artifacts.isLocalEngine).thenReturn(true); @@ -170,6 +204,36 @@ void main() { expect(inputs.readAsStringSync(), contains('fizz')); expect(inputs.lastModifiedSync(), isNot(theDistantPast)); }); + + testWithoutContext('writePerformanceData outputs performance data in JSON form', () { + final List performanceMeasurement = [ + PerformanceMeasurement( + analyicsName: 'foo', + target: 'hidden', + skipped: false, + succeeded: true, + elapsedMilliseconds: 123, + ) + ]; + final FileSystem fileSystem = MemoryFileSystem.test(); + final File outFile = fileSystem.currentDirectory + .childDirectory('foo') + .childFile('out.json'); + + writePerformanceData(performanceMeasurement, outFile); + + expect(outFile, exists); + expect(json.decode(outFile.readAsStringSync()), { + 'targets': [ + { + 'name': 'foo', + 'skipped': false, + 'succeeded': true, + 'elapsedMilliseconds': 123, + }, + ], + }); + }); } class MockBuildSystem extends Mock implements BuildSystem {} diff --git a/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart b/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart index 043c9df6fd4..c58adc03375 100644 --- a/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/build_aot_test.dart @@ -119,14 +119,14 @@ void main() { analyicsName: 'kernel_snapshot', target: 'kernel_snapshot', elapsedMilliseconds: 1000, - passed: true, + succeeded: true, skipped: false, ), 'anything': PerformanceMeasurement( analyicsName: 'android_aot', target: 'anything', elapsedMilliseconds: 1000, - passed: true, + succeeded: true, skipped: false, ), });