Skip to content

Commit d34d5ad

Browse files
committed
Add a new package: measure
1 parent 97c6537 commit d34d5ad

16 files changed

Lines changed: 574 additions & 0 deletions

.gitignore

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

1515
GeneratedPluginRegistrant.java
1616

17+
packages/measure/result.json
18+
*instrumentscli*.trace
19+

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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Install
2+
3+
Make sure that `pub` is on your path. Then run:
4+
```shell
5+
pub global activate measure
6+
```
7+
8+
# Run
9+
First, make sure that `dart` and `flutter` is available in your command line.
10+
11+
Then, connect a **single** iPhone, run a Flutter app on it, and
12+
```shell
13+
# assuming that you're in this directory
14+
measure ioscpugpu new -u resources/TraceUtility -t resources/CpuGpuTemplate.tracetemplate
15+
```
16+
17+
It currently outputs something like
18+
```
19+
gpu: 12.4%, cpu: 22.525%
20+
```
21+
22+
Eventually, we'd like to hook this up to our CI system to continuously monitor
23+
Flutter's CPU/GPU usages, which can be used to infer the energy usage.
24+
25+
For more information, try
26+
```shell
27+
measure help
28+
measure help ioscpugpu
29+
measure help ioscpugpu new
30+
measure help ioscpugpu parse
31+
```

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+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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:meta/meta.dart';
7+
8+
const String kOptionTimeLimitMs = 'time-limit-ms';
9+
const String kOptionTemplate = 'template';
10+
const String kOptionDevice = 'device';
11+
const String kOptionProessName = 'process-name';
12+
const String kOptionTraceUtility = 'trace-utility';
13+
const String kOptionOutJson = 'out-json';
14+
const String kFlagVerbose = 'verbose';
15+
16+
const String kDefaultProccessName = 'Runner'; // Flutter app's default process
17+
18+
abstract class BaseCommand extends Command<void> {
19+
BaseCommand() {
20+
argParser.addFlag(kFlagVerbose);
21+
argParser.addOption(
22+
kOptionOutJson,
23+
abbr: 'o',
24+
help: 'json file for the measure result.',
25+
defaultsTo: 'result.json',
26+
);
27+
}
28+
29+
@protected
30+
void checkRequiredOption(String option) {
31+
if (argResults[option] == null) {
32+
throw Exception('Option $option is required.');
33+
}
34+
}
35+
36+
@protected
37+
bool get isVerbose => argResults[kFlagVerbose];
38+
@protected
39+
String get outJson => argResults[kOptionOutJson];
40+
}
41+
42+
abstract class IosCpuGpuSubcommand extends BaseCommand {
43+
IosCpuGpuSubcommand() {
44+
argParser.addOption(
45+
kOptionTraceUtility,
46+
abbr: 'u',
47+
help:
48+
'path to TraceUtility binary (https://github.com/Qusic/TraceUtility)',
49+
);
50+
argParser.addOption(
51+
kOptionProessName,
52+
abbr: 'p',
53+
help:
54+
'the process name used for filtering the instruments CPU measurements',
55+
defaultsTo: kDefaultProccessName,
56+
);
57+
}
58+
59+
@protected
60+
String get traceUtility => argResults[kOptionTraceUtility];
61+
@protected
62+
String get processName => argResults[kOptionProessName];
63+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
}
20+
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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+
kOptionTemplate,
21+
abbr: 't',
22+
help: 'instruments template'
23+
);
24+
argParser.addOption(
25+
kOptionDevice,
26+
abbr: 'w',
27+
help: 'device identifier recognizable by instruments '
28+
'(e.g., 00008020-000364CE0AF8003A)',
29+
);
30+
}
31+
32+
@override
33+
String get name => 'new';
34+
@override
35+
String get description => 'Take a new measurement on the iOS CPU/GPU '
36+
'percentage (of Flutter Runner).';
37+
38+
String get _timeLimit => argResults[kOptionTimeLimitMs];
39+
40+
@override
41+
Future<void> run() async {
42+
checkRequiredOption(kOptionTemplate);
43+
checkRequiredOption(kOptionTraceUtility);
44+
_checkDevice();
45+
46+
print('Running instruments on iOS device $_device for ${_timeLimit}ms');
47+
48+
final List<String> args = <String>[
49+
'-l', _timeLimit,
50+
'-t', argResults[kOptionTemplate],
51+
'-w', _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 CpuGpuResult result =
62+
IosTraceParser(isVerbose, traceUtility).parseCpuGpu(_traceFilename, processName);
63+
result.writeToJsonFile(outJson);
64+
print('$result\nThe result has been written into $outJson');
65+
}
66+
String _traceFilename;
67+
Future<void> _parseTraceFilename(String out) async {
68+
const String kPrefix = 'Instruments Trace Complete: ';
69+
final int prefixIndex = out.indexOf(kPrefix);
70+
if (prefixIndex == -1) {
71+
throw Exception('Failed to parse instruments output:\n$out');
72+
}
73+
_traceFilename = out.substring(prefixIndex + kPrefix.length).trim();
74+
}
75+
76+
String _device;
77+
void _checkDevice() {
78+
_device = argResults[kOptionDevice];
79+
if (_device == null) {
80+
final ProcessResult result = Process.runSync('flutter', <String>['devices']);
81+
if (result.stdout.toString().contains('1 connected device')) {
82+
final List<String> lines = result.stdout.toString().split('\n');
83+
const String kSeparator = '•';
84+
for (String line in lines) {
85+
if (line.contains(kSeparator)) {
86+
final int left = line.indexOf(kSeparator);
87+
final int right = line.indexOf(kSeparator, left + 1);
88+
_device = line.substring(left + 1, right).trim();
89+
}
90+
}
91+
if (_device == null) {
92+
print('Failed to parse `flutter devices` output:\n ${result.stdout}');
93+
}
94+
} else {
95+
print('''
96+
Option device is not provided, and `flutter devices` returns either 0 or more
97+
than 1 devices, or errored.
98+
99+
stdout of `flutter devices`:
100+
===========================
101+
${result.stdout}
102+
===========================
103+
104+
stderr of `flutter devices`:
105+
===========================
106+
${result.stderr}
107+
===========================
108+
'''
109+
);
110+
}
111+
}
112+
113+
if (_device == null) {
114+
throw Exception('Failed to determine the device.');
115+
}
116+
}
117+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
checkRequiredOption(kOptionTraceUtility);
28+
if (argResults.rest.length != 1) {
29+
print(usage);
30+
throw Exception('exactly one argument <trace-file-path> expected');
31+
}
32+
final String path = argResults.rest[0];
33+
34+
final CpuGpuResult result =
35+
IosTraceParser(isVerbose, traceUtility).parseCpuGpu(path, processName);
36+
result.writeToJsonFile(outJson);
37+
print('$result\nThe result has been written into $outJson');
38+
}
39+
}

0 commit comments

Comments
 (0)