Skip to content

Commit cb10b53

Browse files
authored
Add a new package: measure (flutter#31)
For flutter/flutter#39439 and flutter/flutter#33899
1 parent 9707b5f commit cb10b53

13 files changed

Lines changed: 591 additions & 0 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ GeneratedPluginRegistrant.m
1414

1515
GeneratedPluginRegistrant.java
1616

17+
packages/measure/result.json
18+
packages/measure/resources
19+
*instrumentscli*.trace
20+
*.cipd
21+

packages/measure/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## 0.1.0
2+
3+
* Initial release.
4+

packages/measure/LICENSE

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright 2019 The Chromium Authors. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without
4+
modification, are permitted provided that the following conditions are
5+
met:
6+
7+
* Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
* Redistributions in binary form must reproduce the above
10+
copyright notice, this list of conditions and the following
11+
disclaimer in the documentation and/or other materials provided
12+
with the distribution.
13+
* Neither the name of Google Inc. nor the names of its
14+
contributors may be used to endorse or promote products derived
15+
from this software without specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

packages/measure/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Tools for measuring some performance metrics.
2+
3+
Currently there's only one tool to measure iOS CPU/GPU usages for Flutter's CI
4+
tests.
5+
6+
# Install
7+
8+
First install [depot_tools][1] (we used its `cipd`).
9+
10+
Then install [dart](https://dart.dev/get-dart) and make sure that `pub` is on
11+
your path.
12+
13+
Finally run:
14+
```shell
15+
pub global activate measure
16+
```
17+
18+
# Run
19+
Connect an iPhone, run a Flutter app on it, and
20+
```shell
21+
measure ioscpugpu new
22+
```
23+
24+
Sample output:
25+
```
26+
gpu: 12.4%, cpu: 22.525%
27+
```
28+
29+
For more information, try
30+
```shell
31+
measure help
32+
measure help ioscpugpu
33+
measure help ioscpugpu new
34+
measure help ioscpugpu parse
35+
```
36+
37+
[1]: https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up

packages/measure/bin/measure.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:args/command_runner.dart';
6+
7+
import 'package:measure/commands/ioscpugpu.dart';
8+
9+
void main(List<String> args) {
10+
final CommandRunner<void> runner = CommandRunner<void>(
11+
'measure',
12+
'Tools for measuring some performance metrics.',
13+
);
14+
runner.addCommand(IosCpuGpu());
15+
runner.run(args);
16+
}

packages/measure/cipd.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package: flutter/packages/measure/resources
2+
description: Binaries and other resources for measuring performance metrics.
3+
install_mode: copy
4+
data:
5+
- dir: resources
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:args/command_runner.dart';
8+
import 'package:meta/meta.dart';
9+
10+
const String kOptionResourcesRoot = 'resources-root';
11+
const String kOptionTimeLimitMs = 'time-limit-ms';
12+
const String kOptionDevice = 'device';
13+
const String kOptionProessName = 'process-name';
14+
const String kOptionOutJson = 'out-json';
15+
const String kFlagVerbose = 'verbose';
16+
17+
const String kDefaultProccessName = 'Runner'; // Flutter app's default process
18+
19+
abstract class BaseCommand extends Command<void> {
20+
BaseCommand() {
21+
argParser.addFlag(kFlagVerbose);
22+
argParser.addOption(
23+
kOptionOutJson,
24+
abbr: 'o',
25+
help: 'Specifies the json file for result output.',
26+
defaultsTo: 'result.json',
27+
);
28+
argParser.addOption(
29+
kOptionResourcesRoot,
30+
abbr: 'r',
31+
help: 'Specifies the path to download resources',
32+
defaultsTo: defaultResourcesRoot,
33+
);
34+
}
35+
36+
static String get defaultResourcesRoot =>
37+
'${Platform.environment['HOME']}/.measure';
38+
39+
static Future<void> doEnsureResources(String rootPath,
40+
{bool isVerbose}) async {
41+
final Directory root = await Directory(rootPath).create(recursive: true);
42+
final Directory previous = Directory.current;
43+
Directory.current = root;
44+
final File ensureFile = File('ensure_file.txt');
45+
ensureFile.writeAsStringSync('flutter/packages/measure/resources latest');
46+
if (isVerbose) {
47+
print('Downloading resources from CIPD...');
48+
}
49+
final ProcessResult result = Process.runSync(
50+
'cipd',
51+
<String>[
52+
'ensure',
53+
'-ensure-file',
54+
'ensure_file.txt',
55+
'-root',
56+
'.',
57+
],
58+
);
59+
if (result.exitCode != 0) {
60+
print('cipd ensure stdout:\n${result.stdout}\n');
61+
print('cipd ensure stderr:\n${result.stderr}\n');
62+
throw Exception('Failed to download the CIPD package.');
63+
}
64+
if (isVerbose) {
65+
print('Download completes.');
66+
}
67+
Directory.current = previous;
68+
}
69+
70+
@protected
71+
Future<void> ensureResources() async {
72+
doEnsureResources(resourcesRoot, isVerbose: isVerbose);
73+
}
74+
75+
@protected
76+
void checkRequiredOption(String option) {
77+
if (argResults[option] == null) {
78+
throw Exception('Option $option is required.');
79+
}
80+
}
81+
82+
@protected
83+
bool get isVerbose => argResults[kFlagVerbose];
84+
@protected
85+
String get outJson => argResults[kOptionOutJson];
86+
@protected
87+
String get resourcesRoot => argResults[kOptionResourcesRoot];
88+
}
89+
90+
abstract class IosCpuGpuSubcommand extends BaseCommand {
91+
IosCpuGpuSubcommand() {
92+
argParser.addOption(
93+
kOptionProessName,
94+
abbr: 'p',
95+
help: 'Specifies the process name used for filtering the instruments CPU '
96+
'measurements.',
97+
defaultsTo: kDefaultProccessName,
98+
);
99+
}
100+
101+
@protected
102+
String get processName => argResults[kOptionProessName];
103+
@protected
104+
String get traceUtilityPath => '$resourcesRoot/resources/TraceUtility';
105+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:args/command_runner.dart';
6+
import 'package:measure/commands/ioscpugpu/new.dart';
7+
import 'package:measure/commands/ioscpugpu/parse.dart';
8+
9+
class IosCpuGpu extends Command<void> {
10+
IosCpuGpu() {
11+
addSubcommand(IosCpuGpuNew());
12+
addSubcommand(IosCpuGpuParse());
13+
}
14+
15+
@override
16+
String get name => 'ioscpugpu';
17+
@override
18+
String get description => 'Measure the iOS CPU/GPU percentage.';
19+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:io';
7+
8+
import 'package:measure/commands/base.dart';
9+
import 'package:measure/parser.dart';
10+
11+
class IosCpuGpuNew extends IosCpuGpuSubcommand {
12+
IosCpuGpuNew() {
13+
argParser.addOption(
14+
kOptionTimeLimitMs,
15+
abbr: 'l',
16+
defaultsTo: '5000',
17+
help: 'time limit (in ms) to run instruments for measuring',
18+
);
19+
argParser.addOption(
20+
kOptionDevice,
21+
abbr: 'w',
22+
help: 'device identifier recognizable by instruments '
23+
'(e.g., 00008020-000364CE0AF8003A)',
24+
);
25+
}
26+
27+
@override
28+
String get name => 'new';
29+
@override
30+
String get description => 'Take a new measurement on the iOS CPU/GPU '
31+
'percentage (of Flutter Runner).';
32+
33+
String get _timeLimit => argResults[kOptionTimeLimitMs];
34+
35+
String get _templatePath =>
36+
'$resourcesRoot/resources/CpuGpuTemplate.tracetemplate';
37+
38+
@override
39+
Future<void> run() async {
40+
_checkDevice();
41+
await ensureResources();
42+
43+
print('Running instruments on iOS device $_device for ${_timeLimit}ms');
44+
45+
final List<String> args = <String>[
46+
'-l',
47+
_timeLimit,
48+
'-t',
49+
_templatePath,
50+
'-w',
51+
_device,
52+
];
53+
if (isVerbose) {
54+
print('instruments args: $args');
55+
}
56+
final ProcessResult processResult = Process.runSync('instruments', args);
57+
_parseTraceFilename(processResult.stdout.toString());
58+
59+
print('Parsing $_traceFilename');
60+
61+
final IosTraceParser parser = IosTraceParser(isVerbose, traceUtilityPath);
62+
final CpuGpuResult result = parser.parseCpuGpu(_traceFilename, processName);
63+
result.writeToJsonFile(outJson);
64+
print('$result\nThe result has been written into $outJson');
65+
}
66+
67+
String _traceFilename;
68+
Future<void> _parseTraceFilename(String out) async {
69+
const String kPrefix = 'Instruments Trace Complete: ';
70+
final int prefixIndex = out.indexOf(kPrefix);
71+
if (prefixIndex == -1) {
72+
throw Exception('Failed to parse instruments output:\n$out');
73+
}
74+
_traceFilename = out.substring(prefixIndex + kPrefix.length).trim();
75+
}
76+
77+
String _device;
78+
void _checkDevice() {
79+
_device = argResults[kOptionDevice];
80+
if (_device != null) {
81+
return;
82+
}
83+
final ProcessResult result = Process.runSync(
84+
'instruments',
85+
<String>['-s', 'devices'],
86+
);
87+
for (String line in result.stdout.toString().split('\n')) {
88+
if (line.contains('iPhone') && !line.contains('Simulator')) {
89+
_device = RegExp(r'\[(.+)\]').firstMatch(line).group(1);
90+
break;
91+
}
92+
}
93+
if (_device == null) {
94+
print('''
95+
Option device (-w) is not provided, and failed to find an iPhone(not a
96+
simulator) from `instruments -s devices`.
97+
98+
stdout of `instruments -s device`:
99+
===========================
100+
${result.stdout}
101+
===========================
102+
103+
stderr of `instruments -s device`:
104+
===========================
105+
${result.stderr}
106+
===========================
107+
''');
108+
throw Exception('Failed to determine the device.');
109+
}
110+
}
111+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:measure/commands/base.dart';
8+
import 'package:measure/parser.dart';
9+
10+
class IosCpuGpuParse extends IosCpuGpuSubcommand {
11+
@override
12+
String get name => 'parse';
13+
@override
14+
String get description =>
15+
'parse an existing instruments trace with CPU/GPU measurements.';
16+
17+
@override
18+
String get usage {
19+
final List<String> lines = super.usage.split('\n');
20+
lines[0] = 'Usage: measure ioscpugpu -u <trace-utility-path> '
21+
'parse <trace-file-path>';
22+
return lines.join('\n');
23+
}
24+
25+
@override
26+
Future<void> run() async {
27+
if (argResults.rest.length != 1) {
28+
print(usage);
29+
throw Exception('exactly one argument <trace-file-path> expected');
30+
}
31+
final String path = argResults.rest[0];
32+
33+
await ensureResources();
34+
35+
final CpuGpuResult result = IosTraceParser(isVerbose, traceUtilityPath)
36+
.parseCpuGpu(path, processName);
37+
result.writeToJsonFile(outJson);
38+
print('$result\nThe result has been written into $outJson');
39+
}
40+
}

0 commit comments

Comments
 (0)