Unverified Commit 4fea3ef5 authored by joshualitt's avatar joshualitt Committed by GitHub

Migrate benchmarks to package:web (#126848)

parent 2d6c67f4
......@@ -2,10 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:html' as html;
import 'dart:js_interop';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:web/web.dart' as web;
import 'recorder.dart';
// Measures the performance of image decoding.
......@@ -43,8 +45,11 @@ class BenchImageDecoding extends RawRecorder {
return;
}
for (final String imageUrl in _imageUrls) {
final html.Body image = await html.window.fetch(imageUrl) as html.Body;
_imageData.add((await image.arrayBuffer() as ByteBuffer).asUint8List());
final Future<JSAny?> fetchFuture = web.window.fetch(imageUrl.toJS).toDart;
final web.Body image = (await fetchFuture)! as web.Body;
final Future<JSAny?> imageFuture = image.arrayBuffer().toDart;
final JSArrayBuffer imageBuffer = (await imageFuture)! as JSArrayBuffer;
_imageData.add(imageBuffer.toDart.asUint8List());
}
}
......
......@@ -3,30 +3,32 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:html' as html;
import 'dart:js_interop';
import 'package:flutter/material.dart';
import 'package:web/web.dart' as web;
// TODO(mdebbar): flutter/flutter#55000 Remove this conditional import once
// web-only dart:ui_web APIs are exposed from a dedicated place.
import 'platform_views/non_web.dart'
if (dart.library.html) 'platform_views/web.dart';
if (dart.library.js_interop) 'platform_views/web.dart';
import 'recorder.dart';
const String benchmarkViewType = 'benchmark_element';
void _registerFactory() {
platformViewRegistry.registerViewFactory(benchmarkViewType, (int viewId) {
final html.Element htmlElement = html.DivElement();
htmlElement.id = '${benchmarkViewType}_$viewId';
htmlElement.innerText = 'Google';
final web.HTMLElement htmlElement = web.document.createElement('div'.toJS)
as web.HTMLDivElement;
htmlElement.id = '${benchmarkViewType}_$viewId'.toJS;
htmlElement.innerText = 'Google'.toJS;
htmlElement.style
..width = '100%'
..height = '100%'
..color = 'black'
..backgroundColor = 'rgba(0, 255, 0, .5)'
..textAlign = 'center'
..border = '1px solid black';
..setProperty('width'.toJS, '100%'.toJS)
..setProperty('height'.toJS, '100%'.toJS)
..setProperty('color'.toJS, 'black'.toJS)
..setProperty('backgroundColor'.toJS, 'rgba(0, 255, 0, .5)'.toJS)
..setProperty('textAlign'.toJS, 'center'.toJS)
..setProperty('border'.toJS, '1px solid black'.toJS);
return htmlElement;
});
}
......
......@@ -3,8 +3,11 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:html' as html;
import 'dart:js_util' as js_util;
import 'dart:js_interop';
// The analyzer currently thinks `js_interop_unsafe` is unused, but it is used
// for `JSObject.[]=`.
// ignore: unused_import
import 'dart:js_interop_unsafe';
import 'dart:math' as math;
import 'dart:ui';
......@@ -15,6 +18,7 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:web/web.dart' as web;
/// The default number of samples from warm-up iterations.
///
......@@ -1253,7 +1257,7 @@ void startMeasureFrame(Profile profile) {
if (!profile.isWarmingUp) {
// Tell the browser to mark the beginning of the frame.
html.window.performance.mark('measured_frame_start#$_currentFrameNumber');
web.window.performance.mark('measured_frame_start#$_currentFrameNumber'.toJS);
_isMeasuringFrame = true;
}
......@@ -1276,11 +1280,11 @@ void endMeasureFrame() {
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',
web.window.performance.mark('measured_frame_end#$_currentFrameNumber'.toJS);
web.window.performance.measure(
'measured_frame'.toJS,
'measured_frame_start#$_currentFrameNumber'.toJS,
'measured_frame_end#$_currentFrameNumber'.toJS,
);
// Increment the current frame number.
......@@ -1310,7 +1314,10 @@ void registerEngineBenchmarkValueListener(String name, EngineBenchmarkValueListe
if (_engineBenchmarkListeners.isEmpty) {
// The first listener is being registered. Register the global listener.
js_util.setProperty(html.window, '_flutter_internal_on_benchmark', _dispatchEngineBenchmarkValue);
web.window['_flutter_internal_on_benchmark'.toJS] =
// Upcast to [Object] to export.
// ignore: unnecessary_cast
(_dispatchEngineBenchmarkValue as Object).toJS;
}
_engineBenchmarkListeners[name] = listener;
......@@ -1321,7 +1328,7 @@ void stopListeningToEngineBenchmarkValues(String name) {
_engineBenchmarkListeners.remove(name);
if (_engineBenchmarkListeners.isEmpty) {
// The last listener unregistered. Remove the global listener.
js_util.setProperty(html.window, '_flutter_internal_on_benchmark', null);
web.window['_flutter_internal_on_benchmark'.toJS] = null;
}
}
......
......@@ -4,9 +4,11 @@
import 'dart:async';
import 'dart:convert' show json;
import 'dart:html' as html;
import 'dart:js_interop';
import 'dart:math' as math;
import 'package:web/web.dart' as web;
import 'src/web/bench_build_image.dart';
import 'src/web/bench_build_material_checkbox.dart';
import 'src/web/bench_card_infinite_scroll.dart';
......@@ -95,7 +97,7 @@ Future<void> main() async {
}
await _runBenchmark(nextBenchmark);
html.window.location.reload();
web.window.location.reload();
}
Future<void> _runBenchmark(String benchmarkName) async {
......@@ -150,8 +152,20 @@ Future<void> _runBenchmark(String benchmarkName) async {
);
}
extension WebHTMLElementExtension on web.HTMLElement {
void appendHtml(String html) {
final web.HTMLDivElement div = web.document.createElement('div'.toJS) as
web.HTMLDivElement;
div.innerHTML = html.toJS;
final web.DocumentFragment fragment = web.document.createDocumentFragment();
fragment.append(div);
web.document.adoptNode(fragment);
append(fragment);
}
}
void _fallbackToManual(String error) {
html.document.body!.appendHtml('''
web.document.body!.appendHtml('''
<div id="manual-panel">
<h3>$error</h3>
......@@ -166,28 +180,29 @@ void _fallbackToManual(String error) {
}
</ul>
</div>
''', validator: html.NodeValidatorBuilder()..allowHtml5()..allowInlineStyles());
''');
for (final String benchmarkName in benchmarks.keys) {
final html.Element button = html.document.querySelector('#$benchmarkName')!;
button.addEventListener('click', (_) {
final html.Element? manualPanel = html.document.querySelector('#manual-panel');
final web.Element button = web.document.querySelector('#$benchmarkName'.toJS)!;
button.addEventListener('click'.toJS, (JSObject _) {
final web.Element? manualPanel =
web.document.querySelector('#manual-panel'.toJS);
manualPanel?.remove();
_runBenchmark(benchmarkName);
});
}.toJS);
}
}
/// Visualizes results on the Web page for manual inspection.
void _printResultsToScreen(Profile profile) {
html.document.body!.remove();
html.document.body = html.BodyElement();
html.document.body!.appendHtml('<h2>${profile.name}</h2>');
web.document.body!.remove();
web.document.body = web.document.createElement('body'.toJS) as web.HTMLBodyElement;
web.document.body!.appendHtml('<h2>${profile.name}</h2>');
profile.scoreData.forEach((String scoreKey, Timeseries timeseries) {
html.document.body!.appendHtml('<h2>$scoreKey</h2>');
html.document.body!.appendHtml('<pre>${timeseries.computeStats()}</pre>');
html.document.body!.append(TimeseriesVisualization(timeseries).render());
web.document.body!.appendHtml('<h2>$scoreKey</h2>');
web.document.body!.appendHtml('<pre>${timeseries.computeStats()}</pre>');
web.document.body!.append(TimeseriesVisualization(timeseries).render());
});
}
......@@ -195,15 +210,15 @@ void _printResultsToScreen(Profile profile) {
class TimeseriesVisualization {
TimeseriesVisualization(this._timeseries) {
_stats = _timeseries.computeStats();
_canvas = html.CanvasElement();
_screenWidth = html.window.screen!.width!;
_canvas.width = _screenWidth;
_canvas.height = (_kCanvasHeight * html.window.devicePixelRatio).round();
_canvas = web.document.createElement('canvas'.toJS) as web.HTMLCanvasElement;
_screenWidth = web.window.screen.width.toDart.toInt();
_canvas.width = _screenWidth.toJS;
_canvas.height = (_kCanvasHeight * web.window.devicePixelRatio.toDart).round().toJS;
_canvas.style
..width = '100%'
..height = '${_kCanvasHeight}px'
..outline = '1px solid green';
_ctx = _canvas.context2D;
..setProperty('width'.toJS, '100%'.toJS)
..setProperty('height'.toJS, '${_kCanvasHeight}px'.toJS)
..setProperty('outline'.toJS, '1px solid green'.toJS);
_ctx = _canvas.getContext('2d'.toJS)! as web.CanvasRenderingContext2D;
// The amount of vertical space available on the chart. Because some
// outliers can be huge they can dwarf all the useful values. So we
......@@ -218,8 +233,8 @@ class TimeseriesVisualization {
final Timeseries _timeseries;
late TimeseriesStats _stats;
late html.CanvasElement _canvas;
late html.CanvasRenderingContext2D _ctx;
late web.HTMLCanvasElement _canvas;
late web.CanvasRenderingContext2D _ctx;
late int _screenWidth;
// Used to normalize benchmark values to chart height.
......@@ -235,15 +250,15 @@ class TimeseriesVisualization {
/// A utility for drawing lines.
void drawLine(num x1, num y1, num x2, num y2) {
_ctx.beginPath();
_ctx.moveTo(x1, y1);
_ctx.lineTo(x2, y2);
_ctx.moveTo(x1.toJS, y1.toJS);
_ctx.lineTo(x2.toJS, y2.toJS);
_ctx.stroke();
}
/// Renders the timeseries into a `<canvas>` and returns the canvas element.
html.CanvasElement render() {
_ctx.translate(0, _kCanvasHeight * html.window.devicePixelRatio);
_ctx.scale(1, -html.window.devicePixelRatio);
web.HTMLCanvasElement render() {
_ctx.translate(0.toJS, (_kCanvasHeight * web.window.devicePixelRatio.toDart).toJS);
_ctx.scale(1.toJS, (-web.window.devicePixelRatio.toDart).toJS);
final double barWidth = _screenWidth / _stats.samples.length;
double xOffset = 0;
......@@ -252,40 +267,42 @@ class TimeseriesVisualization {
if (sample.isWarmUpValue) {
// Put gray background behind warm-up samples.
_ctx.fillStyle = 'rgba(200,200,200,1)';
_ctx.fillRect(xOffset, 0, barWidth, _normalized(_maxValueChartRange));
_ctx.fillStyle = 'rgba(200,200,200,1)'.toJS;
_ctx.fillRect(xOffset.toJS, 0.toJS, barWidth.toJS,
_normalized(_maxValueChartRange).toJS);
}
if (sample.magnitude > _maxValueChartRange) {
// The sample value is so big it doesn't fit on the chart. Paint it purple.
_ctx.fillStyle = 'rgba(100,50,100,0.8)';
_ctx.fillStyle = 'rgba(100,50,100,0.8)'.toJS;
} else if (sample.isOutlier) {
// The sample is an outlier, color it light red.
_ctx.fillStyle = 'rgba(255,50,50,0.6)';
_ctx.fillStyle = 'rgba(255,50,50,0.6)'.toJS;
} else {
// A non-outlier sample, color it light blue.
_ctx.fillStyle = 'rgba(50,50,255,0.6)';
_ctx.fillStyle = 'rgba(50,50,255,0.6)'.toJS;
}
_ctx.fillRect(xOffset, 0, barWidth - 1, _normalized(sample.magnitude));
_ctx.fillRect(xOffset.toJS, 0.toJS, (barWidth - 1).toJS,
_normalized(sample.magnitude).toJS);
xOffset += barWidth;
}
// Draw a horizontal solid line corresponding to the average.
_ctx.lineWidth = 1;
_ctx.lineWidth = 1.toJS;
drawLine(0, _normalized(_stats.average), _screenWidth, _normalized(_stats.average));
// Draw a horizontal dashed line corresponding to the outlier cut off.
_ctx.setLineDash(<num>[5, 5]);
_ctx.setLineDash(<JSAny?>[5.toJS, 5.toJS].toJS);
drawLine(0, _normalized(_stats.outlierCutOff), _screenWidth, _normalized(_stats.outlierCutOff));
// Draw a light red band that shows the noise (1 stddev in each direction).
_ctx.fillStyle = 'rgba(255,50,50,0.3)';
_ctx.fillStyle = 'rgba(255,50,50,0.3)'.toJS;
_ctx.fillRect(
0,
_normalized(_stats.average * (1 - _stats.noise)),
_screenWidth,
_normalized(2 * _stats.average * _stats.noise),
0.toJS,
_normalized(_stats.average * (1 - _stats.noise)).toJS,
_screenWidth.toJS,
_normalized(2 * _stats.average * _stats.noise).toJS,
);
return _canvas;
......@@ -313,7 +330,7 @@ class LocalBenchmarkServerClient {
/// Returns [kManualFallback] if local server is not available (uses 404 as a
/// signal).
Future<String> requestNextBenchmark() async {
final html.HttpRequest request = await _requestXhr(
final web.XMLHttpRequest request = await _requestXhr(
'/next-benchmark',
method: 'POST',
mimeType: 'application/json',
......@@ -323,13 +340,13 @@ class LocalBenchmarkServerClient {
// 404 is expected in the following cases:
// - The benchmark is ran using plain `flutter run`, which does not provide "next-benchmark" handler.
// - We ran all benchmarks and the benchmark is telling us there are no more benchmarks to run.
if (request.status == 404) {
if (request.status.toDart != 200) {
isInManualMode = true;
return kManualFallback;
}
isInManualMode = false;
return request.responseText!;
return request.responseText.toDart;
}
void _checkNotManualMode() {
......@@ -345,7 +362,7 @@ class LocalBenchmarkServerClient {
/// DevTools Protocol.
Future<void> startPerformanceTracing(String benchmarkName) async {
_checkNotManualMode();
await html.HttpRequest.request(
await _requestXhr(
'/start-performance-tracing?label=$benchmarkName',
method: 'POST',
mimeType: 'application/json',
......@@ -355,7 +372,7 @@ class LocalBenchmarkServerClient {
/// Stops the performance tracing session started by [startPerformanceTracing].
Future<void> stopPerformanceTracing() async {
_checkNotManualMode();
await html.HttpRequest.request(
await _requestXhr(
'/stop-performance-tracing',
method: 'POST',
mimeType: 'application/json',
......@@ -366,13 +383,13 @@ class LocalBenchmarkServerClient {
/// server.
Future<void> sendProfileData(Profile profile) async {
_checkNotManualMode();
final html.HttpRequest request = await html.HttpRequest.request(
final web.XMLHttpRequest request = await _requestXhr(
'/profile-data',
method: 'POST',
mimeType: 'application/json',
sendData: json.encode(profile.toJson()),
);
if (request.status != 200) {
if (request.status.toDart != 200) {
throw Exception(
'Failed to report profile data to benchmark server. '
'The server responded with status code ${request.status}.'
......@@ -385,7 +402,7 @@ class LocalBenchmarkServerClient {
/// The server will halt the devicelab task and log the error.
Future<void> reportError(dynamic error, StackTrace stackTrace) async {
_checkNotManualMode();
await html.HttpRequest.request(
await _requestXhr(
'/on-error',
method: 'POST',
mimeType: 'application/json',
......@@ -399,7 +416,7 @@ class LocalBenchmarkServerClient {
/// Reports a message about the demo to the benchmark server.
Future<void> printToConsole(String report) async {
_checkNotManualMode();
await html.HttpRequest.request(
await _requestXhr(
'/print-to-console',
method: 'POST',
mimeType: 'text/plain',
......@@ -409,7 +426,7 @@ class LocalBenchmarkServerClient {
/// This is the same as calling [html.HttpRequest.request] but it doesn't
/// crash on 404, which we use to detect `flutter run`.
Future<html.HttpRequest> _requestXhr(
Future<web.XMLHttpRequest> _requestXhr(
String url, {
String? method,
bool? withCredentials,
......@@ -418,38 +435,40 @@ class LocalBenchmarkServerClient {
Map<String, String>? requestHeaders,
dynamic sendData,
}) {
final Completer<html.HttpRequest> completer = Completer<html.HttpRequest>();
final html.HttpRequest xhr = html.HttpRequest();
final Completer<web.XMLHttpRequest> completer = Completer<web.XMLHttpRequest>();
final web.XMLHttpRequest xhr = web.XMLHttpRequest();
method ??= 'GET';
xhr.open(method, url, async: true);
xhr.open(method.toJS, url.toJS, true.toJS);
if (withCredentials != null) {
xhr.withCredentials = withCredentials;
xhr.withCredentials = withCredentials.toJS;
}
if (responseType != null) {
xhr.responseType = responseType;
xhr.responseType = responseType.toJS;
}
if (mimeType != null) {
xhr.overrideMimeType(mimeType);
xhr.overrideMimeType(mimeType.toJS);
}
if (requestHeaders != null) {
requestHeaders.forEach((String header, String value) {
xhr.setRequestHeader(header, value);
xhr.setRequestHeader(header.toJS, value.toJS);
});
}
xhr.onLoad.listen((html.ProgressEvent e) {
xhr.addEventListener('load'.toJS, (web.ProgressEvent e) {
completer.complete(xhr);
});
}.toJS);
xhr.onError.listen(completer.completeError);
xhr.addEventListener('error'.toJS, (JSObject error) {
return completer.completeError(error);
}.toJS);
if (sendData != null) {
xhr.send(sendData);
xhr.send((sendData as Object?).jsify());
} else {
xhr.send();
}
......
......@@ -18,6 +18,8 @@ dependencies:
# flutter update-packages --force-upgrade
flutter_gallery_assets: 1.0.2
web: 0.1.2-beta
async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
......@@ -209,4 +211,4 @@ flutter:
fonts:
- asset: packages/flutter_gallery_assets/fonts/GalleryIcons.ttf
# PUBSPEC CHECKSUM: 1586
# PUBSPEC CHECKSUM: fdda
......@@ -21,6 +21,7 @@ dependencies:
shelf_static: 1.1.2
stack_trace: 1.11.0
vm_service: 11.6.0
web: 0.1.2-beta
webkit_inspection_protocol: 1.2.0
_discoveryapis_commons: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
......@@ -69,4 +70,4 @@ dev_dependencies:
watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
# PUBSPEC CHECKSUM: 022d
# PUBSPEC CHECKSUM: f681
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment