Skip to content

Commit 6f8945f

Browse files
authored
extract engine sub-metrics; change reported metrics (flutter#55331)
* extract engine sub-metrics; change reported metrics - Extract sub-metrics reported by the Web engine: "preroll_frame", "apply_frame". - Add a concept of unreported metrics: displayed on the benchmark UI, but not on the dashboard. - Make "sceneBuildDuration" and "windowRenderDuration" unreported, which are too fine-grained. They are included in "drawFrameDuration" already. - Report outlier ratio instead of outlier average. The ratio is more useful of the two.
1 parent e8a987e commit 6f8945f

File tree

3 files changed

+125
-25
lines changed

3 files changed

+125
-25
lines changed

dev/benchmarks/macrobenchmarks/lib/src/web/bench_picture_recording.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ class BenchPictureRecording extends RawRecorder {
6767
}
6868
canvas.restore();
6969
}
70-
});
70+
}, reported: true);
7171
profile.record('estimatePaintBounds', () {
7272
recorder.endRecording();
73-
});
73+
}, reported: true);
7474
}
7575
}

dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart

+7-11
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ void _useCanvasText(bool useCanvasText) {
5858
);
5959
}
6060

61-
typedef OnBenchmark = void Function(String name, num value);
62-
void _onBenchmark(OnBenchmark listener) {
63-
js_util.setProperty(html.window, '_flutter_internal_on_benchmark', listener);
64-
}
65-
6661
/// Repeatedly lays out a paragraph using the DOM measurement approach.
6762
///
6863
/// Creates a different paragraph each time in order to avoid hitting the cache.
@@ -132,21 +127,21 @@ class BenchTextLayout extends RawRecorder {
132127
}) {
133128
profile.record('$keyPrefix.layout', () {
134129
paragraph.layout(ui.ParagraphConstraints(width: maxWidth));
135-
});
130+
}, reported: true);
136131
profile.record('$keyPrefix.getBoxesForRange', () {
137132
for (int start = 0; start < text.length; start += 3) {
138133
for (int end = start + 1; end < text.length; end *= 2) {
139134
paragraph.getBoxesForRange(start, end);
140135
}
141136
}
142-
});
137+
}, reported: true);
143138
profile.record('$keyPrefix.getPositionForOffset', () {
144139
for (double dx = 0.0; dx < paragraph.width; dx += 10.0) {
145140
for (double dy = 0.0; dy < paragraph.height; dy += 10.0) {
146141
paragraph.getPositionForOffset(Offset(dx, dy));
147142
}
148143
}
149-
});
144+
}, reported: true);
150145
}
151146
}
152147

@@ -179,7 +174,7 @@ class BenchTextCachedLayout extends RawRecorder {
179174
final ui.Paragraph paragraph = builder.build();
180175
profile.record('layout', () {
181176
paragraph.layout(const ui.ParagraphConstraints(width: double.infinity));
182-
});
177+
}, reported: true);
183178
_useCanvasText(null);
184179
}
185180
}
@@ -242,15 +237,15 @@ class BenchBuildColorsGrid extends WidgetBuildRecorder {
242237
if (mode == _TestMode.useDomTextLayout) {
243238
_useCanvasText(false);
244239
}
245-
_onBenchmark((String name, num value) {
240+
registerEngineBenchmarkValueListener('text_layout', (num value) {
246241
_textLayoutMicros += value;
247242
});
248243
}
249244

250245
@override
251246
Future<void> tearDownAll() async {
252247
_useCanvasText(null);
253-
_onBenchmark(null);
248+
stopListeningToEngineBenchmarkValues('text_layout');
254249
}
255250

256251
@override
@@ -268,6 +263,7 @@ class BenchBuildColorsGrid extends WidgetBuildRecorder {
268263
profile.addDataPoint(
269264
'text_layout',
270265
Duration(microseconds: _textLayoutMicros.toInt()),
266+
reported: true,
271267
);
272268
}
273269
super.frameDidDraw();

dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart

+116-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:async';
66
import 'dart:html' as html;
7+
import 'dart:js_util' as js_util;
78
import 'dart:math' as math;
89
import 'dart:ui';
910

@@ -27,6 +28,16 @@ const int _kMeasuredSampleCount = 100;
2728
/// The total number of samples collected by a benchmark.
2829
const int kTotalSampleCount = _kWarmUpSampleCount + _kMeasuredSampleCount;
2930

31+
/// A benchmark metric that includes frame-related computations prior to
32+
/// submitting layer and picture operations to the underlying renderer, such as
33+
/// HTML and CanvasKit. During this phase we compute transforms, clips, and
34+
/// other information needed for rendering.
35+
const String kProfilePrerollFrame = 'preroll_frame';
36+
37+
/// A benchmark metric that includes submitting layer and picture information
38+
/// to the renderer.
39+
const String kProfileApplyFrame = 'apply_frame';
40+
3041
/// Measures the amount of time [action] takes.
3142
Duration timeAction(VoidCallback action) {
3243
final Stopwatch stopwatch = Stopwatch()..start();
@@ -221,9 +232,9 @@ abstract class SceneBuilderRecorder extends Recorder {
221232
final Scene scene = sceneBuilder.build();
222233
profile.record('windowRenderDuration', () {
223234
window.render(scene);
224-
});
225-
});
226-
});
235+
}, reported: false);
236+
}, reported: false);
237+
}, reported: true);
227238
endMeasureFrame();
228239

229240
if (profile.shouldContinue()) {
@@ -331,7 +342,7 @@ abstract class WidgetRecorder extends Recorder implements FrameRecorder {
331342
@mustCallSuper
332343
void frameDidDraw() {
333344
endMeasureFrame();
334-
profile.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed);
345+
profile.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed, reported: true);
335346

336347
if (profile.shouldContinue()) {
337348
window.scheduleFrame();
@@ -353,12 +364,30 @@ abstract class WidgetRecorder extends Recorder implements FrameRecorder {
353364
final _RecordingWidgetsBinding binding =
354365
_RecordingWidgetsBinding.ensureInitialized();
355366
final Widget widget = createWidget();
367+
368+
registerEngineBenchmarkValueListener(kProfilePrerollFrame, (num value) {
369+
localProfile.addDataPoint(
370+
kProfilePrerollFrame,
371+
Duration(microseconds: value.toInt()),
372+
reported: false,
373+
);
374+
});
375+
registerEngineBenchmarkValueListener(kProfileApplyFrame, (num value) {
376+
localProfile.addDataPoint(
377+
kProfileApplyFrame,
378+
Duration(microseconds: value.toInt()),
379+
reported: false,
380+
);
381+
});
382+
356383
binding._beginRecording(this, widget);
357384

358385
try {
359386
await _runCompleter.future;
360387
return localProfile;
361388
} finally {
389+
stopListeningToEngineBenchmarkValues(kProfilePrerollFrame);
390+
stopListeningToEngineBenchmarkValues(kProfileApplyFrame);
362391
_runCompleter = null;
363392
profile = null;
364393
}
@@ -421,7 +450,7 @@ abstract class WidgetBuildRecorder extends Recorder implements FrameRecorder {
421450
// Only record frames that show the widget.
422451
if (showWidget) {
423452
endMeasureFrame();
424-
profile.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed);
453+
profile.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed, reported: true);
425454
}
426455

427456
if (profile.shouldContinue()) {
@@ -488,11 +517,21 @@ class _WidgetBuildRecorderHostState extends State<_WidgetBuildRecorderHost> {
488517
/// calculations will only apply to the latest [_kMeasuredSampleCount] data
489518
/// points.
490519
class Timeseries {
491-
Timeseries(this.name);
520+
Timeseries(this.name, this.isReported);
492521

493522
/// The label of this timeseries used for debugging and result inspection.
494523
final String name;
495524

525+
/// Whether this timeseries is reported to the benchmark dashboard.
526+
///
527+
/// If `true` a new benchmark card is created for the timeseries and is
528+
/// visible on the dashboard.
529+
///
530+
/// If `false` the data is stored but it does not show up on the dashboard.
531+
/// Use unreported metrics for metrics that are useful for manual inspection
532+
/// but that are too fine-grained to be useful for tracking on the dashboard.
533+
final bool isReported;
534+
496535
/// List of all the values that have been recorded.
497536
///
498537
/// This list has no limit.
@@ -700,14 +739,20 @@ class Profile {
700739
final Map<String, dynamic> extraData = <String, dynamic>{};
701740

702741
/// Invokes [callback] and records the duration of its execution under [key].
703-
Duration record(String key, VoidCallback callback) {
742+
Duration record(String key, VoidCallback callback, { @required bool reported }) {
704743
final Duration duration = timeAction(callback);
705-
addDataPoint(key, duration);
744+
addDataPoint(key, duration, reported: reported);
706745
return duration;
707746
}
708747

709-
void addDataPoint(String key, Duration duration) {
710-
scoreData.putIfAbsent(key, () => Timeseries(key)).add(duration.inMicroseconds.toDouble());
748+
/// Adds a timed sample to the timeseries corresponding to [key].
749+
///
750+
/// Set [reported] to `true` to report the timeseries to the dashboard UI.
751+
///
752+
/// Set [reported] to `false` to store the data, but not show it on the
753+
/// dashboard UI.
754+
void addDataPoint(String key, Duration duration, { @required bool reported }) {
755+
scoreData.putIfAbsent(key, () => Timeseries(key, reported)).add(duration.inMicroseconds.toDouble());
711756
}
712757

713758
/// Decides whether the data collected so far is sufficient to stop, or
@@ -740,9 +785,16 @@ class Profile {
740785
};
741786

742787
for (final String key in scoreData.keys) {
743-
scoreKeys.add('$key.average');
744-
scoreKeys.add('$key.outlierAverage');
745788
final Timeseries timeseries = scoreData[key];
789+
790+
if (timeseries.isReported) {
791+
scoreKeys.add('$key.average');
792+
// Report `outlierRatio` rather than `outlierAverage`, because
793+
// the absolute value of outliers is less interesting than the
794+
// ratio.
795+
scoreKeys.add('$key.outlierRatio');
796+
}
797+
746798
final TimeseriesStats stats = timeseries.computeStats();
747799
json['$key.average'] = stats.average;
748800
json['$key.outlierAverage'] = stats.outlierAverage;
@@ -958,3 +1010,55 @@ void endMeasureFrame() {
9581010
);
9591011
_currentFrameNumber += 1;
9601012
}
1013+
1014+
/// A function that receives a benchmark value from the framework.
1015+
typedef EngineBenchmarkValueListener = void Function(num value);
1016+
1017+
// Maps from a value label name to a listener.
1018+
final Map<String, EngineBenchmarkValueListener> _engineBenchmarkListeners = <String, EngineBenchmarkValueListener>{};
1019+
1020+
/// Registers a [listener] for engine benchmark values labeled by [name].
1021+
///
1022+
/// If another listener is already registered, overrides it.
1023+
void registerEngineBenchmarkValueListener(String name, EngineBenchmarkValueListener listener) {
1024+
if (listener == null) {
1025+
throw ArgumentError(
1026+
'Listener must not be null. To stop listening to engine benchmark values '
1027+
'under label "$name", call stopListeningToEngineBenchmarkValues(\'$name\').',
1028+
);
1029+
}
1030+
1031+
if (_engineBenchmarkListeners.containsKey(name)) {
1032+
throw StateError(
1033+
'A listener for "$name" is already registered.\n'
1034+
'Call `stopListeningToEngineBenchmarkValues` to unregister the previous '
1035+
'listener before registering a new one.'
1036+
);
1037+
}
1038+
1039+
if (_engineBenchmarkListeners.isEmpty) {
1040+
// The first listener is being registered. Register the global listener.
1041+
js_util.setProperty(html.window, '_flutter_internal_on_benchmark', _dispatchEngineBenchmarkValue);
1042+
}
1043+
1044+
_engineBenchmarkListeners[name] = listener;
1045+
}
1046+
1047+
/// Stops listening to engine benchmark values under labeled by [name].
1048+
void stopListeningToEngineBenchmarkValues(String name) {
1049+
_engineBenchmarkListeners.remove(name);
1050+
if (_engineBenchmarkListeners.isEmpty) {
1051+
// The last listener unregistered. Remove the global listener.
1052+
js_util.setProperty(html.window, '_flutter_internal_on_benchmark', null);
1053+
}
1054+
}
1055+
1056+
// Dispatches a benchmark value reported by the engine to the relevant listener.
1057+
//
1058+
// If there are no listeners registered for [name], ignores the value.
1059+
void _dispatchEngineBenchmarkValue(String name, double value) {
1060+
final EngineBenchmarkValueListener listener = _engineBenchmarkListeners[name];
1061+
if (listener != null) {
1062+
listener(value);
1063+
}
1064+
}

0 commit comments

Comments
 (0)