Skip to content

Commit 2571a9f

Browse files
moffatmanMairramer
authored andcommitted
Add "Frame Request Pending" lag to collected metrics (flutter#135279)
This measures the wall clock time between a new frame being scheduled in dart code to the Vsync callback in the engine It's an important source of lag which isn't shown in the top-level UI / Build time graphs, and can correlate with "invisible" missed/non-scheduled frames I had to change a few unrelated timings in the test, it was only passing based on luck of sort order, and broke when I added more entries to the timeline. Part of flutter#129150
1 parent be52924 commit 2571a9f

File tree

3 files changed

+117
-6
lines changed

3 files changed

+117
-6
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'percentile_utils.dart';
6+
import 'timeline.dart';
7+
8+
/// Event name for frame request pending timeline events.
9+
const String kFrameRequestPendingEvent = 'Frame Request Pending';
10+
11+
/// Summarizes [TimelineEvents]s corresponding to [kFrameRequestPendingEvent] events.
12+
///
13+
/// `FrameRequestPendingLatency` is the time between `Animator::RequestFrame`
14+
/// and `Animator::BeginFrame` for each frame built by the Flutter engine.
15+
class FrameRequestPendingLatencySummarizer {
16+
/// Creates a FrameRequestPendingLatencySummarizer given the timeline events.
17+
FrameRequestPendingLatencySummarizer(this.frameRequestPendingEvents);
18+
19+
/// Timeline events with names in [kFrameRequestPendingTimelineEventNames].
20+
final List<TimelineEvent> frameRequestPendingEvents;
21+
22+
/// Computes the average `FrameRequestPendingLatency` over the period of the timeline.
23+
double computeAverageFrameRequestPendingLatency() {
24+
final List<double> frameRequestPendingLatencies =
25+
_computeFrameRequestPendingLatencies();
26+
if (frameRequestPendingLatencies.isEmpty) {
27+
return 0;
28+
}
29+
30+
final double total = frameRequestPendingLatencies.reduce((double a, double b) => a + b);
31+
return total / frameRequestPendingLatencies.length;
32+
}
33+
34+
/// Computes the [percentile]-th percentile `FrameRequestPendingLatency` over the
35+
/// period of the timeline.
36+
double computePercentileFrameRequestPendingLatency(double percentile) {
37+
final List<double> frameRequestPendingLatencies =
38+
_computeFrameRequestPendingLatencies();
39+
if (frameRequestPendingLatencies.isEmpty) {
40+
return 0;
41+
}
42+
return findPercentile(frameRequestPendingLatencies, percentile);
43+
}
44+
45+
List<double> _computeFrameRequestPendingLatencies() {
46+
final List<double> result = <double>[];
47+
final Map<String, int> starts = <String, int>{};
48+
for (int i = 0; i < frameRequestPendingEvents.length; i++) {
49+
final TimelineEvent event = frameRequestPendingEvents[i];
50+
if (event.phase == 'b') {
51+
final String? id = event.json['id'] as String?;
52+
if (id != null) {
53+
starts[id] = event.timestampMicros!;
54+
}
55+
} else if (event.phase == 'e') {
56+
final int? start = starts[event.json['id']];
57+
if (start != null) {
58+
result.add((event.timestampMicros! - start).toDouble());
59+
}
60+
}
61+
}
62+
return result;
63+
}
64+
}

packages/flutter_driver/lib/src/driver/timeline_summary.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:file/file.dart';
99
import 'package:path/path.dart' as path;
1010

1111
import 'common.dart';
12+
import 'frame_request_pending_latency_summarizer.dart';
1213
import 'gc_summarizer.dart';
1314
import 'percentile_utils.dart';
1415
import 'profiling_summarizer.dart';
@@ -258,13 +259,22 @@ class TimelineSummary {
258259
/// * "worst_picture_cache_memory": The worst (highest) value seen for the
259260
/// memory used for the engine picture cache entries.
260261
/// See [RasterCacheSummarizer.computeWorstPictureMemory].
262+
/// * "average_frame_request_pending_latency": Computes the average of the delay
263+
/// between `Animator::RequestFrame` and `Animator::BeginFrame` in the engine.
264+
/// See [FrameRequestPendingLatencySummarizer.computeAverageFrameRequestPendingLatency].
265+
/// * "90th_percentile_frame_request_pending_latency" and
266+
/// "99th_percentile_frame_request_pending_latency": The 90/99-th percentile
267+
/// delay between `Animator::RequestFrame` and `Animator::BeginFrame` in the
268+
/// engine.
269+
/// See [FrameRequestPendingLatencySummarizer.computePercentileFrameRequestPendingLatency].
261270
Map<String, dynamic> get summaryJson {
262271
final SceneDisplayLagSummarizer sceneDisplayLagSummarizer = _sceneDisplayLagSummarizer();
263272
final VsyncFrameLagSummarizer vsyncFrameLagSummarizer = _vsyncFrameLagSummarizer();
264273
final Map<String, dynamic> profilingSummary = _profilingSummarizer().summarize();
265274
final RasterCacheSummarizer rasterCacheSummarizer = _rasterCacheSummarizer();
266275
final GCSummarizer gcSummarizer = _gcSummarizer();
267276
final RefreshRateSummary refreshRateSummary = RefreshRateSummary(vsyncEvents: _extractNamedEvents(kUIThreadVsyncProcessEvent));
277+
final FrameRequestPendingLatencySummarizer frameRequestPendingLatencySummarizer = _frameRequestPendingLatencySummarizer();
268278

269279
final Map<String, dynamic> timelineSummary = <String, dynamic>{
270280
'average_frame_build_time_millis': computeAverageFrameBuildTimeMillis(),
@@ -303,6 +313,9 @@ class TimelineSummary {
303313
'average_layer_cache_count': rasterCacheSummarizer.computeAverageLayerCount(),
304314
'90th_percentile_layer_cache_count': rasterCacheSummarizer.computePercentileLayerCount(90.0),
305315
'99th_percentile_layer_cache_count': rasterCacheSummarizer.computePercentileLayerCount(99.0),
316+
'average_frame_request_pending_latency': frameRequestPendingLatencySummarizer.computeAverageFrameRequestPendingLatency(),
317+
'90th_percentile_frame_request_pending_latency': frameRequestPendingLatencySummarizer.computePercentileFrameRequestPendingLatency(90.0),
318+
'99th_percentile_frame_request_pending_latency': frameRequestPendingLatencySummarizer.computePercentileFrameRequestPendingLatency(99.0),
306319
'worst_layer_cache_count': rasterCacheSummarizer.computeWorstLayerCount(),
307320
'average_layer_cache_memory': rasterCacheSummarizer.computeAverageLayerMemory(),
308321
'90th_percentile_layer_cache_memory': rasterCacheSummarizer.computePercentileLayerMemory(90.0),
@@ -491,5 +504,7 @@ class TimelineSummary {
491504

492505
RasterCacheSummarizer _rasterCacheSummarizer() => RasterCacheSummarizer(_extractNamedEvents(kRasterCacheEvent));
493506

507+
FrameRequestPendingLatencySummarizer _frameRequestPendingLatencySummarizer() => FrameRequestPendingLatencySummarizer(_extractNamedEvents(kFrameRequestPendingEvent));
508+
494509
GCSummarizer _gcSummarizer() => GCSummarizer.fromEvents(_extractEventsWithNames(kGCRootEvents));
495510
}

packages/flutter_driver/test/src/real_tests/timeline_summary_test.dart

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,20 @@ void main() {
143143
return result;
144144
}
145145

146+
Map<String, dynamic> frameRequestPendingStart(String id, int timeStamp) => <String, dynamic>{
147+
'name': 'Frame Request Pending',
148+
'ph': 'b',
149+
'id': id,
150+
'ts': timeStamp,
151+
};
152+
153+
Map<String, dynamic> frameRequestPendingEnd(String id, int timeStamp) => <String, dynamic>{
154+
'name': 'Frame Request Pending',
155+
'ph': 'e',
156+
'id': id,
157+
'ts': timeStamp,
158+
};
159+
146160
group('frame_count', () {
147161
test('counts frames', () {
148162
expect(
@@ -448,13 +462,19 @@ void main() {
448462
expect(
449463
summarize(<Map<String, dynamic>>[
450464
begin(1000), end(19000),
451-
begin(19000), end(29000),
452-
begin(29000), end(49000),
465+
begin(19001), end(29001),
466+
begin(29002), end(49002),
453467
...newGenGC(4, 10, 100),
454468
...oldGenGC(5, 10000, 100),
455469
frameBegin(1000), frameEnd(18000),
456470
frameBegin(19000), frameEnd(28000),
457471
frameBegin(29000), frameEnd(48000),
472+
frameRequestPendingStart('1', 1000),
473+
frameRequestPendingEnd('1', 2000),
474+
frameRequestPendingStart('2', 3000),
475+
frameRequestPendingEnd('2', 5000),
476+
frameRequestPendingStart('3', 6000),
477+
frameRequestPendingEnd('3', 9000),
458478
]).summaryJson,
459479
<String, dynamic>{
460480
'average_frame_build_time_millis': 15.0,
@@ -475,7 +495,7 @@ void main() {
475495
'frame_build_times': <int>[17000, 9000, 19000],
476496
'frame_rasterizer_times': <int>[18000, 10000, 20000],
477497
'frame_begin_times': <int>[0, 18000, 28000],
478-
'frame_rasterizer_begin_times': <int>[0, 18000, 28000],
498+
'frame_rasterizer_begin_times': <int>[0, 18001, 28002],
479499
'average_vsync_transitions_missed': 0.0,
480500
'90th_percentile_vsync_transitions_missed': 0.0,
481501
'99th_percentile_vsync_transitions_missed': 0.0,
@@ -505,6 +525,9 @@ void main() {
505525
'90hz_frame_percentage': 0,
506526
'120hz_frame_percentage': 0,
507527
'illegal_refresh_rate_frame_count': 0,
528+
'average_frame_request_pending_latency': 2000.0,
529+
'90th_percentile_frame_request_pending_latency': 3000.0,
530+
'99th_percentile_frame_request_pending_latency': 3000.0,
508531
},
509532
);
510533
});
@@ -556,8 +579,8 @@ void main() {
556579
test('writes summary to JSON file', () async {
557580
await summarize(<Map<String, dynamic>>[
558581
begin(1000), end(19000),
559-
begin(19000), end(29000),
560-
begin(29000), end(49000),
582+
begin(19001), end(29001),
583+
begin(29002), end(49002),
561584
frameBegin(1000), frameEnd(18000),
562585
frameBegin(19000), frameEnd(28000),
563586
frameBegin(29000), frameEnd(48000),
@@ -569,6 +592,12 @@ void main() {
569592
cpuUsage(5000, 20), cpuUsage(5010, 60),
570593
memoryUsage(6000, 20, 40), memoryUsage(6100, 30, 45),
571594
platformVsync(7000), vsyncCallback(7500),
595+
frameRequestPendingStart('1', 1000),
596+
frameRequestPendingEnd('1', 2000),
597+
frameRequestPendingStart('2', 3000),
598+
frameRequestPendingEnd('2', 5000),
599+
frameRequestPendingStart('3', 6000),
600+
frameRequestPendingEnd('3', 9000),
572601
]).writeTimelineToFile('test', destinationDirectory: tempDir.path);
573602
final String written =
574603
await fs.file(path.join(tempDir.path, 'test.timeline_summary.json')).readAsString();
@@ -591,7 +620,7 @@ void main() {
591620
'frame_build_times': <int>[17000, 9000, 19000],
592621
'frame_rasterizer_times': <int>[18000, 10000, 20000],
593622
'frame_begin_times': <int>[0, 18000, 28000],
594-
'frame_rasterizer_begin_times': <int>[0, 18000, 28000],
623+
'frame_rasterizer_begin_times': <int>[0, 18001, 28002],
595624
'average_vsync_transitions_missed': 8.0,
596625
'90th_percentile_vsync_transitions_missed': 12.0,
597626
'99th_percentile_vsync_transitions_missed': 12.0,
@@ -627,6 +656,9 @@ void main() {
627656
'90hz_frame_percentage': 0,
628657
'120hz_frame_percentage': 0,
629658
'illegal_refresh_rate_frame_count': 0,
659+
'average_frame_request_pending_latency': 2000.0,
660+
'90th_percentile_frame_request_pending_latency': 3000.0,
661+
'99th_percentile_frame_request_pending_latency': 3000.0,
630662
});
631663
});
632664
});

0 commit comments

Comments
 (0)