Golem harness for measuring startup time and frame time
diff --git a/benchmarks/lib/complex.dart b/benchmarks/lib/complex.dart
index b473f1f..496d34e 100644
--- a/benchmarks/lib/complex.dart
+++ b/benchmarks/lib/complex.dart
@@ -1,3 +1,7 @@
+// Copyright 2023 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.
+
// @dart=2.12
import 'dart:math' as math;
@@ -6,10 +10,13 @@
import 'package:flute/cupertino.dart';
import 'package:flute/material.dart';
+import 'harness.dart';
+
const int maxDepth = 6;
final math.Random random = math.Random(0);
-void main() {
+void main(List<String> args) {
+ initializeBenchmarkHarness('Complex', args);
ui.initializeEngine(
screenSize: const Size(3840, 2160), // 4k
);
diff --git a/benchmarks/lib/counter.dart b/benchmarks/lib/counter.dart
index 8703c1b..c8d46a8 100644
--- a/benchmarks/lib/counter.dart
+++ b/benchmarks/lib/counter.dart
@@ -1,7 +1,14 @@
+// Copyright 2023 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:engine/ui.dart' as ui;
import 'package:flute/material.dart';
-void main() {
+import 'harness.dart';
+
+void main(List<String> args) {
+ initializeBenchmarkHarness('Counter', args);
ui.initializeEngine(
screenSize: const Size(3840, 2160), // 4k
);
diff --git a/benchmarks/lib/harness.dart b/benchmarks/lib/harness.dart
new file mode 100644
index 0000000..d669570
--- /dev/null
+++ b/benchmarks/lib/harness.dart
@@ -0,0 +1,64 @@
+// Copyright 2023 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:engine/ui.dart' show PlatformDispatcher;
+
+void initializeBenchmarkHarness(String name, List<String> args) {
+ final int startOfMain = DateTime.now().microsecondsSinceEpoch;
+
+ if (args.isEmpty) {
+ // Run in default mode, rather than as a Golem benchmark.
+ return;
+ }
+
+ // First argument is time since epoch as measured just prior to launching the
+ // benchmark, in seconds, formatted as a decimal number, i.e. the output of
+ // `date '+%s.%N'`.
+ final int beforeStart = (double.parse(args[0]) * 1000000).toInt();
+ final int timeToMain = startOfMain - beforeStart;
+ print('$name.TimeToMain(StartupTime): $timeToMain us.');
+
+ // Second argument (optional) is number of frames to measure, default 1000.
+ final int framesToTime = args.length < 2 ? 1000 : int.parse(args[1]);
+
+ // Third argument (optional) is number of frames to skip before starting the
+ // measurement, default 10.
+ final int framesToSkip = args.length < 3 ? 10 : int.parse(args[2]);
+
+ double buildSum = 0;
+ double drawSum = 0;
+
+ bool averagesPrinted = false;
+
+ bool frameCallback(int frameCount, double buildTime, double drawTime) {
+ if (frameCount == 1) {
+ final int afterFirstFrame = DateTime.now().microsecondsSinceEpoch;
+ final int timeToFirstFrame = afterFirstFrame - beforeStart;
+ print('$name.TimeToFirstFrame(StartupTime): $timeToFirstFrame us.');
+ }
+
+ if (frameCount > framesToSkip + framesToTime) {
+ if (!averagesPrinted) {
+ final int averageBuild = (buildSum / framesToTime * 1000).toInt();
+ print('$name.AverageBuild(RunTimeRaw): $averageBuild us.');
+ final int averageDraw = (drawSum / framesToTime * 1000).toInt();
+ print('$name.AverageDraw(RunTimeRaw): $averageDraw us.');
+ final int averageFrame =
+ ((buildSum + drawSum) / framesToTime * 1000).toInt();
+ print('$name.AverageFrame(RunTime): $averageFrame us.');
+ averagesPrinted = true;
+ }
+ return false;
+ }
+
+ if (frameCount > framesToSkip) {
+ buildSum += buildTime;
+ drawSum += drawTime;
+ }
+
+ return true;
+ }
+
+ PlatformDispatcher.frameCallback = frameCallback;
+}
diff --git a/engine/lib/src/platform_dispatcher.dart b/engine/lib/src/platform_dispatcher.dart
index d7837b0..2355aca 100644
--- a/engine/lib/src/platform_dispatcher.dart
+++ b/engine/lib/src/platform_dispatcher.dart
@@ -546,9 +546,17 @@
Timer? _frameTimer;
Duration _frameTime = Duration.zero;
+ bool _continue = true;
static int _frameCount = 1;
+ // Frame callback function that can be injected by the benchmark harness.
+ static bool Function(int, double, double) frameCallback =
+ (int frameCount, double buildTime, double drawTime) {
+ print('Frame #$frameCount: build $buildTime ms; draw $drawTime ms');
+ return true;
+ };
+
/// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
/// [onDrawFrame] callbacks be invoked.
///
@@ -560,7 +568,9 @@
_frameTimer ??= Timer(
const Duration(milliseconds: 16),
() {
- _frameTimer = null;
+ if (_continue) {
+ _frameTimer = null;
+ }
final int microseconds = _frameTime.inMicroseconds;
_frameTime += const Duration(milliseconds: 16);
Timer.run(() {
@@ -571,7 +581,10 @@
final Stopwatch drawSw = Stopwatch()..start();
_drawFrame();
drawSw.stop();
- print('Frame #$_frameCount: build ${beginSw.elapsedMicroseconds / 1000} ms; draw ${drawSw.elapsedMicroseconds / 1000} ms');
+ _continue = frameCallback(
+ _frameCount,
+ beginSw.elapsedMicroseconds / 1000,
+ drawSw.elapsedMicroseconds / 1000);
_frameCount += 1;
});
});