-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Added imitation game benchmarks #181
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 all commits
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,93 @@ | ||
| # Imitation Game | ||
|
|
||
| ## Description | ||
|
|
||
| `imitation_game` is a platform for performing automated tests to compare the | ||
| performance of different UI frameworks. For example, how much memory does the | ||
| same app written in Flutter and UIKit take? | ||
|
|
||
| ## Running all the tests | ||
|
|
||
| You need a mobile device plugged into your computer and setup for development. | ||
| The mobile device and the computer need to be on the same network, one that | ||
| allows communication between computers since that's how the mobile phone will | ||
| report its results to the computer. | ||
|
|
||
| ```sh | ||
| dart imitation_game.dart | ||
| ``` | ||
|
|
||
| ## Dependencies | ||
|
|
||
| In order to run the tests you will need the union of all the platforms being | ||
| tested. As new tests are added please add to this list: | ||
|
|
||
| ### iOS | ||
|
|
||
| - Flutter | ||
| - Xcode | ||
| - [ios_deploy](https://github.com/ios-control/ios-deploy) - used to launch apps | ||
| on the attached iOS device. | ||
|
|
||
|
Member
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. I'd list out the currently existing scenarios tested and a brief description of the scenarios
Member
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. That information should exist in the readme file for each test. I don't want it duplicated here. |
||
| ## Example File Layout | ||
|
|
||
| ```text | ||
| ./ | ||
| ├─ imitation_game.dart | ||
| └─ tests/ | ||
|
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. There may need to be another OS subdirectory under This may introduce some duplicate code if a UI framework is cross-platform (e.g., Flutter), or maybe we can symlink to make
Member
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. Yep, I think there will be an opportunity for sharing more between tests. I think those things will become apparent when we have more implementations and they should come organically from that. |
||
| ├─ smiley/ | ||
| │ ├─ README.md | ||
| │ ├─ flutter/ | ||
| │ │ ├─ run_ios.sh | ||
| │ │ └─ <flutter project files> | ||
| │ └─ uikit/ | ||
| │ ├─ run_ios.sh | ||
| │ └─ <uikit project files> | ||
| └─ memory/ | ||
|
Member
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. looks like this directory isn't in this PR. Is this readme right?
Member
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. It's just an example of the layout
Member
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. explicitly called it an example |
||
| ├─ README.md | ||
| ├─ flutter/ | ||
| │ ├─ run_ios.sh | ||
| │ └─ <flutter project files> | ||
| └─ uikit/ | ||
| ├─ run_ios.sh | ||
| └─ <uikit project files> | ||
| ``` | ||
|
|
||
| Here there are 2 different tests with 2 different platform implementations. The | ||
| tests are named `smiley` and `memory`, they are both implemented on the | ||
|
Member
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. memory is self explanatory. Maybe smiley needs a more descriptive name? :D
Member
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. "Smiley" is just referencing the name in the example above. It could have been anything. What it is testing is ill-relevant to the point. =) |
||
| platforms `flutter` and `uikit`. | ||
|
|
||
| ### Adding a test | ||
|
|
||
| Tests should comprise of implementations on one or more platform. The directory | ||
| for the test should be added to `./tests`. Inside that directory there should | ||
|
Member
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. your tree above didn't list out the various platform implementations. Is it a partial tree for illustration? Maybe add that to the description.
Member
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. Added |
||
| be a directory of implementations and a `README.md` file that explains the test. | ||
|
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. It would be nice for
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. One more thing I just recalled today from checking this doc: the test
For example, we can probably use Pixel 4 as D* and Moto G4 as D for Android tests. |
||
|
|
||
| ### Adding an implementation to a test | ||
|
|
||
| An implementation has to follow these rules: | ||
|
|
||
| - It needs to perform the same operations as the other implementations and | ||
| follow the description in the test's `README.md`. | ||
| - It needs to contain a `run_ios.sh` script that will build and launch the test | ||
|
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
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. BTW, how do you think about changing |
||
| on the connected device. | ||
| - It should contain a file named `ip.txt` which will be overwritten by | ||
|
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. I still feel that it's better to put the device-to-host reporting logic inside the test (e.g.,
Member
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. There is value in having the reporting mechanism the same across all implementations. HTTP is the most universal mechanism that should be easy for users. Once we have a few tests we can probably just wrap it up in a library so when you implement a test you just call
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. I like just calling |
||
| `imitation_game.dart` with the ip address and port that should be used to | ||
| report results to. | ||
| - It needs to report its results to the ip and port in the `ip.txt` via an HTTP | ||
| POST of JSON data. | ||
|
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. I feel that the measurement code/tool should not live inside the implementation directory. There should only be a single measurement code/tool that's under the test (e.g., This might be critical when a UI framework that no one is familiar enters the arena. Say if someone invented a new framework called Glutter, we probably don't have the expertise and time to vet its measuring code and tools.
Member
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. We just have to rely on humans to read the code. Someone reading the code can say if it is measuring the same thing. Certain things are only measurable on device, like time since the process was launched. From the host computer we can just measure the time between when we asked it to launch which isn't as accurate. If we find there is a way to share measurement code that can be factored out. Nothing about the current design disallows it. |
||
|
|
||
| ## Data format for results | ||
|
|
||
| ```json | ||
| { | ||
| "test": "name_of_test", | ||
| "platform": "name_of_platform", | ||
| "results": { | ||
| "some_result_name": 1.23, | ||
| "some_result_name2": 4.56, | ||
|
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. What do multiple results correspond to? Are they for different implementations such as
Member
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. No, it's a way to measure 2 different things for the same execution. For example, the startup test could measure memory as well as startup time. |
||
| } | ||
| } | ||
| ``` | ||
|
|
||
| A single test run can report multiple numbers. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| import 'dart:async'; | ||
| import 'dart:convert'; | ||
| import 'dart:io'; | ||
|
|
||
| const int _port = 4040; | ||
|
|
||
| Future<String> _findIpAddress() async { | ||
| String result; | ||
| final List<NetworkInterface> interfaces = await NetworkInterface.list(); | ||
| for (NetworkInterface interface in interfaces) { | ||
| for (InternetAddress address in interface.addresses) { | ||
| if (address.type == InternetAddressType.IPv4) { | ||
| // TODO(gaaclarke): Implment having multiple addresses. | ||
| assert(result == null); | ||
| result = address.address; | ||
| } | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| typedef FileFilter = bool Function(FileSystemEntity); | ||
| Future<List<FileSystemEntity>> findFiles(Directory dir, {FileFilter where}) { | ||
| final List<FileSystemEntity> files = <FileSystemEntity>[]; | ||
| final Completer<List<FileSystemEntity>> completer = | ||
| Completer<List<FileSystemEntity>>(); | ||
| final Stream<FileSystemEntity> lister = dir.list(recursive: true); | ||
| lister.listen((FileSystemEntity file) { | ||
| if (where == null || where(file)) { | ||
| files.add(file); | ||
| } | ||
| }, onDone: () => completer.complete(files)); | ||
| return completer.future; | ||
| } | ||
|
|
||
| class _Script { | ||
| _Script({this.path}); | ||
| String path; | ||
| } | ||
|
|
||
| class _ScriptRunner { | ||
| _ScriptRunner(this._scriptPaths); | ||
|
|
||
| final List<String> _scriptPaths; | ||
| Process _currentProcess; | ||
| StreamSubscription<String> _stdoutSubscription; | ||
| StreamSubscription<String> _stderrSubscription; | ||
|
|
||
| Future<_Script> runNext() async { | ||
| if (_currentProcess != null) { | ||
| _stdoutSubscription.cancel(); | ||
| _stderrSubscription.cancel(); | ||
| _currentProcess.kill(); | ||
| _currentProcess = null; | ||
| } | ||
|
|
||
| if (_scriptPaths.isEmpty) { | ||
| return null; | ||
| } else { | ||
| final String path = _scriptPaths.last; | ||
| print('running: $path'); | ||
| _scriptPaths.removeLast(); | ||
| _currentProcess = await Process.start('sh', <String>[path]); | ||
| // TODO(gaaclarke): Implement a timeout. | ||
| _stdoutSubscription = | ||
| _currentProcess.stdout.transform(utf8.decoder).listen((String data) { | ||
| print(data); | ||
| }); | ||
| _stderrSubscription = | ||
| _currentProcess.stderr.transform(utf8.decoder).listen((String data) { | ||
| print(data); | ||
| }); | ||
| return _Script(path: path); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| class _ImitationGame { | ||
| final Map<String, dynamic> results = <String, dynamic>{}; | ||
| _ScriptRunner _scriptRunner; | ||
| _Script _currentScript; | ||
|
|
||
| Future<bool> start(List<String> iosScripts) { | ||
| _scriptRunner = _ScriptRunner(iosScripts); | ||
| return _runNext(); | ||
| } | ||
|
|
||
| Future<bool> handleResult(Map<String, dynamic> data) { | ||
| final String test = data['test']; | ||
| final String platform = data['platform']; | ||
| if (!results.containsKey(test)) { | ||
| results[test] = <String, dynamic>{}; | ||
| } | ||
| if (!results[test].containsKey(platform)) { | ||
| results[test][platform] = <String, dynamic>{}; | ||
| } | ||
| data['results'].forEach((String k, dynamic v) { | ||
| results[test][platform][k] = v as double; | ||
| }); | ||
| return _runNext(); | ||
| } | ||
|
|
||
| Future<bool> handleTimeout() { | ||
| return _runNext(); | ||
| } | ||
|
|
||
| Future<bool> _runNext() async { | ||
| _currentScript = await _scriptRunner.runNext(); | ||
| return _currentScript != null; | ||
| } | ||
| } | ||
|
|
||
| Future<void> main() async { | ||
| final HttpServer server = await HttpServer.bind( | ||
| InternetAddress.anyIPv4, | ||
| _port, | ||
| ); | ||
| final String ipaddress = await _findIpAddress(); | ||
| print('Listening on $ipaddress:${server.port}'); | ||
|
|
||
| for (FileSystemEntity entity in await findFiles(Directory.current, | ||
| where: (FileSystemEntity f) => f.path.endsWith('ip.txt'))) { | ||
| final File file = File(entity.path); | ||
| file.writeAsStringSync('$ipaddress:${server.port}'); | ||
| } | ||
|
|
||
| final List<String> iosScripts = (await findFiles(Directory.current, | ||
| where: (FileSystemEntity f) => f.path.endsWith('run_ios.sh'))) | ||
| .map((FileSystemEntity e) => e.path) | ||
| .toList(); | ||
|
|
||
| if (iosScripts.isEmpty) { | ||
| return; | ||
| } | ||
|
|
||
| final _ImitationGame game = _ImitationGame(); | ||
| bool keepRunning = await game.start(iosScripts); | ||
|
|
||
| while (keepRunning) { | ||
| try { | ||
| final Stream<HttpRequest> timeoutServer = server.timeout( | ||
| const Duration(minutes: 5), onTimeout: (EventSink<HttpRequest> sink) { | ||
| print('TIMEOUT!'); | ||
| throw TimeoutException('timeout'); | ||
| }); | ||
| await for (HttpRequest request in timeoutServer) { | ||
| print('got request: ${request.method}'); | ||
| if (request.method == 'POST') { | ||
| final String content = await utf8.decoder.bind(request).join(); | ||
| final Map<String, dynamic> data = | ||
| jsonDecode(content) as Map<String, dynamic>; | ||
| print('$data'); | ||
| keepRunning = await game.handleResult(data); | ||
| if (!keepRunning) { | ||
| break; | ||
| } | ||
| } else { | ||
| request.response.write('use post'); | ||
| } | ||
| await request.response.close(); | ||
| } | ||
| } on TimeoutException catch (_) { | ||
| keepRunning = await game.handleTimeout(); | ||
| } | ||
| } | ||
| const JsonEncoder encoder = JsonEncoder.withIndent(' '); | ||
| final String jsonResults = encoder.convert(game.results); | ||
| print('$jsonResults'); | ||
| await server.close(force: true); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Smiley | ||
|
|
||
| This test is an app that just draws one large image to the screen `smiley.png`, | ||
| stretched to fit inside the screen. | ||
|
|
||
| The following things are measured: | ||
|
|
||
| - 'startupTime' - The time between the start of the process and the rendering of | ||
| the image to the screen. | ||
| - 'memory' - The amount of system memory the app is using after having rendered | ||
| the image to the screen. | ||
|
Member
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. while we're here, should we add a cumulative cpu+gpu cost test? (in a different pr as needed) |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| #!/bin/sh | ||
| cd $( dirname "${BASH_SOURCE[0]}" ) | ||
| cd smiley | ||
| flutter run --release |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Miscellaneous | ||
| *.class | ||
| *.log | ||
| *.pyc | ||
| *.swp | ||
| .DS_Store | ||
| .atom/ | ||
| .buildlog/ | ||
| .history | ||
| .svn/ | ||
|
|
||
| # IntelliJ related | ||
| *.iml | ||
| *.ipr | ||
| *.iws | ||
| .idea/ | ||
|
|
||
| # The .vscode folder contains launch configuration and tasks you configure in | ||
| # VS Code which you may wish to be included in version control, so this line | ||
| # is commented out by default. | ||
| #.vscode/ | ||
|
|
||
| # Flutter/Dart/Pub related | ||
| **/doc/api/ | ||
| **/ios/Flutter/.last_build_id | ||
| .dart_tool/ | ||
| .flutter-plugins | ||
| .flutter-plugins-dependencies | ||
| .packages | ||
| .pub-cache/ | ||
| .pub/ | ||
| /build/ | ||
|
|
||
| # Web related | ||
| lib/generated_plugin_registrant.dart | ||
|
|
||
| # Symbolication related | ||
| app.*.symbols | ||
|
|
||
| # Obfuscation related | ||
| app.*.map.json | ||
|
|
||
| # Exceptions to above rules. | ||
| !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # This file tracks properties of this Flutter project. | ||
| # Used by Flutter tool to assess capabilities and perform upgrades etc. | ||
| # | ||
| # This file should be version controlled and should not be manually edited. | ||
|
|
||
| version: | ||
| revision: 227990ab308574184e06944710bae000330412d0 | ||
| channel: unknown | ||
|
|
||
| project_type: app |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # smiley | ||
|
|
||
| A new Flutter project. | ||
|
|
||
| ## Getting Started | ||
|
|
||
| This project is a starting point for a Flutter application. | ||
|
|
||
| A few resources to get you started if this is your first Flutter project: | ||
|
|
||
| - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) | ||
| - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) | ||
|
|
||
| For help getting started with Flutter, view our | ||
| [online documentation](https://flutter.dev/docs), which offers tutorials, | ||
| samples, guidance on mobile development, and a full API reference. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| gradle-wrapper.jar | ||
| /.gradle | ||
| /captures/ | ||
| /gradlew | ||
| /gradlew.bat | ||
| /local.properties | ||
| GeneratedPluginRegistrant.java | ||
|
|
||
| # Remember to never publicly share your keystore. | ||
| # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app | ||
| key.properties |
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.
A local mode with a mobile device and a host computer is definitely needed for development. Additionally, I think it's nice to also have a cloud mode where the tests can run from something like Firebase test lab.