Skip to content

Commit

Permalink
[web_benchmarks] migrate to pkg:web (flutter#5592)
Browse files Browse the repository at this point in the history
Drop dart:html
Enables usage via WebAssembly

Fixes flutter/flutter#139577
  • Loading branch information
kevmoo authored and arc-yong committed Jun 14, 2024
1 parent 9398f5f commit 4956ef3
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 54 deletions.
4 changes: 4 additions & 0 deletions packages/web_benchmarks/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.1.0+11

* Migrates benchmark recorder from `dart:html` to `package:web` to support WebAssembly.

## 0.1.0+10

* Ensure the benchmark client reloads with the proper `initialPage`.
Expand Down
101 changes: 54 additions & 47 deletions packages/web_benchmarks/lib/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

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/helpers.dart';

import 'src/common.dart';
import 'src/recorder.dart';

export 'src/recorder.dart';

/// Signature for a function that creates a [Recorder].
Expand Down Expand Up @@ -47,7 +50,7 @@ Future<void> runBenchmarks(

await _runBenchmark(nextBenchmark);

final Uri currentUri = Uri.parse(html.window.location.href);
final Uri currentUri = Uri.parse(window.location.href);
// Create a new URI with the current 'page' value set to [initialPage] to
// ensure the benchmark app is reloaded at the proper location.
final Uri newUri = Uri(
Expand All @@ -58,7 +61,7 @@ Future<void> runBenchmarks(
);

// Reloading the window will trigger the next benchmark to run.
html.window.location.replace(newUri.toString());
window.location.replace(newUri.toString());
}

Future<void> _runBenchmark(String? benchmarkName) async {
Expand Down Expand Up @@ -116,7 +119,7 @@ Future<void> _runBenchmark(String? benchmarkName) async {
}

void _fallbackToManual(String error) {
html.document.body!.appendHtml('''
document.body!.appendHtml('''
<div id="manual-panel">
<h3>$error</h3>
Expand All @@ -127,33 +130,33 @@ void _fallbackToManual(String error) {
${_benchmarks.keys.map((String name) => '<li><button id="$name">$name</button></li>').join('\n')}
</ul>
</div>
''',
validator: html.NodeValidatorBuilder()
..allowHtml5()
..allowInlineStyles());
''');

for (final String benchmarkName in _benchmarks.keys) {
// Find the button elements added above.
final html.Element button = html.document.querySelector('#$benchmarkName')!;
button.addEventListener('click', (_) {
final html.Element? manualPanel =
html.document.querySelector('#manual-panel');
manualPanel?.remove();
_runBenchmark(benchmarkName);
});
final Element button = document.querySelector('#$benchmarkName')!;
button.addEventListener(
'click',
(JSAny? arg) {
final Element? manualPanel = document.querySelector('#manual-panel');
manualPanel?.remove();
_runBenchmark(benchmarkName);
}.toJS);
}
}

/// Visualizes results on the Web page for manual inspection.
void _printResultsToScreen(Profile profile) {
final html.BodyElement body = html.document.body!;
final HTMLBodyElement body = document.body! as HTMLBodyElement;

body.innerHtml = '<h2>${profile.name}</h2>';
body.innerHTML = '<h2>${profile.name}</h2>';

profile.scoreData.forEach((String scoreKey, Timeseries timeseries) {
body.appendHtml('<h2>$scoreKey</h2>');
body.appendHtml('<pre>${timeseries.computeStats()}</pre>');
body.append(TimeseriesVisualization(timeseries).render());
// TODO(kevmoo): remove `NodeGlue` cast when we no longer need to support
// pkg:web 0.3.0
NodeGlue(body).append(TimeseriesVisualization(timeseries).render());
});
}

Expand All @@ -162,10 +165,10 @@ class TimeseriesVisualization {
/// Creates a visualization for a [Timeseries].
TimeseriesVisualization(this._timeseries) {
_stats = _timeseries.computeStats();
_canvas = html.CanvasElement();
_screenWidth = html.window.screen!.width!;
_canvas = CanvasElement();
_screenWidth = window.screen.width;
_canvas.width = _screenWidth;
_canvas.height = (_kCanvasHeight * html.window.devicePixelRatio).round();
_canvas.height = (_kCanvasHeight * window.devicePixelRatio).round();
_canvas.style
..width = '100%'
..height = '${_kCanvasHeight}px'
Expand All @@ -186,8 +189,8 @@ class TimeseriesVisualization {

final Timeseries _timeseries;
late TimeseriesStats _stats;
late html.CanvasElement _canvas;
late html.CanvasRenderingContext2D _ctx;
late CanvasElement _canvas;
late CanvasRenderingContext2D _ctx;
late int _screenWidth;

// Used to normalize benchmark values to chart height.
Expand All @@ -209,9 +212,9 @@ class TimeseriesVisualization {
}

/// 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);
CanvasElement render() {
_ctx.translate(0, _kCanvasHeight * window.devicePixelRatio);
_ctx.scale(1, -window.devicePixelRatio);

final double barWidth = _screenWidth / _stats.samples.length;
double xOffset = 0;
Expand All @@ -220,19 +223,19 @@ class TimeseriesVisualization {

if (sample.isWarmUpValue) {
// Put gray background behind warm-up samples.
_ctx.fillStyle = 'rgba(200,200,200,1)';
_ctx.fillStyle = 'rgba(200,200,200,1)'.toJS;
_ctx.fillRect(xOffset, 0, barWidth, _normalized(_maxValueChartRange));
}

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));
Expand All @@ -245,12 +248,12 @@ class TimeseriesVisualization {
_normalized(_stats.average));

// Draw a horizontal dashed line corresponding to the outlier cut off.
_ctx.setLineDash(<num>[5, 5]);
_ctx.setLineDash(<JSNumber>[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)),
Expand Down Expand Up @@ -283,7 +286,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 XMLHttpRequest request = await _requestXhr(
'/next-benchmark',
method: 'POST',
mimeType: 'application/json',
Expand All @@ -298,7 +301,7 @@ class LocalBenchmarkServerClient {
}

isInManualMode = false;
return request.responseText ?? kManualFallback;
return request.responseText;
}

void _checkNotManualMode() {
Expand All @@ -314,7 +317,7 @@ class LocalBenchmarkServerClient {
/// DevTools Protocol.
Future<void> startPerformanceTracing(String? benchmarkName) async {
_checkNotManualMode();
await html.HttpRequest.request(
await HttpRequest.request(
'/start-performance-tracing?label=$benchmarkName',
method: 'POST',
mimeType: 'application/json',
Expand All @@ -324,7 +327,7 @@ class LocalBenchmarkServerClient {
/// Stops the performance tracing session started by [startPerformanceTracing].
Future<void> stopPerformanceTracing() async {
_checkNotManualMode();
await html.HttpRequest.request(
await HttpRequest.request(
'/stop-performance-tracing',
method: 'POST',
mimeType: 'application/json',
Expand All @@ -335,7 +338,7 @@ class LocalBenchmarkServerClient {
/// server.
Future<void> sendProfileData(Profile profile) async {
_checkNotManualMode();
final html.HttpRequest request = await html.HttpRequest.request(
final XMLHttpRequest request = await _requestXhr(
'/profile-data',
method: 'POST',
mimeType: 'application/json',
Expand All @@ -352,7 +355,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 HttpRequest.request(
'/on-error',
method: 'POST',
mimeType: 'application/json',
Expand All @@ -366,31 +369,35 @@ class LocalBenchmarkServerClient {
/// Reports a message about the demo to the benchmark server.
Future<void> printToConsole(String report) async {
_checkNotManualMode();
await html.HttpRequest.request(
await 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
/// This is the same as calling [XMLHttpRequest.request] but it doesn't
/// crash on 404, which we use to detect `flutter run`.
Future<html.HttpRequest> _requestXhr(
Future<XMLHttpRequest> _requestXhr(
String url, {
required String method,
required String mimeType,
required dynamic sendData,
required String sendData,
}) {
final Completer<html.HttpRequest> completer = Completer<html.HttpRequest>();
final html.HttpRequest xhr = html.HttpRequest();
xhr.open(method, url, async: true);
final Completer<XMLHttpRequest> completer = Completer<XMLHttpRequest>();
final XMLHttpRequest xhr = XMLHttpRequest();
xhr.open(method, url, true);
xhr.overrideMimeType(mimeType);
xhr.onLoad.listen((html.ProgressEvent e) {
xhr.onLoad.listen((ProgressEvent e) {
completer.complete(xhr);
});
xhr.onError.listen(completer.completeError);
xhr.send(sendData);
xhr.send(sendData.toJS);
return completer.future;
}
}

extension on HTMLElement {
void appendHtml(String input) => insertAdjacentHTML('beforeend', input);
}
5 changes: 3 additions & 2 deletions packages/web_benchmarks/lib/src/recorder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:html' as html;
import 'dart:js_interop';
import 'dart:math' as math;
import 'dart:ui';
import 'dart:ui_web' as ui_web;
Expand All @@ -15,6 +15,7 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:web/helpers.dart' as html;

import 'common.dart';

Expand Down Expand Up @@ -1188,7 +1189,7 @@ void endMeasureFrame() {
html.window.performance.mark('measured_frame_end#$_currentFrameNumber');
html.window.performance.measure(
'measured_frame',
'measured_frame_start#$_currentFrameNumber',
'measured_frame_start#$_currentFrameNumber'.toJS,
'measured_frame_end#$_currentFrameNumber',
);

Expand Down
3 changes: 2 additions & 1 deletion packages/web_benchmarks/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: web_benchmarks
description: A benchmark harness for performance-testing Flutter apps in Chrome.
repository: https://github.com/flutter/packages/tree/main/packages/web_benchmarks
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+web_benchmarks%22
version: 0.1.0+10
version: 0.1.0+11

environment:
sdk: ">=3.2.0 <4.0.0"
Expand All @@ -20,6 +20,7 @@ dependencies:
shelf: ^1.2.0
shelf_static: ^1.1.0
test: ^1.19.5
web: '>=0.3.0 <0.5.0'
webkit_inspection_protocol: ^1.0.0

topics:
Expand Down
2 changes: 1 addition & 1 deletion packages/web_benchmarks/testing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ do the following:
* Fetch dependencies for the `test_app` directory inside `testing`:

```bash
flutter pub get testing/test_app
flutter pub get --directory testing/test_app
```

* Fetch dependencies for the `web_benchmarks` directory:
Expand Down
3 changes: 0 additions & 3 deletions packages/web_benchmarks/testing/test_app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@
.pub/
/build/

# Web related
lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols

Expand Down

0 comments on commit 4956ef3

Please sign in to comment.