Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[web_benchmarks] migrate to pkg:web #5592

Merged
merged 4 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the <0.5.0 constraint?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we capture both v0.3.0 and v0.4.0

Because stable pins one version and dev pins the other

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<1.0.0 would also capture 0.4.0, right? And it would also be less sensitive to version constraints in adjacent packages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we're not sure about what we're doing in 0.5.0+ This is a "good" range for things we've tested.

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