-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Add a new package: measure #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
d34d5ad
92e44b3
76b5189
d60482e
299363a
cb9736e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ## 0.1.0 | ||
|
|
||
| * Initial release. | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| Copyright 2019 The Chromium Authors. All rights reserved. | ||
|
|
||
| Redistribution and use in source and binary forms, with or without | ||
| modification, are permitted provided that the following conditions are | ||
| met: | ||
|
|
||
| * Redistributions of source code must retain the above copyright | ||
| notice, this list of conditions and the following disclaimer. | ||
| * Redistributions in binary form must reproduce the above | ||
| copyright notice, this list of conditions and the following | ||
| disclaimer in the documentation and/or other materials provided | ||
| with the distribution. | ||
| * Neither the name of Google Inc. nor the names of its | ||
| contributors may be used to endorse or promote products derived | ||
| from this software without specific prior written permission. | ||
|
|
||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| Tools for measuring some performance metrics. | ||
|
|
||
| Currently there's only one tool to measure iOS CPU/GPU usages for Flutter's CI | ||
| tests. | ||
|
|
||
| # Install | ||
|
|
||
| First install [depot_tools][1] (we used its `cipd`). | ||
|
|
||
| Then install [dart](https://dart.dev/get-dart) and make sure that `pub` is on | ||
| your path. | ||
|
|
||
| Finally run: | ||
| ```shell | ||
| pub global activate measure | ||
| ``` | ||
|
|
||
| # Run | ||
| Connect an iPhone, run a Flutter app on it, and | ||
| ```shell | ||
| measure ioscpugpu new | ||
| ``` | ||
|
|
||
| Sample output: | ||
| ``` | ||
| gpu: 12.4%, cpu: 22.525% | ||
| ``` | ||
|
|
||
| For more information, try | ||
| ```shell | ||
| measure help | ||
| measure help ioscpugpu | ||
| measure help ioscpugpu new | ||
| measure help ioscpugpu parse | ||
| ``` | ||
|
|
||
| [1]: https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // Copyright 2019 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 'package:args/command_runner.dart'; | ||
|
|
||
| import 'package:measure/commands/ioscpugpu.dart'; | ||
|
|
||
| void main(List<String> args) { | ||
| final CommandRunner<void> runner = CommandRunner<void>( | ||
| 'measure', | ||
| 'Tools for measuring some performance metrics.', | ||
| ); | ||
| runner.addCommand(IosCpuGpu()); | ||
| runner.run(args); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package: flutter/packages/measure/resources | ||
| description: Binaries and other resources for measuring performance metrics. | ||
| install_mode: copy | ||
| data: | ||
| - dir: resources |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| // Copyright 2019 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 'package:args/command_runner.dart'; | ||
| import 'package:meta/meta.dart'; | ||
|
|
||
| const String kOptionResourcesRoot = 'resources-root'; | ||
| const String kOptionTimeLimitMs = 'time-limit-ms'; | ||
| const String kOptionDevice = 'device'; | ||
| const String kOptionProessName = 'process-name'; | ||
| const String kOptionOutJson = 'out-json'; | ||
| const String kFlagVerbose = 'verbose'; | ||
|
|
||
| const String kDefaultProccessName = 'Runner'; // Flutter app's default process | ||
|
|
||
| abstract class BaseCommand extends Command<void> { | ||
| BaseCommand() { | ||
| argParser.addFlag(kFlagVerbose); | ||
| argParser.addOption( | ||
| kOptionOutJson, | ||
| abbr: 'o', | ||
| help: 'Specifies the json file for outputing result.', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| defaultsTo: 'result.json', | ||
| ); | ||
| argParser.addOption( | ||
| kOptionResourcesRoot, | ||
| abbr: 'r', | ||
| help: 'Specifies the path to download resources', | ||
| defaultsTo: defaultResourcesRoot, | ||
| ); | ||
| } | ||
|
|
||
| static String get defaultResourcesRoot => | ||
| '${Platform.environment['HOME']}/.measure'; | ||
|
|
||
| static Future<void> doEnsureResources( | ||
| String rootPath, | ||
| {bool isVerbose}) async | ||
| { | ||
| final Directory root = | ||
| await Directory(rootPath).create(recursive: true); | ||
| final Directory previous = Directory.current; | ||
| Directory.current = root; | ||
| final File ensureFile = File('ensure_file.txt'); | ||
| ensureFile.writeAsStringSync('flutter/packages/measure/resources latest'); | ||
| if (isVerbose) { | ||
| print('Downloading resources from CIPD...'); | ||
| } | ||
| final ProcessResult result = Process.runSync( | ||
| 'cipd', | ||
| <String>[ | ||
| 'ensure', | ||
| '-ensure-file', | ||
| 'ensure_file.txt', | ||
| '-root', | ||
| '.', | ||
| ], | ||
| ); | ||
| if (result.exitCode != 0) { | ||
| print('cipd ensure stdout:\n${result.stdout}\n'); | ||
| print('cipd ensure stderr:\n${result.stderr}\n'); | ||
| throw Exception('Failed to download the CIPD package.'); | ||
| } | ||
| if (isVerbose) { | ||
| print('Download completes.'); | ||
| } | ||
| Directory.current = previous; | ||
| } | ||
|
|
||
| @protected | ||
| Future<void> ensureResources() async { | ||
| doEnsureResources(resourcesRoot, isVerbose: isVerbose); | ||
| } | ||
|
|
||
| @protected | ||
| void checkRequiredOption(String option) { | ||
| if (argResults[option] == null) { | ||
| throw Exception('Option $option is required.'); | ||
| } | ||
| } | ||
|
|
||
| @protected | ||
| bool get isVerbose => argResults[kFlagVerbose]; | ||
| @protected | ||
| String get outJson => argResults[kOptionOutJson]; | ||
| @protected | ||
| String get resourcesRoot => argResults[kOptionResourcesRoot]; | ||
| } | ||
|
|
||
| abstract class IosCpuGpuSubcommand extends BaseCommand { | ||
| IosCpuGpuSubcommand() { | ||
| argParser.addOption( | ||
| kOptionProessName, | ||
| abbr: 'p', | ||
| help: | ||
| 'Specifies the process name used for filtering the instruments CPU ' | ||
| 'measurements.', | ||
| defaultsTo: kDefaultProccessName, | ||
| ); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| } | ||
|
|
||
| @protected | ||
| String get processName => argResults[kOptionProessName]; | ||
| @protected | ||
| String get traceUtilityPath => '$resourcesRoot/resources/TraceUtility'; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Copyright 2019 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 'package:args/command_runner.dart'; | ||
| import 'package:measure/commands/ioscpugpu/new.dart'; | ||
| import 'package:measure/commands/ioscpugpu/parse.dart'; | ||
|
|
||
| class IosCpuGpu extends Command<void> { | ||
| IosCpuGpu() { | ||
| addSubcommand(IosCpuGpuNew()); | ||
| addSubcommand(IosCpuGpuParse()); | ||
| } | ||
|
|
||
| @override | ||
| String get name => 'ioscpugpu'; | ||
| @override | ||
| String get description => 'Measure the iOS CPU/GPU percentage.'; | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| // Copyright 2019 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'; | ||
| import 'dart:io'; | ||
|
|
||
| import 'package:measure/commands/base.dart'; | ||
| import 'package:measure/parser.dart'; | ||
|
|
||
| class IosCpuGpuNew extends IosCpuGpuSubcommand { | ||
| IosCpuGpuNew() { | ||
| argParser.addOption( | ||
| kOptionTimeLimitMs, | ||
| abbr: 'l', | ||
| defaultsTo: '5000', | ||
| help: 'time limit (in ms) to run instruments for measuring', | ||
| ); | ||
| argParser.addOption( | ||
| kOptionDevice, | ||
| abbr: 'w', | ||
| help: 'device identifier recognizable by instruments ' | ||
| '(e.g., 00008020-000364CE0AF8003A)', | ||
| ); | ||
| } | ||
|
|
||
| @override | ||
| String get name => 'new'; | ||
| @override | ||
| String get description => 'Take a new measurement on the iOS CPU/GPU ' | ||
| 'percentage (of Flutter Runner).'; | ||
|
|
||
| String get _timeLimit => argResults[kOptionTimeLimitMs]; | ||
|
|
||
| String get _templatePath => | ||
| '$resourcesRoot/resources/CpuGpuTemplate.tracetemplate'; | ||
|
|
||
| @override | ||
| Future<void> run() async { | ||
| _checkDevice(); | ||
| await ensureResources(); | ||
|
|
||
| print('Running instruments on iOS device $_device for ${_timeLimit}ms'); | ||
|
|
||
| final List<String> args = <String>[ | ||
| '-l', _timeLimit, | ||
| '-t', _templatePath, | ||
| '-w', _device, | ||
| ]; | ||
| if (isVerbose) { | ||
| print('instruments args: $args'); | ||
| } | ||
| final ProcessResult processResult = Process.runSync('instruments', args); | ||
| _parseTraceFilename(processResult.stdout.toString()); | ||
|
|
||
| print('Parsing $_traceFilename'); | ||
|
|
||
| final IosTraceParser parser = IosTraceParser(isVerbose, traceUtilityPath); | ||
| final CpuGpuResult result = parser.parseCpuGpu(_traceFilename, processName); | ||
| result.writeToJsonFile(outJson); | ||
| print('$result\nThe result has been written into $outJson'); | ||
| } | ||
| String _traceFilename; | ||
| Future<void> _parseTraceFilename(String out) async { | ||
| const String kPrefix = 'Instruments Trace Complete: '; | ||
| final int prefixIndex = out.indexOf(kPrefix); | ||
| if (prefixIndex == -1) { | ||
| throw Exception('Failed to parse instruments output:\n$out'); | ||
| } | ||
| _traceFilename = out.substring(prefixIndex + kPrefix.length).trim(); | ||
| } | ||
|
|
||
| String _device; | ||
| void _checkDevice() { | ||
| _device = argResults[kOptionDevice]; | ||
| if (_device != null) { | ||
| return; | ||
| } | ||
| final ProcessResult result = Process.runSync( | ||
| 'instruments', | ||
| <String>['-s', 'devices'], | ||
| ); | ||
| for (String line in result.stdout.toString().split('\n')) { | ||
| if (line.contains('iPhone') && !line.contains('Simulator')) { | ||
| _device = RegExp(r'\[(.+)\]').firstMatch(line).group(1); | ||
| break; | ||
| } | ||
| } | ||
| if (_device == null) { | ||
| print(''' | ||
| Option device (-w) is not provided, and failed to find an iPhone(not a | ||
| simulator) from `instruments -s devices`. | ||
|
|
||
| stdout of `instruments -s device`: | ||
| =========================== | ||
| ${result.stdout} | ||
| =========================== | ||
|
|
||
| stderr of `instruments -s device`: | ||
| =========================== | ||
| ${result.stderr} | ||
| =========================== | ||
| '''); | ||
| throw Exception('Failed to determine the device.'); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // Copyright 2019 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'; | ||
|
|
||
| import 'package:measure/commands/base.dart'; | ||
| import 'package:measure/parser.dart'; | ||
|
|
||
| class IosCpuGpuParse extends IosCpuGpuSubcommand { | ||
| @override | ||
| String get name => 'parse'; | ||
| @override | ||
| String get description => | ||
| 'parse an existing instruments trace with CPU/GPU measurements.'; | ||
|
|
||
| @override | ||
| String get usage { | ||
| final List<String> lines = super.usage.split('\n'); | ||
| lines[0] = 'Usage: measure ioscpugpu -u <trace-utility-path> ' | ||
| 'parse <trace-file-path>'; | ||
| return lines.join('\n'); | ||
| } | ||
|
|
||
| @override | ||
| Future<void> run() async { | ||
| if (argResults.rest.length != 1) { | ||
| print(usage); | ||
| throw Exception('exactly one argument <trace-file-path> expected'); | ||
| } | ||
| final String path = argResults.rest[0]; | ||
|
|
||
| await ensureResources(); | ||
|
|
||
| final CpuGpuResult result = | ||
| IosTraceParser(isVerbose, traceUtilityPath).parseCpuGpu(path, processName); | ||
| result.writeToJsonFile(outJson); | ||
| print('$result\nThe result has been written into $outJson'); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a section explaining what this packages does / what it is for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.