diff --git a/.cirrus.yml b/.cirrus.yml index 8316d09f416..d9f5eea4b3a 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -18,8 +18,10 @@ web_shard_template: &WEB_SHARD_TEMPLATE linux_shard_template: &LINUX_SHARD_TEMPLATE environment: # Some of the host-only devicelab tests are pretty involved and need a lot of RAM. - CPU: 2 - MEMORY: 8G + # In June 2020, the CPU and memory were increased so that + # web benchmarks (including gallery benchmarks) can be run successfully on Linux. + CPU: 4 + MEMORY: 16G script: - dart --enable-asserts ./dev/bots/test.dart diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/gallery/gallery_recorder.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/gallery/gallery_recorder.dart new file mode 100644 index 00000000000..5de16c54826 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/gallery/gallery_recorder.dart @@ -0,0 +1,48 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import 'package:gallery/benchmarks/gallery_automator.dart'; + +import 'package:macrobenchmarks/src/web/recorder.dart'; + +/// A recorder that measures frame building durations for the Gallery. +class GalleryRecorder extends WidgetRecorder { + GalleryRecorder({ + @required this.benchmarkName, + this.shouldRunPredicate, + this.testScrollsOnly = false, + }) : assert(testScrollsOnly || shouldRunPredicate != null), + super(name: benchmarkName, useCustomWarmUp: true); + + /// The name of the gallery benchmark to be run. + final String benchmarkName; + + /// A function that accepts the name of a demo and returns whether we should + /// run this demo in this benchmark. + final bool Function(String) shouldRunPredicate; + + /// Whether this benchmark only tests scrolling. + final bool testScrollsOnly; + + /// Whether we should continue recording. + @override + bool shouldContinue() => !_finished || profile.shouldContinue(); + + GalleryAutomator _galleryAutomator; + bool get _finished => _galleryAutomator?.finished ?? false; + + /// Creates the [GalleryAutomator] widget. + @override + Widget createWidget() { + _galleryAutomator = GalleryAutomator( + benchmarkName: benchmarkName, + shouldRunPredicate: shouldRunPredicate, + testScrollsOnly: testScrollsOnly, + stopWarmingUpCallback: profile.stopWarmingUp, + ); + return _galleryAutomator.createWidget(); + } +} diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart index 4bd4a4134a4..366dfe5ea80 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart @@ -235,7 +235,7 @@ abstract class SceneBuilderRecorder extends Recorder { window.onBeginFrame = (_) { try { - startMeasureFrame(); + startMeasureFrame(profile); onBeginFrame(); } catch (error, stackTrace) { profileCompleter.completeError(error, stackTrace); @@ -333,7 +333,10 @@ abstract class SceneBuilderRecorder extends Recorder { /// } /// ``` abstract class WidgetRecorder extends Recorder implements FrameRecorder { - WidgetRecorder({@required String name}) : super._(name, true); + WidgetRecorder({ + @required String name, + this.useCustomWarmUp = false, + }) : super._(name, true); /// Creates a widget to be benchmarked. /// @@ -349,12 +352,15 @@ abstract class WidgetRecorder extends Recorder implements FrameRecorder { Profile profile; Completer _runCompleter; + /// Whether to delimit warm-up frames in a custom way. + final bool useCustomWarmUp; + Stopwatch _drawFrameStopwatch; @override @mustCallSuper void frameWillDraw() { - startMeasureFrame(); + startMeasureFrame(profile); _drawFrameStopwatch = Stopwatch()..start(); } @@ -380,7 +386,7 @@ abstract class WidgetRecorder extends Recorder implements FrameRecorder { @override Future run() async { _runCompleter = Completer(); - final Profile localProfile = profile = Profile(name: name); + final Profile localProfile = profile = Profile(name: name, useCustomWarmUp: useCustomWarmUp); final _RecordingWidgetsBinding binding = _RecordingWidgetsBinding.ensureInitialized(); final Widget widget = createWidget(); @@ -460,7 +466,7 @@ abstract class WidgetBuildRecorder extends Recorder implements FrameRecorder { @mustCallSuper void frameWillDraw() { if (showWidget) { - startMeasureFrame(); + startMeasureFrame(profile); _drawFrameStopwatch = Stopwatch()..start(); } } @@ -538,7 +544,8 @@ class _WidgetBuildRecorderHostState extends State<_WidgetBuildRecorderHost> { /// calculations will only apply to the latest [_kMeasuredSampleCount] data /// points. class Timeseries { - Timeseries(this.name, this.isReported); + Timeseries(this.name, this.isReported, {this.useCustomWarmUp = false}) + : _warmUpFrameCount = useCustomWarmUp ? 0 : null; /// The label of this timeseries used for debugging and result inspection. final String name; @@ -553,6 +560,18 @@ class Timeseries { /// but that are too fine-grained to be useful for tracking on the dashboard. final bool isReported; + /// Whether to delimit warm-up frames in a custom way. + final bool useCustomWarmUp; + + /// The number of frames ignored as warm-up frames, used only + /// when [useCustomWarmUp] is true. + int _warmUpFrameCount; + + /// The number of frames ignored as warm-up frames. + int get warmUpFrameCount => useCustomWarmUp + ? _warmUpFrameCount + : count - _kMeasuredSampleCount; + /// List of all the values that have been recorded. /// /// This list has no limit. @@ -566,11 +585,15 @@ class Timeseries { /// /// See [TimeseriesStats] for more details. TimeseriesStats computeStats() { + final int finalWarmUpFrameCount = warmUpFrameCount; + + assert(finalWarmUpFrameCount >= 0 && finalWarmUpFrameCount < count); + // The first few values we simply discard and never look at. They're from the warm-up phase. - final List warmUpValues = _allValues.sublist(0, _allValues.length - _kMeasuredSampleCount); + final List warmUpValues = _allValues.sublist(0, finalWarmUpFrameCount); // Values we analyze. - final List candidateValues = _allValues.sublist(_allValues.length - _kMeasuredSampleCount); + final List candidateValues = _allValues.sublist(finalWarmUpFrameCount); // The average that includes outliers. final double dirtyAverage = _computeAverage(name, candidateValues); @@ -628,13 +651,16 @@ class Timeseries { } /// Adds a value to this timeseries. - void add(double value) { + void add(double value, {@required bool isWarmUpValue}) { if (value < 0.0) { throw StateError( 'Timeseries $name: negative metric values are not supported. Got: $value', ); } _allValues.add(value); + if (useCustomWarmUp && isWarmUpValue) { + _warmUpFrameCount += 1; + } } } @@ -748,11 +774,36 @@ class AnnotatedSample { /// Base class for a profile collected from running a benchmark. class Profile { - Profile({@required this.name}) : assert(name != null); + Profile({@required this.name, this.useCustomWarmUp = false}) + : assert(name != null), + _isWarmingUp = useCustomWarmUp; /// The name of the benchmark that produced this profile. final String name; + /// Whether to delimit warm-up frames in a custom way. + final bool useCustomWarmUp; + + /// Whether we are measuring warm-up frames currently. + bool get isWarmingUp => _isWarmingUp; + + bool _isWarmingUp; + + /// Stop the warm-up phase. + /// + /// Call this method only when [useCustomWarmUp] and [isWarmingUp] are both + /// true. + /// Call this method only once for each profile. + void stopWarmingUp() { + if (!useCustomWarmUp) { + throw Exception('`stopWarmingUp` should be used only when `useCustomWarmUp` is true.'); + } else if (!_isWarmingUp) { + throw Exception('Warm-up already stopped.'); + } else { + _isWarmingUp = false; + } + } + /// This data will be used to display cards in the Flutter Dashboard. final Map scoreData = {}; @@ -773,7 +824,10 @@ class Profile { /// Set [reported] to `false` to store the data, but not show it on the /// dashboard UI. void addDataPoint(String key, Duration duration, { @required bool reported }) { - scoreData.putIfAbsent(key, () => Timeseries(key, reported)).add(duration.inMicroseconds.toDouble()); + scoreData.putIfAbsent( + key, + () => Timeseries(key, reported, useCustomWarmUp: useCustomWarmUp), + ).add(duration.inMicroseconds.toDouble(), isWarmUpValue: isWarmingUp); } /// Decides whether the data collected so far is sufficient to stop, or @@ -1007,6 +1061,20 @@ class _RecordingWidgetsBinding extends BindingBase int _currentFrameNumber = 1; +/// If [_calledStartMeasureFrame] is true, we have called [startMeasureFrame] +/// but have not its pairing [endMeasureFrame] yet. +/// +/// This flag ensures that [startMeasureFrame] and [endMeasureFrame] are always +/// called in pairs, with [startMeasureFrame] followed by [endMeasureFrame]. +bool _calledStartMeasureFrame = false; + +/// Whether we are recording a measured frame. +/// +/// This flag ensures that we always stop measuring a frame if we +/// have started one. Because we want to skip warm-up frames, this flag +/// is necessary. +bool _isMeasuringFrame = false; + /// Adds a marker indication the beginning of frame rendering. /// /// This adds an event to the performance trace used to find measured frames in @@ -1014,22 +1082,53 @@ int _currentFrameNumber = 1; /// benchmarks are only interested in a subset of frames. For example, /// [WidgetBuildRecorder] only measures frames that build widgets, and ignores /// frames that clear the screen. -void startMeasureFrame() { - html.window.performance.mark('measured_frame_start#$_currentFrameNumber'); +/// +/// Warm-up frames are not measured. If [profile.isWarmingUp] is true, +/// this function does nothing. +void startMeasureFrame(Profile profile) { + if (_calledStartMeasureFrame) { + throw Exception('`startMeasureFrame` called twice in a row.'); + } + + _calledStartMeasureFrame = true; + + if (!profile.isWarmingUp) { + // Tell the browser to mark the beginning of the frame. + html.window.performance.mark('measured_frame_start#$_currentFrameNumber'); + + _isMeasuringFrame = true; + } } /// Signals the end of a measured frame. /// /// See [startMeasureFrame] for details on what this instrumentation is used /// for. +/// +/// Warm-up frames are not measured. If [profile.isWarmingUp] was true +/// when the corresponding [startMeasureFrame] was called, +/// this function does nothing. void endMeasureFrame() { - html.window.performance.mark('measured_frame_end#$_currentFrameNumber'); - html.window.performance.measure( - 'measured_frame', - 'measured_frame_start#$_currentFrameNumber', - 'measured_frame_end#$_currentFrameNumber', - ); - _currentFrameNumber += 1; + if (!_calledStartMeasureFrame) { + throw Exception('`startMeasureFrame` has not been called before calling `endMeasureFrame`'); + } + + _calledStartMeasureFrame = false; + + if (_isMeasuringFrame) { + // Tell the browser to mark the end of the frame, and measure the duration. + html.window.performance.mark('measured_frame_end#$_currentFrameNumber'); + html.window.performance.measure( + 'measured_frame', + 'measured_frame_start#$_currentFrameNumber', + 'measured_frame_end#$_currentFrameNumber', + ); + + // Increment the current frame number. + _currentFrameNumber += 1; + + _isMeasuringFrame = false; + } } /// A function that receives a benchmark value from the framework. diff --git a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart index 44406552949..4b672adcd94 100644 --- a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart +++ b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart @@ -10,6 +10,8 @@ import 'dart:math' as math; import 'package:macrobenchmarks/src/web/bench_text_layout.dart'; import 'package:macrobenchmarks/src/web/bench_text_out_of_picture_bounds.dart'; +import 'package:gallery/benchmarks/gallery_automator.dart' show DemoType, typeOfDemo; + import 'src/web/bench_build_material_checkbox.dart'; import 'src/web/bench_card_infinite_scroll.dart'; import 'src/web/bench_child_layers.dart'; @@ -22,12 +24,15 @@ import 'src/web/bench_paths.dart'; import 'src/web/bench_picture_recording.dart'; import 'src/web/bench_simple_lazy_text_scroll.dart'; import 'src/web/bench_text_out_of_picture_bounds.dart'; +import 'src/web/gallery/gallery_recorder.dart'; import 'src/web/recorder.dart'; typedef RecorderFactory = Recorder Function(); const bool isCanvasKit = bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false); +const String _galleryBenchmarkPrefix = 'gallery_v2'; + /// List of all benchmarks that run in the devicelab. /// /// When adding a new benchmark, add it to this map. Make sure that the name @@ -57,7 +62,16 @@ final Map benchmarks = { BenchTextCachedLayout.canvasBenchmarkName: () => BenchTextCachedLayout(useCanvas: true), BenchBuildColorsGrid.domBenchmarkName: () => BenchBuildColorsGrid.dom(), BenchBuildColorsGrid.canvasBenchmarkName: () => BenchBuildColorsGrid.canvas(), - } + + // The following benchmark is for the Flutter Gallery. + // This benchmark is failing when run with CanvasKit, so we skip it + // for now. + // TODO(yjbanov): https://github.com/flutter/flutter/issues/59082 + '${_galleryBenchmarkPrefix}_studies_perf': () => GalleryRecorder( + benchmarkName: '${_galleryBenchmarkPrefix}_studies_perf', + shouldRunPredicate: (String demo) => typeOfDemo(demo) == DemoType.study, + ), + }, }; final LocalBenchmarkServerClient _client = LocalBenchmarkServerClient(); @@ -83,29 +97,48 @@ Future _runBenchmark(String benchmarkName) async { return; } - try { - final Recorder recorder = recorderFactory(); - final Runner runner = recorder.isTracingEnabled && !_client.isInManualMode - ? Runner( - recorder: recorder, - setUpAllDidRun: () => _client.startPerformanceTracing(benchmarkName), - tearDownAllWillRun: _client.stopPerformanceTracing, - ) - : Runner(recorder: recorder); + await runZoned>( + () async { + final Recorder recorder = recorderFactory(); + final Runner runner = recorder.isTracingEnabled && !_client.isInManualMode + ? Runner( + recorder: recorder, + setUpAllDidRun: () => _client.startPerformanceTracing(benchmarkName), + tearDownAllWillRun: _client.stopPerformanceTracing, + ) + : Runner(recorder: recorder); - final Profile profile = await runner.run(); - if (!_client.isInManualMode) { - await _client.sendProfileData(profile); - } else { - _printResultsToScreen(profile); - print(profile); - } - } catch (error, stackTrace) { - if (_client.isInManualMode) { - rethrow; - } - await _client.reportError(error, stackTrace); - } + final Profile profile = await runner.run(); + if (!_client.isInManualMode) { + await _client.sendProfileData(profile); + } else { + _printResultsToScreen(profile); + print(profile); + } + }, + zoneSpecification: ZoneSpecification( + print: (Zone self, ZoneDelegate parent, Zone zone, String line) async { + if (_client.isInManualMode) { + parent.print(zone, '[$benchmarkName] $line'); + } else { + await _client.printToConsole(line); + } + }, + handleUncaughtError: ( + Zone self, + ZoneDelegate parent, + Zone zone, Object error, + StackTrace stackTrace, + ) async { + if (_client.isInManualMode) { + parent.print(zone, '[$benchmarkName] $error, $stackTrace'); + parent.handleUncaughtError(zone, error, stackTrace); + } else { + await _client.reportError(error, stackTrace); + } + }, + ), + ); } void _fallbackToManual(String error) { @@ -354,6 +387,17 @@ class LocalBenchmarkServerClient { ); } + /// Reports a message about the demo to the benchmark server. + Future printToConsole(String report) async { + _checkNotManualMode(); + await html.HttpRequest.request( + '/print-to-console', + method: 'POST', + mimeType: 'text/plain', + sendData: report, + ); + } + /// This is the same as calling [html.HttpRequest.request] but it doesn't /// crash on 404, which we use to detect `flutter run`. Future _requestXhr( diff --git a/dev/benchmarks/macrobenchmarks/pubspec.yaml b/dev/benchmarks/macrobenchmarks/pubspec.yaml index f0b9f3d6b0c..f8e788d23d2 100644 --- a/dev/benchmarks/macrobenchmarks/pubspec.yaml +++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml @@ -17,6 +17,12 @@ dependencies: # flutter update-packages --force-upgrade flutter_gallery_assets: 0.2.1 + # This is needed for web_benchmarks. + gallery: + git: + url: https://github.com/flutter/gallery.git + ref: 0c5ec2f285c1ec30b2ed15194bc8de755654a590 + archive: 2.0.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" args: 1.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -87,8 +93,173 @@ dev_dependencies: flutter: uses-material-design: true assets: - - packages/flutter_gallery_assets/food/butternut_squash_soup.png - - packages/flutter_gallery_assets/food/cherry_pie.png - - assets/999x1000.png + - packages/flutter_gallery_assets/food/butternut_squash_soup.png + - packages/flutter_gallery_assets/food/cherry_pie.png + - assets/999x1000.png + + # The following assets are required for running Flutter Gallery benchmarks. + - packages/flutter_gallery_assets/assets/icons/settings/settings_light.flr + - packages/flutter_gallery_assets/assets/icons/settings/settings_dark.flr + - packages/flutter_gallery_assets/assets/studies/shrine_card_dark.png + - packages/flutter_gallery_assets/assets/studies/starter_card.png + - packages/flutter_gallery_assets/assets/studies/starter_card_dark.png + - packages/flutter_gallery_assets/assets/studies/fortnightly_card_dark.png + - packages/flutter_gallery_assets/assets/studies/rally_card_dark.png + - packages/flutter_gallery_assets/assets/studies/fortnightly_card.png + - packages/flutter_gallery_assets/assets/studies/crane_card.png + - packages/flutter_gallery_assets/assets/studies/shrine_card.png + - packages/flutter_gallery_assets/assets/studies/crane_card_dark.png + - packages/flutter_gallery_assets/assets/studies/rally_card.png + - packages/flutter_gallery_assets/assets/logo/flutter_logo.png + - packages/flutter_gallery_assets/assets/logo/flutter_logo_color.png + - packages/flutter_gallery_assets/assets/icons/cupertino/cupertino.png + - packages/flutter_gallery_assets/assets/icons/material/material.png + - packages/flutter_gallery_assets/assets/icons/reference/reference.png + - packages/flutter_gallery_assets/assets/demos/bottom_navigation_background.png + - packages/flutter_gallery_assets/fonts/GalleryIcons.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Merriweather-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Eczar-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Montserrat-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Rubik-Bold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Merriweather-Light.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/RobotoCondensed-Bold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Raleway-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Raleway-SemiBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/RobotoMono-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-ExtraBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-Bold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Oswald-SemiBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Oswald-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-SemiBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Raleway-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Montserrat-Bold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Merriweather-BoldItalic.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Raleway-Light.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Rubik-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Montserrat-SemiBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/RobotoCondensed-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Montserrat-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Rubik-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Eczar-SemiBold.ttf + - packages/flutter_gallery_assets/crane/destinations/eat_1.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_2.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_3.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_4.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_5.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_6.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_7.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_8.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_9.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_10.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_0.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_1.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_2.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_3.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_4.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_5.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_6.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_7.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_8.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_9.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_10.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_11.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_12.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_13.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_0.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_1.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_2.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_3.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_4.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_5.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_6.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_7.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_8.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_9.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_10.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_11.jpg + - packages/flutter_gallery_assets/crane/logo/logo.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_army.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_bees.jpg + - packages/flutter_gallery_assets/fortnightly/fortnightly_chart.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_fabrics.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_feminists.jpg + - packages/flutter_gallery_assets/fortnightly/fortnightly_gas.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_healthcare.jpg + - packages/flutter_gallery_assets/fortnightly/fortnightly_stocks.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_title.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_war.png + - packages/flutter_gallery_assets/places/india_chennai_flower_market.png + - packages/flutter_gallery_assets/places/india_thanjavur_market.png + - packages/flutter_gallery_assets/places/india_tanjore_bronze_works.png + - packages/flutter_gallery_assets/places/india_tanjore_market_merchant.png + - packages/flutter_gallery_assets/places/india_tanjore_thanjavur_temple.png + - packages/flutter_gallery_assets/places/india_pondicherry_salt_farm.png + - packages/flutter_gallery_assets/places/india_chennai_highway.png + - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png + - packages/flutter_gallery_assets/places/india_tanjore_thanjavur_temple_carvings.png + - packages/flutter_gallery_assets/places/india_chettinad_produce.png + - packages/flutter_gallery_assets/places/india_tanjore_market_technology.png + - packages/flutter_gallery_assets/places/india_pondicherry_beach.png + - packages/flutter_gallery_assets/places/india_pondicherry_fisherman.png + - packages/flutter_gallery_assets/splash_effects/splash_effect_1.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_2.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_3.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_4.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_5.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_6.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_7.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_8.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_9.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_10.gif + - packages/rally_assets/logo.png + - packages/rally_assets/thumb.png + - packages/shrine_images/diamond.png + - packages/shrine_images/slanted_menu.png + - packages/shrine_images/0-0.jpg + - packages/shrine_images/1-0.jpg + - packages/shrine_images/2-0.jpg + - packages/shrine_images/3-0.jpg + - packages/shrine_images/4-0.jpg + - packages/shrine_images/5-0.jpg + - packages/shrine_images/6-0.jpg + - packages/shrine_images/7-0.jpg + - packages/shrine_images/8-0.jpg + - packages/shrine_images/9-0.jpg + - packages/shrine_images/10-0.jpg + - packages/shrine_images/11-0.jpg + - packages/shrine_images/12-0.jpg + - packages/shrine_images/13-0.jpg + - packages/shrine_images/14-0.jpg + - packages/shrine_images/15-0.jpg + - packages/shrine_images/16-0.jpg + - packages/shrine_images/17-0.jpg + - packages/shrine_images/18-0.jpg + - packages/shrine_images/19-0.jpg + - packages/shrine_images/20-0.jpg + - packages/shrine_images/21-0.jpg + - packages/shrine_images/22-0.jpg + - packages/shrine_images/23-0.jpg + - packages/shrine_images/24-0.jpg + - packages/shrine_images/25-0.jpg + - packages/shrine_images/26-0.jpg + - packages/shrine_images/27-0.jpg + - packages/shrine_images/28-0.jpg + - packages/shrine_images/29-0.jpg + - packages/shrine_images/30-0.jpg + - packages/shrine_images/31-0.jpg + - packages/shrine_images/32-0.jpg + - packages/shrine_images/33-0.jpg + - packages/shrine_images/34-0.jpg + - packages/shrine_images/35-0.jpg + - packages/shrine_images/36-0.jpg + - packages/shrine_images/37-0.jpg + + # The following font is required for running Flutter Gallery benchmarks. + fonts: + - family: GalleryIcons + fonts: + - asset: packages/flutter_gallery_assets/fonts/GalleryIcons.ttf # PUBSPEC CHECKSUM: 8ced diff --git a/dev/devicelab/lib/tasks/web_benchmarks.dart b/dev/devicelab/lib/tasks/web_benchmarks.dart index a45fd37ebc7..0994351e20c 100644 --- a/dev/devicelab/lib/tasks/web_benchmarks.dart +++ b/dev/devicelab/lib/tasks/web_benchmarks.dart @@ -99,6 +99,13 @@ Future runWebBenchmark({ @required bool useCanvasKit }) async { profileData.complete(collectedProfiles); return Response.notFound('Finished running benchmarks.'); } + } else if (request.requestedUri.path.endsWith('/print-to-console')) { + // A passthrough used by + // `dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart` + // to print information. + final String message = await request.readAsString(); + print('[Gallery] $message'); + return Response.ok('Reported.'); } else { return Response.notFound( 'This request is not handled by the profile-data handler.');