diff --git a/agent/bin/agent.dart b/agent/bin/agent.dart index d69b1c9b41..72fb461f3c 100644 --- a/agent/bin/agent.dart +++ b/agent/bin/agent.dart @@ -10,7 +10,6 @@ import 'package:http/http.dart'; import 'package:cocoon_agent/src/agent.dart'; import 'package:cocoon_agent/src/commands/ci.dart'; -import 'package:cocoon_agent/src/commands/run.dart'; import 'package:cocoon_agent/src/utils.dart'; Future main(List rawArgs) async { @@ -26,7 +25,6 @@ Future main(List rawArgs) async { defaultsTo: '.' ); argParser.addCommand('ci'); - argParser.addCommand('run', RunCommand.argParser); ArgResults args = argParser.parse(rawArgs); @@ -45,7 +43,6 @@ Future main(List rawArgs) async { } registerCommand(new ContinuousIntegrationCommand(agent)); - registerCommand(new RunCommand(agent)); if (args.command == null) { print('No command specified, expected one of: ${allCommands.keys.join(', ')}'); diff --git a/agent/lib/src/adb.dart b/agent/lib/src/adb.dart index 6188ce6db6..0bc4be27b6 100644 --- a/agent/lib/src/adb.dart +++ b/agent/lib/src/adb.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:math' as math; import 'package:meta/meta.dart'; @@ -28,24 +27,8 @@ abstract class DeviceDiscovery { } } - /// Selects a device to work with, load-balancing between devices if more than - /// one are available. - /// - /// A task performing multiple actions on a device would call this method - /// first then follow up by calling [workingDevice]. Calling [workingDevice] - /// repeatedly must guarantee to return the same device. - Future chooseWorkingDevice(); - - /// Returns current working device. - /// - /// Must be called _after_ a successful call to [chooseWorkingDevice]. May be - /// called repeatedly to perform multiple operations on one specific device; - /// guarantees to return the same device until the next time - /// [chooseWorkingDevice] is called. - Device get workingDevice; - /// Lists all available devices' IDs. - Future> discoverDevices(); + Future> discoverDevices(); /// Checks the health of the available devices. Future> checkDevices(); @@ -94,37 +77,8 @@ class AndroidDeviceDiscovery implements DeviceDiscovery { AndroidDeviceDiscovery._(); - AndroidDevice _workingDevice; - - @override - AndroidDevice get workingDevice { - if (_workingDevice == null) { - throw new StateError( - 'No working device chosen. Call `chooseWorkingDevice` prior to calling ' - 'the `workingDevice` getter.', - ); - } - - return _workingDevice; - } - - /// Picks a random Android device out of connected devices and sets it as - /// [workingDevice]. @override - Future chooseWorkingDevice() async { - List allDevices = (await discoverDevices()) - .map((String id) => new AndroidDevice(deviceId: id)) - .toList(); - - if (allDevices.isEmpty) - throw 'No Android devices detected'; - - // TODO(yjbanov): filter out and warn about those with low battery level - _workingDevice = allDevices[new math.Random().nextInt(allDevices.length)]; - } - - @override - Future> discoverDevices() async { + Future> discoverDevices() async { List output = (await eval(config.adbPath, ['devices', '-l'], canFail: false)) .trim().split('\n'); List results = []; @@ -150,21 +104,22 @@ class AndroidDeviceDiscovery implements DeviceDiscovery { } } - return results; + return results + .map((String id) => new AndroidDevice(deviceId: id)) + .toList(); } @override Future> checkDevices() async { Map results = {}; - for (String deviceId in await discoverDevices()) { + for (AndroidDevice device in await discoverDevices()) { try { - AndroidDevice device = new AndroidDevice(deviceId: deviceId); // Just a smoke test that we can read wakefulness state // TODO(yjbanov): check battery level await device._getWakefulness(); - results['android-device-$deviceId'] = new HealthCheckResult.success(); + results['android-device-${device.deviceId}'] = new HealthCheckResult.success(); } catch(e, s) { - results['android-device-$deviceId'] = new HealthCheckResult.error(e, s); + results['android-device-${device.deviceId}'] = new HealthCheckResult.error(e, s); } } return results; @@ -260,37 +215,8 @@ class IosDeviceDiscovery implements DeviceDiscovery { IosDeviceDiscovery._(); - IosDevice _workingDevice; - @override - IosDevice get workingDevice { - if (_workingDevice == null) { - throw new StateError( - 'No working device chosen. Call `chooseWorkingDevice` prior to calling ' - 'the `workingDevice` getter.', - ); - } - - return _workingDevice; - } - - /// Picks a random iOS device out of connected devices and sets it as - /// [workingDevice]. - @override - Future chooseWorkingDevice() async { - List allDevices = (await discoverDevices()) - .map((String id) => new IosDevice(deviceId: id)) - .toList(); - - if (allDevices.length == 0) - throw 'No iOS devices detected'; - - // TODO(yjbanov): filter out and warn about those with low battery level - _workingDevice = allDevices[new math.Random().nextInt(allDevices.length)]; - } - - @override - Future> discoverDevices() async { + Future> discoverDevices() async { // TODO: use the -k UniqueDeviceID option, which requires much less parsing. List iosDeviceIds = grep('UniqueDeviceID', from: await eval('ideviceinfo', [])) .map((String line) => line.split(' ').last).toList(); @@ -298,15 +224,17 @@ class IosDeviceDiscovery implements DeviceDiscovery { if (iosDeviceIds.isEmpty) throw 'No connected iOS devices found.'; - return iosDeviceIds; + return iosDeviceIds + .map((String id) => new IosDevice(deviceId: id)) + .toList(); } @override Future> checkDevices() async { Map results = {}; - for (String deviceId in await discoverDevices()) { + for (Device device in await discoverDevices()) { // TODO: do a more meaningful connectivity check than just recording the ID - results['ios-device-$deviceId'] = new HealthCheckResult.success(); + results['ios-device-${device.deviceId}'] = new HealthCheckResult.success(); } return results; } diff --git a/agent/lib/src/agent.dart b/agent/lib/src/agent.dart index 0aafb91d04..67fb4f25bf 100644 --- a/agent/lib/src/agent.dart +++ b/agent/lib/src/agent.dart @@ -9,15 +9,6 @@ import 'package:args/args.dart'; import 'package:http/http.dart'; import 'package:meta/meta.dart'; -import 'package:cocoon_agent/src/adb.dart'; -import 'package:cocoon_agent/src/analysis.dart'; -import 'package:cocoon_agent/src/framework.dart'; -import 'package:cocoon_agent/src/gallery.dart'; -import 'package:cocoon_agent/src/golem.dart'; -import 'package:cocoon_agent/src/perf_tests.dart'; -import 'package:cocoon_agent/src/refresh.dart'; -import 'package:cocoon_agent/src/hot_dev_cycle.dart'; -import 'package:cocoon_agent/src/size_tests.dart'; import 'package:cocoon_agent/src/utils.dart'; /// Contains information about a Cocoon task. @@ -49,25 +40,6 @@ class Agent { return JSON.decode(resp.body); } - Future performTask(CocoonTask reservation) async { - int golemRevision = await computeGolemRevision(); - Task task = await getTask(reservation); - TaskRunner runner = new TaskRunner(reservation.revision, golemRevision, [task]); - try { - return await runner.run(); - } finally { - await _screenOff(); - } - } - - Future _screenOff() async { - try { - await devices.workingDevice.sendToSleep(); - } catch(error, stackTrace) { - print('Failed to turn off screen: $error\n$stackTrace'); - } - } - Future uploadLogChunk(CocoonTask task, String chunk) async { String url = '$baseCocoonUrl/api/append-log?ownerKey=${task.key}'; Response resp = await httpClient.post(url, body: chunk); @@ -77,36 +49,6 @@ class Agent { } } - Future getTask(CocoonTask task) async { - DateTime revisionTimestamp = await getFlutterRepoCommitTimestamp(task.revision); - String dartSdkVersion = await getDartVersion(); - - List allTasks = [ - createComplexLayoutScrollPerfTest(), - createComplexLayoutScrollPerfTest(ios: true), - createFlutterGalleryStartupTest(), - createFlutterGalleryStartupTest(ios: true), - createComplexLayoutStartupTest(), - createComplexLayoutStartupTest(ios: true), - createFlutterGalleryBuildTest(), - createComplexLayoutBuildTest(), - createGalleryTransitionTest(), - createGalleryTransitionTest(ios: true), - createBasicMaterialAppSizeTest(), - createAnalyzerCliTest(sdk: dartSdkVersion, commit: task.revision, timestamp: revisionTimestamp), - createAnalyzerServerTest(sdk: dartSdkVersion, commit: task.revision, timestamp: revisionTimestamp), - createRefreshTest(commit: task.revision, timestamp: revisionTimestamp), - createHotDevCycleTest(commit: task.revision, timestamp: revisionTimestamp), - ]; - - return allTasks.firstWhere( - (Task t) => t.name == task.name, - orElse: () { - throw 'Task $task.name not found'; - } - ); - } - /// Reserves a task in Cocoon backend to be performed by this agent. /// /// If not tasks are available returns `null`. diff --git a/agent/lib/src/analysis.dart b/agent/lib/src/analysis.dart deleted file mode 100644 index 2440aeb12c..0000000000 --- a/agent/lib/src/analysis.dart +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2016 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:meta/meta.dart'; -import 'package:path/path.dart' as path; - -import 'benchmarks.dart'; -import 'framework.dart'; -import 'utils.dart'; - -Task createAnalyzerCliTest({ - @required String sdk, - @required String commit, - @required DateTime timestamp -}) { - return new AnalyzerCliTask(sdk, commit, timestamp); -} - -Task createAnalyzerServerTest({ - @required String sdk, - @required String commit, - @required DateTime timestamp -}) { - return new AnalyzerServerTask(sdk, commit, timestamp); -} - -abstract class AnalyzerTask extends Task { - AnalyzerTask(String name) : super(name); - - Benchmark benchmark; - - @override - Future run() async { - section(benchmark.name); - await runBenchmark(benchmark, iterations: 3, warmUpBenchmark: true); - return benchmark.bestResult; - } -} - -class AnalyzerCliTask extends AnalyzerTask { - AnalyzerCliTask(String sdk, String commit, DateTime timestamp) : super('analyzer_cli__analysis_time') { - this.benchmark = new FlutterAnalyzeBenchmark(sdk, commit, timestamp); - } -} - -class AnalyzerServerTask extends AnalyzerTask { - AnalyzerServerTask(String sdk, String commit, DateTime timestamp) : super('analyzer_server__analysis_time') { - this.benchmark = new FlutterAnalyzeAppBenchmark(sdk, commit, timestamp); - } -} - -class FlutterAnalyzeBenchmark extends Benchmark { - FlutterAnalyzeBenchmark(this.sdk, this.commit, this.timestamp) - : super('flutter analyze --flutter-repo'); - - final String sdk; - final String commit; - final DateTime timestamp; - - File get benchmarkFile => file(path.join(config.flutterDirectory.path, 'analysis_benchmark.json')); - - @override - TaskResultData get lastResult => new TaskResultData.fromFile(benchmarkFile); - - @override - Future run() async { - rm(benchmarkFile); - await inDirectory(config.flutterDirectory, () async { - await flutter('analyze', options: ['--flutter-repo', '--benchmark']); - }); - return addBuildInfo(benchmarkFile, timestamp: timestamp, expected: 25.0, sdk: sdk, commit: commit); - } -} - -class FlutterAnalyzeAppBenchmark extends Benchmark { - FlutterAnalyzeAppBenchmark(this.sdk, this.commit, this.timestamp) - : super('analysis server mega_gallery'); - - final String sdk; - final String commit; - final DateTime timestamp; - - @override - TaskResultData get lastResult => new TaskResultData.fromFile(benchmarkFile); - - Directory get megaDir => dir(path.join(config.flutterDirectory.path, 'dev/benchmarks/mega_gallery')); - File get benchmarkFile => file(path.join(megaDir.path, 'analysis_benchmark.json')); - - Future init() { - return inDirectory(config.flutterDirectory, () async { - await dart(['dev/tools/mega_gallery.dart']); - }); - } - - @override - Future run() async { - rm(benchmarkFile); - await inDirectory(megaDir, () async { - await flutter('watch', options: ['--benchmark']); - }); - return addBuildInfo(benchmarkFile, timestamp: timestamp, expected: 10.0, sdk: sdk, commit: commit); - } -} diff --git a/agent/lib/src/benchmarks.dart b/agent/lib/src/benchmarks.dart deleted file mode 100644 index 5877218633..0000000000 --- a/agent/lib/src/benchmarks.dart +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2016 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 'framework.dart'; - -abstract class Benchmark { - Benchmark(this.name); - - final String name; - - TaskResultData bestResult; - - Future init() => new Future.value(); - - Future run(); - TaskResultData get lastResult; - - String toString() => name; -} - -Future runBenchmark(Benchmark benchmark, { - int iterations: 1, - bool warmUpBenchmark: false -}) async { - await benchmark.init(); - - List allRuns = []; - - num minValue; - - if (warmUpBenchmark) - await benchmark.run(); - - while (iterations > 0) { - iterations--; - - print(''); - - try { - num result = await benchmark.run(); - allRuns.add(result); - - if (minValue == null || result < minValue) { - benchmark.bestResult = benchmark.lastResult; - minValue = result; - } - } catch (error) { - print('benchmark failed with error: $error'); - } - } - - return minValue; -} diff --git a/agent/lib/src/buildbot.dart b/agent/lib/src/buildbot.dart deleted file mode 100644 index 44566fc131..0000000000 --- a/agent/lib/src/buildbot.dart +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2016 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:convert'; -import 'dart:io'; - -import 'package:http/http.dart' as http; - -const String _jsonEndpoint = 'https://build.chromium.org/p/client.flutter/json'; - -/// Looks up the latest `git` commit SHA that succeeded both on Linux and Mac -/// buildbots. -Future getLatestGreenRevision() async { - BuilderClient linuxBuilder = new BuilderClient('Linux'); - BuilderClient macBuilder = new BuilderClient('Mac'); - List linuxBuildNumbers = await linuxBuilder.listRecentBuilds(); - List macBuildNumbers = await macBuilder.listRecentBuilds(); - Set greenLinuxRevisions = new Set(); - Set greenMacRevisions = new Set(); - - // Keep fetching builds until we find a build that was green both on Mac - // and Linux buildbots. - while (greenLinuxRevisions.intersection(greenMacRevisions).isEmpty && - linuxBuildNumbers.isNotEmpty && - macBuildNumbers.isNotEmpty) { - BuildInfo linuxBuild = await linuxBuilder.getBuild(linuxBuildNumbers.removeLast()); - if (linuxBuild.isGreen) - greenLinuxRevisions.add(linuxBuild.revision); - - BuildInfo macBuild = await macBuilder.getBuild(macBuildNumbers.removeLast()); - if (macBuild.isGreen) - greenMacRevisions.add(macBuild.revision); - } - - Set intersection = greenLinuxRevisions.intersection(greenMacRevisions); - if (intersection.isEmpty) { - // No builds that are green on both Mac and Linux - return null; - } - - return intersection.single; -} - -class BuildInfo { - BuildInfo(this.builderName, this.number, this.isGreen, this.revision); - - final String builderName; - final int number; - final bool isGreen; - final String revision; -} - -class BuilderClient { - BuilderClient(this.builderName); - - final String builderName; - - String get builderUrl => '${_jsonEndpoint}/builders/$builderName'; - - Future getBuild(int buildNumber) async { - Map buildJson = await _getJson('$builderUrl/builds/$buildNumber'); - - return new BuildInfo( - builderName, - buildNumber, - _isGreen(buildJson), - _getBuildProperty(buildJson, 'git_revision') - ); - } - - Future> listRecentBuilds() async { - Map resp = await _getJson('$builderUrl/builds'); - return resp.keys.map(int.parse).toList(); - } -} - -Future _getJson(String url) async { - return JSON.decode((await http.get(url)).body); -} - -/// Properties are encoded as: -/// -/// { -/// "properties": [ -/// [ -/// "name1", -/// value1, -/// ... things we don't care about ... -/// ], -/// [ -/// "name2", -/// value2, -/// ... things we don't care about ... -/// ] -/// ] -/// } -dynamic _getBuildProperty(Map buildJson, String propertyName) { - List> properties = buildJson['properties']; - for (List property in properties) { - if (property[0] == propertyName) - return property[1]; - } - return null; -} - -/// Parses out whether the build was successful. -/// -/// Successes are encoded like this: -/// -/// "text": [ -/// "build", -/// "successful" -/// ] -/// -/// Exceptions are encoded like this: -/// -/// "text": [ -/// "exception", -/// "steps", -/// "exception", -/// "flutter build apk material_gallery" -/// ] -/// -/// Errors are encoded like this: -/// -/// "text": [ -/// "failed", -/// "steps", -/// "failed", -/// "flutter build ios simulator stocks" -/// ] -bool _isGreen(Map buildJson) { - if (buildJson['text'] == null || buildJson['text'].length < 2) { - stderr.writeln('WARNING: failed to parse "text" property out of build JSON'); - return false; - } - - return buildJson['text'][1] == 'successful'; -} diff --git a/agent/lib/src/commands/ci.dart b/agent/lib/src/commands/ci.dart index 2554a6de94..27652ee84e 100644 --- a/agent/lib/src/commands/ci.dart +++ b/agent/lib/src/commands/ci.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:args/args.dart'; @@ -11,7 +10,8 @@ import 'package:args/args.dart'; import '../adb.dart'; import '../agent.dart'; import '../firebase.dart'; -import '../framework.dart'; +import '../golem.dart'; +import '../runner.dart'; import '../utils.dart'; /// Agents periodically poll the server for more tasks. This sleep period is @@ -31,6 +31,10 @@ class ContinuousIntegrationCommand extends Command { @override Future run(ArgResults args) async { + return runAndCaptureAsyncStacks(() => _runContinuously(args)); + } + + Future _runContinuously(ArgResults args) async { // Perform one pre-flight round of checks and quit immediately if something // is wrong. AgentHealth health = await _performHealthChecks(); @@ -66,24 +70,15 @@ class ContinuousIntegrationCommand extends Command { CocoonTask task = await agent.reserveTask(); try { if (task != null) { + section('Task info:'); + print(' name : ${task.name}'); + print(' key : ${task.key ?? ""}'); + print(' revision : ${task.revision}'); + // Sync flutter outside of the task so it does not contribute to // the task timeout. await getFlutterAt(task.revision).timeout(const Duration(minutes: 10)); - - // No need to pass revision as repo syncing is done here. - List runnerArgs = [ - 'run', - '--task-name=${task.name}', - '--task-key=${task.key}', - ]; - - Process proc = await startProcess( - dartBin, - [config.runTaskFile.path]..addAll(runnerArgs) - ).timeout(const Duration(minutes: 1)); - - _logStandardStreams(task, proc); - await proc.exitCode.timeout(taskTimeoutWithGracePeriod); + await _runTask(task); } } catch(error, stackTrace) { print('ERROR: $error\n$stackTrace'); @@ -92,6 +87,7 @@ class ContinuousIntegrationCommand extends Command { } catch(error, stackTrace) { print('ERROR: $error\n$stackTrace'); } finally { + await _screensOff(); await forceQuitRunningProcesses(); } @@ -99,6 +95,29 @@ class ContinuousIntegrationCommand extends Command { } } + Future _runTask(CocoonTask task) async { + await runAndCaptureAsyncStacks(() async { + TaskResult result = await runTask(agent, task); + if (result.succeeded) { + await agent.updateTaskStatus(task.key, 'Succeeded'); + await _uploadDataToFirebase(task, result); + } else { + await agent.updateTaskStatus(task.key, 'Failed'); + } + }); + } + + Future _screensOff() async { + try { + for (Device device in await devices.discoverDevices()) { + device.sendToSleep(); + } + } catch(error, stackTrace) { + // Best effort only. + print('Failed to turn off screen: $error\n$stackTrace'); + } + } + Future _performHealthChecks() async { AgentHealth results = new AgentHealth(); try { @@ -138,31 +157,6 @@ class ContinuousIntegrationCommand extends Command { return results; } - /// Listens to standard output and upload logs to Cocoon in semi-realtime. - Future _logStandardStreams(CocoonTask task, Process proc) async { - StringBuffer buffer = new StringBuffer(); - - Future sendLog(String message, {bool flush: false}) async { - buffer.write(message); - print('[task runner] $message'); - // Send in chunks 100KB each, or upon request. - if (flush || buffer.length > 100000) { - String chunk = buffer.toString(); - buffer = new StringBuffer(); - await agent.uploadLogChunk(task, chunk); - } - } - - proc.stdout.transform(UTF8.decoder).listen((String s) { - sendLog(s); - }); - proc.stderr.transform(UTF8.decoder).listen((String s) { - sendLog(s); - }); - await proc.exitCode; - sendLog('Task execution finished', flush: true); - } - void _listenToShutdownSignals() { _streamSubscriptions.addAll([ ProcessSignal.SIGINT.watch().listen((_) { @@ -220,3 +214,33 @@ Future _scrapeRemoteAccessInfo() async { : 'Possible remote access IP: $ip' ); } + +Future _uploadDataToFirebase(CocoonTask task, TaskResult result) async { + List> golemData = >[]; + int golemRevision = await computeGolemRevision(); + + Map data = new Map.from(result.data); + + if (result.benchmarkScoreKeys != null) { + for (String scoreKey in result.benchmarkScoreKeys) { + String benchmarkName = '${task.name}.$scoreKey'; + if (registeredBenchmarkNames.contains(benchmarkName)) { + golemData.add({ + 'benchmark_name': benchmarkName, + 'golem_revision': golemRevision, + 'score': result.data[scoreKey], + }); + } + } + } + + data['__metadata__'] = { + 'success': result.succeeded, + 'revision': task.revision, + 'message': result.reason ?? 'N/A', + }; + + data['__golem__'] = golemData; + + await uploadToFirebase(task.name, data); +} diff --git a/agent/lib/src/commands/run.dart b/agent/lib/src/commands/run.dart deleted file mode 100644 index 6192a5175c..0000000000 --- a/agent/lib/src/commands/run.dart +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2016 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:args/args.dart'; -import 'package:stack_trace/stack_trace.dart'; - -import '../adb.dart'; -import '../agent.dart'; -import '../golem.dart'; -import '../firebase.dart'; -import '../framework.dart'; -import '../utils.dart'; - -/// Runs the agent once to perform a specific task and, optionally, report the -/// result back to Cocoon backend. -/// -/// The task is run at the currently synced revision of Flutter. This command -/// assumes that Flutter is already synced to the desired revision. -class RunCommand extends Command { - RunCommand(Agent agent) : super('run', agent); - - static final ArgParser argParser = new ArgParser() - ..addOption( - 'task-name', - help: '(required) The name of the task to run.' - ) - ..addOption( - 'task-key', - help: '(optional) The key of the task to update the status of in Cocoon. ' - 'It is only required if you want the runner to upload logs and ' - 'change task status in Cocoon.' - ); - - @override - Future run(ArgResults args) async { - CocoonTask task = new CocoonTask( - name: args['task-name'], - key: args['task-key'], - revision: await getCurrentFlutterRepoCommit() - ); - - section('Task info:'); - print(' name : ${task.name}'); - print(' key : ${task.key ?? ""}'); - print(' revision : ${task.revision}'); - - if (task.name == null || task.name == '') { - print('\n Incorrect command-line options. Usage:'); - print(argParser.usage); - exit(1); - } - - try { - await runAndCaptureAsyncStacks(() async { - // Load-balance tests across attached devices - await devices.chooseWorkingDevice(); - - BuildResult result = await agent.performTask(task); - if (task.key != null) { - if (result.succeeded) { - await agent.updateTaskStatus(task.key, 'Succeeded'); - await _uploadDataToFirebase(result); - } else { - await agent.updateTaskStatus(task.key, 'Failed'); - } - } - }).timeout(taskTimeout); - } catch(error, stackTrace) { - print('Caught: $error\n${new Chain.forTrace(stackTrace).terse}'); - if (task.key != null) - await agent.updateTaskStatus(task.key, 'Failed'); - exitCode = 1; - } finally { - await forceQuitRunningProcesses(); - } - - if (exitCode != 0) { - // Force-quitting child processes sometimes causes the Dart VM to fail to - // exit. So we give a 2-second grace period for any pending cleanups, then - // self-destruct. - await new Future.delayed(const Duration(seconds: 2)); - exit(exitCode); - } - } -} - -Future _uploadDataToFirebase(BuildResult result) async { - List> golemData = >[]; - - for (TaskResult taskResult in result.results) { - // TODO(devoncarew): We should also upload the fact that these tasks failed. - if (taskResult.data == null) - continue; - - Map data = new Map.from(taskResult.data.json); - - if (taskResult.data.benchmarkScoreKeys != null) { - for (String scoreKey in taskResult.data.benchmarkScoreKeys) { - String benchmarkName = '${taskResult.task.name}.$scoreKey'; - if (registeredBenchmarkNames.contains(benchmarkName)) { - golemData.add({ - 'benchmark_name': benchmarkName, - 'golem_revision': result.golemRevision, - 'score': taskResult.data.json[scoreKey], - }); - } - } - } - - data['__metadata__'] = { - 'success': taskResult.succeeded, - 'revision': taskResult.revision, - 'message': taskResult.message, - }; - - data['__golem__'] = golemData; - - uploadToFirebase(taskResult.task.name, data); - } -} diff --git a/agent/lib/src/framework.dart b/agent/lib/src/framework.dart deleted file mode 100644 index ff18ba235d..0000000000 --- a/agent/lib/src/framework.dart +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2016 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 'dart:convert'; - -import 'utils.dart'; - -/// Maximum amount of time a single task is allowed to take to run. -/// -/// If exceeded the task is considered to have failed. -const Duration taskTimeout = const Duration(minutes: 10); - -/// Slightly longer than [taskTimeout] that gives the task runner a chance to -/// clean-up before forcefully quitting it. -const Duration taskTimeoutWithGracePeriod = const Duration(minutes: 11); - -/// Represents a unit of work performed on the dashboard build box that can -/// succeed, fail and be retried independently of others. -abstract class Task { - Task(this.name); - - /// The name of the task that shows up in log messages. - /// - /// This should be as unique as possible to avoid confusion. - final String name; - - /// Performs actual work. - Future run(); -} - -/// Runs a queue of tasks; collects results. -class TaskRunner { - TaskRunner(this.revision, this.golemRevision, this.tasks); - - /// Flutter repository revision at which this runner ran. - final String revision; - - /// Revision _number_ as understood by Golem. - /// - /// See [computeGolemRevision]. - final int golemRevision; - final List tasks; - - Future run() async { - List results = []; - - for (Task task in tasks) { - section('Running task ${task.name}'); - TaskResult result; - try { - TaskResultData data = await task.run(); - if (data != null) - result = new TaskResult.success(task, revision, data); - else - result = new TaskResult.failure(task, revision, 'Task data missing'); - } catch (taskError, taskErrorStack) { - String message = '${task.name} failed: $taskError'; - if (taskErrorStack != null) { - message += '\n\n$taskErrorStack'; - } - print(''); - print(message); - result = new TaskResult.failure(task, revision, message); - } - results.add(result); - section('Task ${task.name} ${result.succeeded ? "succeeded" : "failed"}.'); - } - - return new BuildResult(golemRevision, results); - } -} - -/// All results accumulated from a build session. -class BuildResult { - BuildResult(this.golemRevision, this.results); - - final int golemRevision; - - /// Individual task results. - final List results; - - /// Whether the overall build failed. - /// - /// We consider the build as failed if at least one task fails. - bool get failed => results.any((TaskResult res) => res.failed); - - /// The opposite of [failed], i.e. all tasks succeeded. - bool get succeeded => !failed; - - /// The number of failed tasks. - int get failedTaskCount => results.fold(0, (int previous, TaskResult res) => previous + (res.failed ? 1 : 0)); -} - -/// A result of running a single task. -class TaskResult { - - /// Constructs a successful result. - TaskResult.success(this.task, this.revision, this.data) - : this.succeeded = true, - this.message = 'success'; - - /// Constructs an unsuccessful result. - TaskResult.failure(this.task, this.revision, this.message) - : this.succeeded = false, - this.data = null; - - /// The task that was run. - final Task task; - - /// Whether the task succeeded. - final bool succeeded; - - /// The revision of Flutter repo at which this result was generated. - final String revision; - - /// Data generated by the task that will be uploaded to Firebase. - final TaskResultData data; - - /// Whether the task failed. - bool get failed => !succeeded; - - /// Explains the result in a human-readable format. - final String message; -} - -/// Data generated by the task that will be uploaded to Firebase. -class TaskResultData { - TaskResultData(this.json, {this.benchmarkScoreKeys}) { - const JsonEncoder prettyJson = const JsonEncoder.withIndent(' '); - if (benchmarkScoreKeys != null) { - for (String key in benchmarkScoreKeys) { - if (!json.containsKey(key)) { - throw 'Invalid Golem score key "$key". It does not exist in task result data ${prettyJson.convert(json)}'; - } - } - } - } - - factory TaskResultData.fromFile(File file, {List benchmarkScoreKeys}) { - return new TaskResultData(JSON.decode(file.readAsStringSync()), benchmarkScoreKeys: benchmarkScoreKeys); - } - - /// Task-specific JSON data - final Map json; - - /// Keys in [json] that store scores that will be submitted to Golem. - /// - /// Each key is also part of a benchmark's name tracked by Golem. - /// A benchmark name is computed by combining [Task.name] with a key - /// separated by a dot. For example, if a task's name is - /// `"complex_layout__start_up"` and score key is - /// `"engineEnterTimestampMicros"`, the score will be submitted to Golem under - /// `"complex_layout__start_up.engineEnterTimestampMicros"`. - /// - /// This convention reduces the amount of configuration that needs to be done - /// to submit benchmark scores to Golem. - final List benchmarkScoreKeys; -} diff --git a/agent/lib/src/gallery.dart b/agent/lib/src/gallery.dart deleted file mode 100644 index a03c594ff6..0000000000 --- a/agent/lib/src/gallery.dart +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2016 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:convert'; -import 'dart:io'; - -import 'adb.dart'; -import 'framework.dart'; -import 'utils.dart'; - -Task createGalleryTransitionTest({ bool ios: false }) => new GalleryTransitionTest(ios: ios); - -class GalleryTransitionTest extends Task { - GalleryTransitionTest({ bool ios }) : - this.ios = ios, - super('flutter_gallery${ios ? "_ios" : ""}__transition_perf'); - - final bool ios; - - @override - Future run() async { - String deviceId = devices.workingDevice.deviceId; - Directory galleryDirectory = dir('${config.flutterDirectory.path}/examples/flutter_gallery'); - await inDirectory(galleryDirectory, () async { - await flutter('packages', options: ['get']); - - if (ios) { - // This causes an Xcode project to be created. - await flutter('build', options: ['ios', '--profile']); - } - - await flutter('drive', options: [ - '--profile', - '--trace-startup', - '-t', - 'test_driver/transitions_perf.dart', - '-d', - deviceId, - ]); - }); - - // Route paths contains slashes, which Firebase doesn't accept in keys, so we - // remove them. - Map original = JSON.decode(file('${galleryDirectory.path}/build/transition_durations.timeline.json').readAsStringSync()); - Map clean = new Map.fromIterable( - original.keys, - key: (String key) => key.replaceAll('/', ''), - value: (String key) => original[key] - ); - - return new TaskResultData(clean); - } -} diff --git a/agent/lib/src/hot_dev_cycle.dart b/agent/lib/src/hot_dev_cycle.dart deleted file mode 100644 index b0208afd2d..0000000000 --- a/agent/lib/src/hot_dev_cycle.dart +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2016 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:path/path.dart' as path; - -import 'adb.dart'; -import 'framework.dart'; -import 'utils.dart'; - -Task createHotDevCycleTest({ - String commit, - DateTime timestamp -}) => new HotDevCycleTask(commit, timestamp); - -class HotDevCycleTask extends Task { - HotDevCycleTask(this.commit, this.timestamp) - : super('hot_mode_dev_cycle__benchmark') { - assert(commit != null); - assert(timestamp != null); - } - - final String commit; - final DateTime timestamp; - - Directory get appDir => - dir(path.join(config.flutterDirectory.path, - 'examples/flutter_gallery')); - - File get benchmarkFile => - file(path.join(appDir.path, 'hot_benchmark.json')); - - final List benchmarkScoreKeys = [ - 'hotReloadMillisecondsToFrame', - 'hotRestartMillisecondsToFrame' - ]; - - @override - Future run() async { - Device device = devices.workingDevice; - device.unlock(); - rm(benchmarkFile); - await inDirectory(appDir, () async { - return await flutter( - 'run', - options: ['--hot', '-d', device.deviceId, '--benchmark'], - canFail: false - ); - }); - return new TaskResultData.fromFile(benchmarkFile, - benchmarkScoreKeys: benchmarkScoreKeys); - } -} diff --git a/agent/lib/src/perf_tests.dart b/agent/lib/src/perf_tests.dart deleted file mode 100644 index d2e86f2098..0000000000 --- a/agent/lib/src/perf_tests.dart +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2016 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:convert' show JSON; - -import 'adb.dart'; -import 'framework.dart'; -import 'utils.dart'; - -Task createComplexLayoutScrollPerfTest({ bool ios: false }) { - return new PerfTest( - 'complex_layout_scroll_perf${ios ? "_ios" : ""}__timeline_summary', - '${config.flutterDirectory.path}/dev/benchmarks/complex_layout', - 'test_driver/scroll_perf.dart', - 'complex_layout_scroll_perf', - ios: ios - ); -} - -Task createFlutterGalleryStartupTest({ bool ios: false }) { - return new StartupTest( - 'flutter_gallery${ios ? "_ios" : ""}__start_up', - '${config.flutterDirectory.path}/examples/flutter_gallery', - ios: ios - ); -} - -Task createComplexLayoutStartupTest({ bool ios: false }) { - return new StartupTest( - 'complex_layout${ios ? "_ios" : ""}__start_up', - '${config.flutterDirectory.path}/dev/benchmarks/complex_layout', - ios: ios - ); -} - -Task createFlutterGalleryBuildTest() { - return new BuildTest('flutter_gallery__build', '${config.flutterDirectory.path}/examples/flutter_gallery'); -} - -Task createComplexLayoutBuildTest() { - return new BuildTest('complex_layout__build', '${config.flutterDirectory.path}/dev/benchmarks/complex_layout'); -} - -/// Measure application startup performance. -class StartupTest extends Task { - static const Duration _startupTimeout = const Duration(minutes: 2); - - StartupTest(String name, this.testDirectory, { this.ios }) : super(name); - - final String testDirectory; - final bool ios; - - Future run() async { - return await inDirectory(testDirectory, () async { - String deviceId = devices.workingDevice.deviceId; - await flutter('packages', options: ['get']); - - if (ios) { - // This causes an Xcode project to be created. - await flutter('build', options: ['ios', '--profile']); - } - - await flutter('run', options: [ - '--profile', - '--trace-startup', - '-d', - deviceId - ]).timeout(_startupTimeout); - Map data = JSON.decode(file('$testDirectory/build/start_up_info.json').readAsStringSync()); - return new TaskResultData(data, benchmarkScoreKeys: [ - 'engineEnterTimestampMicros', - 'timeToFirstFrameMicros', - ]); - }); - } -} - -/// Measures application runtime performance, specifically per-frame -/// performance. -class PerfTest extends Task { - - PerfTest(String name, this.testDirectory, this.testTarget, this.timelineFileName, { this.ios }) - : super(name); - - final String testDirectory; - final String testTarget; - final String timelineFileName; - final bool ios; - - @override - Future run() { - return inDirectory(testDirectory, () async { - String deviceId = devices.workingDevice.deviceId; - await flutter('packages', options: ['get']); - - if (ios) { - // This causes an Xcode project to be created. - await flutter('build', options: ['ios', '--profile']); - } - - await flutter('drive', options: [ - '-v', - '--profile', - '--trace-startup', // Enables "endless" timeline event buffering. - '-t', - testTarget, - '-d', - deviceId, - ]); - Map data = JSON.decode(file('$testDirectory/build/${timelineFileName}.timeline_summary.json').readAsStringSync()); - return new TaskResultData(data, benchmarkScoreKeys: [ - 'average_frame_build_time_millis', - 'worst_frame_build_time_millis', - 'missed_frame_build_budget_count', - ]); - }); - } -} - -class BuildTest extends Task { - - BuildTest(String name, this.testDirectory) : super(name); - - final String testDirectory; - - Future run() async { - return await inDirectory(testDirectory, () async { - Device device = devices.workingDevice; - device.unlock(); - await flutter('packages', options: ['get']); - - var watch = new Stopwatch()..start(); - await flutter('build', options: [ - 'aot', - '--profile', - '--no-pub', - '--target-platform', 'android-arm' // Generate blobs instead of assembly. - ]); - watch.stop(); - - var vmisolateSize = file("$testDirectory/build/aot/snapshot_aot_vmisolate").lengthSync(); - var isolateSize = file("$testDirectory/build/aot/snapshot_aot_isolate").lengthSync(); - var instructionsSize = file("$testDirectory/build/aot/snapshot_aot_instr").lengthSync(); - var rodataSize = file("$testDirectory/build/aot/snapshot_aot_rodata").lengthSync(); - var totalSize = vmisolateSize + isolateSize + instructionsSize + rodataSize; - - Map data = { - 'aot_snapshot_build_millis': watch.elapsedMilliseconds, - 'aot_snapshot_size_vmisolate': vmisolateSize, - 'aot_snapshot_size_isolate': isolateSize, - 'aot_snapshot_size_instructions': instructionsSize, - 'aot_snapshot_size_rodata': rodataSize, - 'aot_snapshot_size_total': totalSize, - }; - return new TaskResultData(data, benchmarkScoreKeys: [ - 'aot_snapshot_build_millis', - 'aot_snapshot_size_vmisolate', - 'aot_snapshot_size_isolate', - 'aot_snapshot_size_instructions', - 'aot_snapshot_size_rodata', - 'aot_snapshot_size_total', - ]); - }); - } -} diff --git a/agent/lib/src/refresh.dart b/agent/lib/src/refresh.dart deleted file mode 100644 index a34592d601..0000000000 --- a/agent/lib/src/refresh.dart +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2016 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:path/path.dart' as path; - -import 'adb.dart'; -import 'benchmarks.dart'; -import 'framework.dart'; -import 'utils.dart'; - -Task createRefreshTest({ - String commit, - DateTime timestamp -}) => new EditRefreshTask(commit, timestamp); - -class EditRefreshTask extends Task { - EditRefreshTask(this.commit, this.timestamp) - : super('mega_gallery__refresh_time') { - assert(commit != null); - assert(timestamp != null); - } - - final String commit; - final DateTime timestamp; - - @override - Future run() async { - Device device = devices.workingDevice; - device.unlock(); - Benchmark benchmark = new EditRefreshBenchmark(commit, timestamp); - section(benchmark.name); - await runBenchmark(benchmark, iterations: 3, warmUpBenchmark: true); - return benchmark.bestResult; - } -} - -class EditRefreshBenchmark extends Benchmark { - EditRefreshBenchmark(this.commit, this.timestamp) - : super('edit refresh'); - - final String commit; - final DateTime timestamp; - - Directory get megaDir => dir(path.join(config.flutterDirectory.path, 'dev/benchmarks/mega_gallery')); - File get benchmarkFile => file(path.join(megaDir.path, 'refresh_benchmark.json')); - - @override - TaskResultData get lastResult => new TaskResultData.fromFile(benchmarkFile); - - Future init() { - return inDirectory(config.flutterDirectory, () async { - await dart(['dev/tools/mega_gallery.dart']); - }); - } - - @override - Future run() async { - Device device = devices.workingDevice; - rm(benchmarkFile); - int exitCode = await inDirectory(megaDir, () async { - return await flutter( - 'run', options: ['-d', device.deviceId, '--benchmark'], canFail: true - ); - }); - if (exitCode != 0) - return new Future.error(exitCode); - return addBuildInfo(benchmarkFile, timestamp: timestamp, expected: 200, commit: commit); - } -} diff --git a/agent/lib/src/runner.dart b/agent/lib/src/runner.dart new file mode 100644 index 0000000000..bf7e9dca0e --- /dev/null +++ b/agent/lib/src/runner.dart @@ -0,0 +1,188 @@ +// Copyright 2016 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:convert'; +import 'dart:io'; + +import 'package:vm_service_client/vm_service_client.dart'; + +import 'agent.dart'; +import 'utils.dart'; + +/// Slightly longer task timeout that gives the task runner a chance to +/// clean-up before forcefully quitting it. +const Duration taskTimeoutWithGracePeriod = const Duration(minutes: 11); + +/// Send logs in 10KB chunks. +const int _kLogChunkSize = 10000; + +/// A result of running a single task. +/// +/// In normal circumstances, even when a task fails, the result is parsed from +/// JSON returned by the task runner process via the VM service. However, if +/// things are completely out of control and the task runner process is +/// corrupted a failed result can be instantiated directly using +/// [TaskResult.failure] constructor. +class TaskResult { + /// Parses a task result from JSON. + TaskResult.parse(Map json) + : succeeded = json['success'], + data = json['data'], + benchmarkScoreKeys = json['benchmarkScoreKeys'] ?? const [], + reason = json['reason']; + + /// Constructs an unsuccessful result. + TaskResult.failure(this.reason) + : this.succeeded = false, + this.data = const {}, + this.benchmarkScoreKeys = const []; + + /// Whether the task succeeded. + final bool succeeded; + + /// Task-specific JSON data. + final Map data; + + /// Keys in [data] that store scores that will be submitted to Golem. + /// + /// Each key is also part of a benchmark's name tracked by Golem. + /// A benchmark name is computed by combining [Task.name] with a key + /// separated by a dot. For example, if a task's name is + /// `"complex_layout__start_up"` and score key is + /// `"engineEnterTimestampMicros"`, the score will be submitted to Golem under + /// `"complex_layout__start_up.engineEnterTimestampMicros"`. + /// + /// This convention reduces the amount of configuration that needs to be done + /// to submit benchmark scores to Golem. + final List benchmarkScoreKeys; + + /// Whether the task failed. + bool get failed => !succeeded; + + /// Explains the failure reason if [failed]. + final String reason; +} + +/// Runs a task in a separate Dart VM and collects the result using the VM +/// service protocol. +/// +/// [taskName] is the name of the task. The corresponding task executable is +/// expected to be found under `bin/tasks`. +Future runTask(Agent agent, CocoonTask task) async { + String devicelabPath = '${config.flutterDirectory.path}/dev/devicelab'; + String taskExecutable = 'bin/tasks/${task.name}.dart'; + + if (!file('$devicelabPath/$taskExecutable').existsSync()) + throw 'Executable Dart file not found: $taskExecutable'; + + int vmServicePort = await _findAvailablePort(); + Process runner; + await inDirectory(devicelabPath, () async { + runner = await startProcess(dartBin, [ + '--enable-vm-service=$vmServicePort', + '--no-pause-isolates-on-exit', + taskExecutable, + ]); + }); + + bool runnerFinished = false; + + runner.exitCode.then((_) { + runnerFinished = true; + }); + + StringBuffer buffer = new StringBuffer(); + + Future sendLog(String message, {bool flush: false}) async { + buffer.write(message); + print('[task runner] [${task.name}] $message'); + // Send a chunk at a time, or upon request. + if (flush || buffer.length > _kLogChunkSize) { + String chunk = buffer.toString(); + buffer = new StringBuffer(); + await agent.uploadLogChunk(task, chunk); + } + } + + var stdoutSub = runner.stdout.transform(UTF8.decoder).listen(sendLog); + var stderrSub = runner.stderr.transform(UTF8.decoder).listen(sendLog); + + String waitingFor = 'connection'; + try { + VMIsolate isolate = await _connectToRunnerIsolate(vmServicePort); + waitingFor = 'task completion'; + Map taskResult = + await isolate.invokeExtension('ext.cocoonRunTask').timeout(taskTimeoutWithGracePeriod); + waitingFor = 'task process to exit'; + await runner.exitCode.timeout(const Duration(seconds: 1)); + return new TaskResult.parse(taskResult); + } on TimeoutException catch (timeout) { + runner.kill(ProcessSignal.SIGINT); + return new TaskResult.failure( + 'Timeout waiting for $waitingFor: ${timeout.message}' + ); + } finally { + await stdoutSub.cancel(); + await stderrSub.cancel(); + sendLog('Task execution finished', flush: true); + if (!runnerFinished) + runner.kill(ProcessSignal.SIGKILL); + await forceQuitRunningProcesses(); + } +} + +Future _connectToRunnerIsolate(int vmServicePort) async { + String url = 'ws://localhost:$vmServicePort/ws'; + DateTime started = new DateTime.now(); + + // TODO(yjbanov): due to lack of imagination at the moment the handshake with + // the task process is very rudimentary and requires this small + // delay to let the task process open up the VM service port. + // Otherwise we almost always hit the non-ready case first and + // wait a whole 1 second, which is annoying. + await new Future.delayed(const Duration(milliseconds: 100)); + + while (true) { + try { + // Make sure VM server is up by successfully opening and closing a socket. + await (await WebSocket.connect(url)).close(); + + // Look up the isolate. + VMServiceClient client = new VMServiceClient.connect(url); + VM vm = await client.getVM(); + VMIsolate isolate = vm.isolates.single; + String response = await isolate.invokeExtension('ext.cocoonRunnerReady'); + if (response != 'ready') + throw 'not ready yet'; + return isolate; + } catch (error) { + const Duration connectionTimeout = const Duration(seconds: 2); + if (new DateTime.now().difference(started) > connectionTimeout) { + throw new TimeoutException( + 'Failed to connect to the task runner process', + connectionTimeout, + ); + } + print('VM service not ready yet: $error'); + const Duration pauseBetweenRetries = const Duration(milliseconds: 200); + print('Will retry in $pauseBetweenRetries.'); + await new Future.delayed(pauseBetweenRetries); + } + } +} + +Future _findAvailablePort() async { + int port = 20000; + while (true) { + try { + ServerSocket socket = + await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, port); + await socket.close(); + return port; + } catch (_) { + port++; + } + } +} diff --git a/agent/lib/src/size_tests.dart b/agent/lib/src/size_tests.dart deleted file mode 100644 index cb911cf7bb..0000000000 --- a/agent/lib/src/size_tests.dart +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2016 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 'framework.dart'; -import 'utils.dart'; - -Task createBasicMaterialAppSizeTest() => new BasicMaterialAppSizeTest(); - -class BasicMaterialAppSizeTest extends Task { - BasicMaterialAppSizeTest() : super('basic_material_app__size'); - - @override - Future run() async { - const sampleAppName = 'sample_flutter_app'; - Directory sampleDir = dir('${Directory.systemTemp.path}/$sampleAppName'); - - if (await sampleDir.exists()) - rrm(sampleDir); - - int apkSizeInBytes; - - await inDirectory(Directory.systemTemp, () async { - await flutter('create', options: [sampleAppName]); - - if (!(await sampleDir.exists())) - throw 'Failed to create sample Flutter app in ${sampleDir.path}'; - - await inDirectory(sampleDir, () async { - await flutter('packages', options: ['get']); - await flutter('build', options: ['clean']); - await flutter('build', options: ['apk', '--release']); - apkSizeInBytes = await file('${sampleDir.path}/build/app.apk').length(); - }); - }); - - return new TaskResultData({ - 'release_size_in_bytes': apkSizeInBytes - }, benchmarkScoreKeys: [ - 'release_size_in_bytes' - ]); - } -} diff --git a/agent/pubspec.yaml b/agent/pubspec.yaml index e07d96e722..00136cfff7 100644 --- a/agent/pubspec.yaml +++ b/agent/pubspec.yaml @@ -1,7 +1,7 @@ name: cocoon_agent version: 0.0.1 author: Flutter Authors -description: Scripts that run Cocoon checklist tasks +description: Flutter continuous integration agent (Cocoon agent) homepage: https://github.com/flutter/cocoon environment: @@ -9,21 +9,14 @@ environment: dependencies: args: ^0.13.0 - browser: ^0.10.0 - charted: ^0.4.0 - dart_to_js_script_rewriter: ^1.0.0 - firebase: ^0.6.6 firebase_rest: ^0.1.3 http: ^0.11.3 - intl: ^0.12.7 meta: ^0.12.1 path: ^1.3.0 stack_trace: ^1.6.5 + vm_service_client: ^0.2.0 yaml: ^2.1.10 dev_dependencies: test: ^0.12.13 collection: ^1.8.0 - -transformers: - - dart_to_js_script_rewriter diff --git a/app/bin/build_and_test.sh b/app/bin/build_and_test.sh index da1af910ed..a5afb6a3ee 100755 --- a/app/bin/build_and_test.sh +++ b/app/bin/build_and_test.sh @@ -10,7 +10,7 @@ if [ ! -f "pubspec.yaml" -a ! -f "app.yaml" ]; then fi rm -rf build -pub get --no-packages-dir +pub get pub run test pub build cp web/*.dart build/web/ diff --git a/db/schema.go b/db/schema.go index 7b477b4c85..4fb38c2724 100644 --- a/db/schema.go +++ b/db/schema.go @@ -115,9 +115,10 @@ type Agent struct { AgentID string IsHealthy bool HealthCheckTimestamp int64 - HealthDetails string // a human-readable printout of health details - AuthTokenHash []byte - Capabilities []string + // a human-readable printout of health details + HealthDetails string `datastore:"HealthDetails,noindex"` + AuthTokenHash []byte + Capabilities []string } // AgentStatus contains agent health status.