| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Helper code for setting up SL4F, running performance tests, and |
| // uploading the tests' results to the Catapult performance dashboard. |
| |
| import 'dart:io' show File, Platform; |
| |
| import 'package:args/args.dart'; |
| import 'package:logging/logging.dart'; |
| import 'package:sl4f/sl4f.dart' as sl4f; |
| import 'package:sl4f/trace_processing.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'summarize.dart' show summarizeFuchsiaPerfFiles, writeFuchsiaPerfJson; |
| |
| void enableLoggingOutput() { |
| // This is necessary to get information about the commands the tests have |
| // run, and to get information about what they outputted on stdout/stderr |
| // if they fail. |
| Logger.root |
| ..level = Level.ALL |
| ..onRecord.listen((rec) => print('[${rec.level}]: ${rec.message}')); |
| } |
| |
| void runShardTests(List<String> args, List<void Function()> tests) { |
| // The Dart test library is supposed to support sharding, but its |
| // sharding options do not seem to be accessible when running Dart tests |
| // on Fuchsia, so we reimplement the same options here. |
| final parser = ArgParser() |
| ..addOption('total-shards', |
| help: 'Number of total shards to split test suites into.', |
| defaultsTo: '1') |
| ..addOption('shard-index', |
| help: 'Which shard of test suites to run.', defaultsTo: '0'); |
| final argResults = parser.parse(args); |
| |
| int totalShards = int.parse(argResults['total-shards']); |
| int shardIndex = int.parse(argResults['shard-index']); |
| for (var i = 0; i < tests.length; i++) { |
| if (i % totalShards == shardIndex) { |
| tests[i](); |
| } |
| } |
| } |
| |
| class PerfTestHelper { |
| sl4f.Sl4f sl4fDriver; |
| sl4f.Performance performance; |
| sl4f.Dump dump; |
| sl4f.Storage storage; |
| sl4f.Component component; |
| |
| Future<void> setUp() async { |
| sl4fDriver = sl4f.Sl4f.fromEnvironment(); |
| await sl4fDriver.startServer(); |
| addTearDown(() async { |
| await sl4fDriver.stopServer(); |
| sl4fDriver.close(); |
| }); |
| performance = sl4f.Performance(sl4fDriver); |
| dump = sl4f.Dump(); |
| storage = sl4f.Storage(sl4fDriver); |
| component = sl4f.Component(sl4fDriver); |
| } |
| |
| static Future<PerfTestHelper> make() async { |
| final helper = PerfTestHelper(); |
| await helper.setUp(); |
| return helper; |
| } |
| |
| // Takes a fuchsiaperf file, specified as a remote filename |
| // (i.e. the path of the file on the Fuchsia device under test). |
| // Publishes this file as results for the current test. |
| Future<void> processResults(String resultsFile) async { |
| final localResultsFile = |
| await storage.dumpFile(resultsFile, 'results', 'fuchsiaperf.json'); |
| |
| await performance.convertResults('runtime_deps/catapult_converter', |
| localResultsFile, Platform.environment); |
| } |
| |
| // Takes a set of "raw data" fuchsiaperf files, specified as local |
| // files. Generates a "summary" version of that data, following the |
| // process described in summarize.dart, and publishes that as |
| // results for the current test. |
| Future<void> processResultsSummarized(List<File> jsonFiles) async { |
| final jsonSummaryData = summarizeFuchsiaPerfFiles(jsonFiles); |
| |
| final File jsonSummaryFile = dump.createFile('results', 'fuchsiaperf.json'); |
| await writeFuchsiaPerfJson(jsonSummaryFile, jsonSummaryData); |
| |
| await performance.convertResults('runtime_deps/catapult_converter', |
| jsonSummaryFile, Platform.environment); |
| } |
| |
| // Runs a command over SSH and publishes its output as performance |
| // test results. |
| // |
| // The command to run is specified via a function that takes a |
| // filename as an argument and returns a shell command string. The |
| // filename is for the results file that the command will write its |
| // results to, in fuchsiaperf.json format. |
| Future<void> runTestCommand( |
| String Function(String resultsFilename) getCommand) async { |
| // Make a filename that is very likely to be unique. Using a |
| // unique filename should not be strictly necessary, but it should |
| // avoid potential problems. We do not expect performance tests |
| // to be run concurrently on the Infra builders, but it may be |
| // useful to do so locally for development purposes when we don't |
| // care about the performance results. |
| final timestamp = DateTime.now().microsecondsSinceEpoch; |
| final resultsFile = '/tmp/perf_results_$timestamp.fuchsiaperf.json'; |
| final command = getCommand(resultsFile); |
| final result = await sl4fDriver.ssh.run(command); |
| expect(result.exitCode, equals(0)); |
| try { |
| await processResults(resultsFile); |
| } finally { |
| // Clean up: remove the temporary file. |
| final result = await sl4fDriver.ssh.run('rm -f $resultsFile'); |
| expect(result.exitCode, equals(0)); |
| } |
| } |
| } |
| |
| final _log = Logger('TouchInputLatencyMetricsProcessor'); |
| |
| // Custom MetricsProcessor for input latency tests that rely on trace events |
| // tagged with the "touch-input-test" category. |
| List<TestCaseResults> touchInputLatencyMetricsProcessor( |
| Model model, Map<String, dynamic> extraArgs) { |
| final inputLatency = getArgValuesFromEvents<num>( |
| filterEventsTyped<InstantEvent>(getAllEvents(model), |
| category: 'touch-input-test', name: 'input_latency'), |
| 'elapsed_time') |
| .map((t) => t.toDouble()) |
| .toList(); |
| |
| if (inputLatency.length != 1) { |
| throw ArgumentError("touch-input-test didn't log an elapsed time."); |
| } |
| |
| _log.info('Elapsed time: ${inputLatency.first} ns.'); |
| |
| final List<TestCaseResults> testCaseResults = [ |
| TestCaseResults('touch_input_latency', Unit.nanoseconds, inputLatency) |
| ]; |
| |
| return testCaseResults; |
| } |