Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ GeneratedPluginRegistrant.m

GeneratedPluginRegistrant.java

packages/measure/result.json
*instrumentscli*.trace

4 changes: 4 additions & 0 deletions packages/measure/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 0.1.0

* Initial release.

27 changes: 27 additions & 0 deletions packages/measure/LICENSE
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.
31 changes: 31 additions & 0 deletions packages/measure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Install

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


Make sure that `pub` is on your path. Then run:
```shell
pub global activate measure
```

# Run
First, make sure that `dart` and `flutter` is available in your command line.

Then, connect a **single** iPhone, run a Flutter app on it, and
```shell
# assuming that you're in this directory
measure ioscpugpu new -u resources/TraceUtility -t resources/CpuGpuTemplate.tracetemplate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some way we can avoid packaging a native binary with this?

@liyuqian liyuqian Aug 30, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I know of... This is probably due to my lack of iOS/Xcode/instruments knowledge. I'm happy to remove this binary if someone can parse the trace without using this binary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just discussed with Dan offline. We can use CIPD to serve the binary instead of uploading it to git.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is CIPD accessible for anybody? Or is this packages only meant for "internal" use?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have both a public and an internal one. This should be fine for public. Public is at https://chrome-infra-packages.appspot.com/p/flutter, internal is https://chrome-infra-packages.appspot.com/p/flutter_internal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(public is readable by all)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

```

It currently outputs something like

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This language will become outdated and probably not get updated. Can we just say something like Sample output: and get rid of the whole Eventually, we'd like to hook this...?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

```
gpu: 12.4%, cpu: 22.525%
```

Eventually, we'd like to hook this up to our CI system to continuously monitor
Flutter's CPU/GPU usages, which can be used to infer the energy usage.

For more information, try
```shell
measure help
measure help ioscpugpu
measure help ioscpugpu new
measure help ioscpugpu parse
```
16 changes: 16 additions & 0 deletions packages/measure/bin/measure.dart
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);
}
63 changes: 63 additions & 0 deletions packages/measure/lib/commands/base.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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:meta/meta.dart';

const String kOptionTimeLimitMs = 'time-limit-ms';
const String kOptionTemplate = 'template';
const String kOptionDevice = 'device';
const String kOptionProessName = 'process-name';
const String kOptionTraceUtility = 'trace-utility';
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: 'json file for the measure result.',
defaultsTo: 'result.json',
);
}

@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];
}

abstract class IosCpuGpuSubcommand extends BaseCommand {
IosCpuGpuSubcommand() {
argParser.addOption(
kOptionTraceUtility,
abbr: 'u',
help:
'path to TraceUtility binary (https://github.com/Qusic/TraceUtility)',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this a full sentence, starting with a capital and ending with a period. E.g. Specifies the path to the TraceUtility binary... (...)..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

);
argParser.addOption(
kOptionProessName,
abbr: 'p',
help:
'the process name used for filtering the instruments CPU measurements',
defaultsTo: kDefaultProccessName,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

@protected
String get traceUtility => argResults[kOptionTraceUtility];
@protected
String get processName => argResults[kOptionProessName];
}
20 changes: 20 additions & 0 deletions packages/measure/lib/commands/ioscpugpu.dart
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.';
}

117 changes: 117 additions & 0 deletions packages/measure/lib/commands/ioscpugpu/new.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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(
kOptionTemplate,
abbr: 't',
help: 'instruments template'
);
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];

@override
Future<void> run() async {
checkRequiredOption(kOptionTemplate);
checkRequiredOption(kOptionTraceUtility);
_checkDevice();

print('Running instruments on iOS device $_device for ${_timeLimit}ms');

final List<String> args = <String>[
'-l', _timeLimit,
'-t', argResults[kOptionTemplate],
'-w', _device,
];
if (isVerbose) {
print('instruments args: $args');
}
final ProcessResult processResult = Process.runSync('instruments', args);
_parseTraceFilename(processResult.stdout.toString());

print('Parsing $_traceFilename');

final CpuGpuResult result =
IosTraceParser(isVerbose, traceUtility).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) {
final ProcessResult result = Process.runSync('flutter', <String>['devices']);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub lost some comments :(

I think we should not do this. Flutter tool output is not guarnateed to be stable, and an upstream change could easily break this unintentionally. I would recommend making the device argument be required, and callers could use flutter devices or whatever other method to call it (in the devicelab, we know the device ID we're supposed to target anyway).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've already let the devicelab provide the deviceId in flutter/flutter#39439

However, I'd like my local use and test to be a little easier (see the last test in measure_test.dart).

I've now removed the flutter and instead used instruments to find the deviceId.

if (result.stdout.toString().contains('1 connected device')) {
final List<String> lines = result.stdout.toString().split('\n');
const String kSeparator = '•';
for (String line in lines) {
if (line.contains(kSeparator)) {
final int left = line.indexOf(kSeparator);
final int right = line.indexOf(kSeparator, left + 1);
_device = line.substring(left + 1, right).trim();
}
}
if (_device == null) {
print('Failed to parse `flutter devices` output:\n ${result.stdout}');
}
} else {
print('''
Option device is not provided, and `flutter devices` returns either 0 or more
than 1 devices, or errored.

stdout of `flutter devices`:
===========================
${result.stdout}
===========================

stderr of `flutter devices`:
===========================
${result.stderr}
===========================
'''
);
}
}

if (_device == null) {
throw Exception('Failed to determine the device.');
}
}
}
39 changes: 39 additions & 0 deletions packages/measure/lib/commands/ioscpugpu/parse.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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 {
checkRequiredOption(kOptionTraceUtility);
if (argResults.rest.length != 1) {
print(usage);
throw Exception('exactly one argument <trace-file-path> expected');
}
final String path = argResults.rest[0];

final CpuGpuResult result =
IosTraceParser(isVerbose, traceUtility).parseCpuGpu(path, processName);
result.writeToJsonFile(outJson);
print('$result\nThe result has been written into $outJson');
}
}
Loading