[flutter_driver] show refresh rate status in timeline summary (#95699)
This commit is contained in:
parent
5f3bee55ad
commit
0ce527eb91
@ -0,0 +1,130 @@
|
||||
// Copyright 2014 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 'timeline.dart';
|
||||
|
||||
/// Event name for refresh rate related timeline events.
|
||||
const String kUIThreadVsyncProcessEvent = 'VsyncProcessCallback';
|
||||
|
||||
/// A summary of [TimelineEvents]s corresponding to `kUIThreadVsyncProcessEvent` events.
|
||||
///
|
||||
/// `RefreshRate` is the time between the start of a vsync pulse and the target time of that vsync.
|
||||
class RefreshRateSummary {
|
||||
|
||||
/// Creates a [RefreshRateSummary] given the timeline events.
|
||||
factory RefreshRateSummary({required List<TimelineEvent> vsyncEvents}) {
|
||||
return RefreshRateSummary._(refreshRates: _computeRefreshRates(vsyncEvents));
|
||||
}
|
||||
|
||||
RefreshRateSummary._({required List<double> refreshRates}) {
|
||||
_numberOfTotalFrames = refreshRates.length;
|
||||
for (final double refreshRate in refreshRates) {
|
||||
if ((refreshRate - 30).abs() < _kErrorMargin) {
|
||||
_numberOf30HzFrames++;
|
||||
continue;
|
||||
}
|
||||
if ((refreshRate - 60).abs() < _kErrorMargin) {
|
||||
_numberOf60HzFrames++;
|
||||
continue;
|
||||
}
|
||||
if ((refreshRate - 90).abs() < _kErrorMargin) {
|
||||
_numberOf90HzFrames++;
|
||||
continue;
|
||||
}
|
||||
if ((refreshRate - 120).abs() < _kErrorMargin) {
|
||||
_numberOf120HzFrames++;
|
||||
continue;
|
||||
}
|
||||
_framesWithIllegalRefreshRate.add(refreshRate);
|
||||
}
|
||||
assert(_numberOfTotalFrames ==
|
||||
_numberOf30HzFrames +
|
||||
_numberOf60HzFrames +
|
||||
_numberOf90HzFrames +
|
||||
_numberOf120HzFrames +
|
||||
_framesWithIllegalRefreshRate.length);
|
||||
}
|
||||
|
||||
static const double _kErrorMargin = 6.0;
|
||||
|
||||
/// Number of frames with 30hz refresh rate
|
||||
int get numberOf30HzFrames => _numberOf30HzFrames;
|
||||
|
||||
/// Number of frames with 60hz refresh rate
|
||||
int get numberOf60HzFrames => _numberOf60HzFrames;
|
||||
|
||||
/// Number of frames with 90hz refresh rate
|
||||
int get numberOf90HzFrames => _numberOf90HzFrames;
|
||||
|
||||
/// Number of frames with 120hz refresh rate
|
||||
int get numberOf120HzFrames => _numberOf120HzFrames;
|
||||
|
||||
/// The percentage of 30hz frames.
|
||||
///
|
||||
/// For example, if this value is 20, it means there are 20 percent of total
|
||||
/// frames are 30hz. 0 means no frames are 30hz, 100 means all frames are 30hz.
|
||||
double get percentageOf30HzFrames => _numberOfTotalFrames > 0
|
||||
? _numberOf30HzFrames / _numberOfTotalFrames * 100
|
||||
: 0;
|
||||
|
||||
/// The percentage of 60hz frames.
|
||||
///
|
||||
/// For example, if this value is 20, it means there are 20 percent of total
|
||||
/// frames are 60hz. 0 means no frames are 60hz, 100 means all frames are 60hz.
|
||||
double get percentageOf60HzFrames => _numberOfTotalFrames > 0
|
||||
? _numberOf60HzFrames / _numberOfTotalFrames * 100
|
||||
: 0;
|
||||
|
||||
/// The percentage of 90hz frames.
|
||||
///
|
||||
/// For example, if this value is 20, it means there are 20 percent of total
|
||||
/// frames are 90hz. 0 means no frames are 90hz, 100 means all frames are 90hz.
|
||||
double get percentageOf90HzFrames => _numberOfTotalFrames > 0
|
||||
? _numberOf90HzFrames / _numberOfTotalFrames * 100
|
||||
: 0;
|
||||
|
||||
/// The percentage of 90hz frames.
|
||||
///
|
||||
/// For example, if this value is 20, it means there are 20 percent of total
|
||||
/// frames are 120hz. 0 means no frames are 120hz, 100 means all frames are 120hz.
|
||||
double get percentageOf120HzFrames => _numberOfTotalFrames > 0
|
||||
? _numberOf120HzFrames / _numberOfTotalFrames * 100
|
||||
: 0;
|
||||
|
||||
/// A list of all the frames with Illegal refresh rate.
|
||||
///
|
||||
/// A refresh rate is consider illegal if it does not belong to anyone below:
|
||||
/// 30hz, 60hz, 90hz or 120hz.
|
||||
List<double> get framesWithIllegalRefreshRate =>
|
||||
_framesWithIllegalRefreshRate;
|
||||
|
||||
int _numberOf30HzFrames = 0;
|
||||
int _numberOf60HzFrames = 0;
|
||||
int _numberOf90HzFrames = 0;
|
||||
int _numberOf120HzFrames = 0;
|
||||
int _numberOfTotalFrames = 0;
|
||||
|
||||
final List<double> _framesWithIllegalRefreshRate = <double>[];
|
||||
|
||||
static List<double> _computeRefreshRates(List<TimelineEvent> vsyncEvents) {
|
||||
final List<double> result = <double>[];
|
||||
for (int i = 0; i < vsyncEvents.length; i++) {
|
||||
final TimelineEvent event = vsyncEvents[i];
|
||||
if (event.phase != 'B') {
|
||||
continue;
|
||||
}
|
||||
assert(event.name == kUIThreadVsyncProcessEvent);
|
||||
assert(event.arguments != null);
|
||||
final Map<String, dynamic> arguments = event.arguments!;
|
||||
const double nanosecondsPerSecond = 1e+9;
|
||||
final int startTimeInNanoseconds = int.parse(arguments['StartTime'] as String);
|
||||
final int targetTimeInNanoseconds = int.parse(arguments['TargetTime'] as String);
|
||||
final int frameDurationInNanoseconds = targetTimeInNanoseconds - startTimeInNanoseconds;
|
||||
final double refreshRate = nanosecondsPerSecond /
|
||||
frameDurationInNanoseconds;
|
||||
result.add(refreshRate);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import 'gc_summarizer.dart';
|
||||
import 'percentile_utils.dart';
|
||||
import 'profiling_summarizer.dart';
|
||||
import 'raster_cache_summarizer.dart';
|
||||
import 'refresh_rate_summarizer.dart';
|
||||
import 'scene_display_lag_summarizer.dart';
|
||||
import 'timeline.dart';
|
||||
import 'vsync_frame_lag_summarizer.dart';
|
||||
@ -220,6 +221,7 @@ class TimelineSummary {
|
||||
final Map<String, dynamic> profilingSummary = _profilingSummarizer().summarize();
|
||||
final RasterCacheSummarizer rasterCacheSummarizer = _rasterCacheSummarizer();
|
||||
final GCSummarizer gcSummarizer = _gcSummarizer();
|
||||
final RefreshRateSummary refreshRateSummary = RefreshRateSummary(vsyncEvents: _extractNamedEvents(kUIThreadVsyncProcessEvent));
|
||||
|
||||
final Map<String, dynamic> timelineSummary = <String, dynamic>{
|
||||
'average_frame_build_time_millis': computeAverageFrameBuildTimeMillis(),
|
||||
@ -271,6 +273,11 @@ class TimelineSummary {
|
||||
'99th_percentile_picture_cache_memory': rasterCacheSummarizer.computePercentilePictureMemory(99.0),
|
||||
'worst_picture_cache_memory': rasterCacheSummarizer.computeWorstPictureMemory(),
|
||||
'total_ui_gc_time': gcSummarizer.totalGCTimeMillis,
|
||||
'30hz_frame_percentage': refreshRateSummary.percentageOf30HzFrames,
|
||||
'60hz_frame_percentage': refreshRateSummary.percentageOf60HzFrames,
|
||||
'90hz_frame_percentage': refreshRateSummary.percentageOf90HzFrames,
|
||||
'120hz_frame_percentage': refreshRateSummary.percentageOf120HzFrames,
|
||||
'illegal_refresh_rate_frame_count': refreshRateSummary.framesWithIllegalRefreshRate.length,
|
||||
};
|
||||
|
||||
timelineSummary.addAll(profilingSummary);
|
||||
|
@ -3,10 +3,12 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert' show json;
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:flutter_driver/src/driver/profiling_summarizer.dart';
|
||||
import 'package:flutter_driver/src/driver/refresh_rate_summarizer.dart';
|
||||
import 'package:flutter_driver/src/driver/scene_display_lag_summarizer.dart';
|
||||
import 'package:flutter_driver/src/driver/vsync_frame_lag_summarizer.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
@ -89,10 +91,14 @@ void main() {
|
||||
'ts': timeStamp,
|
||||
};
|
||||
|
||||
Map<String, dynamic> vsyncCallback(int timeStamp) => <String, dynamic>{
|
||||
Map<String, dynamic> vsyncCallback(int timeStamp, {String phase = 'B', String startTime = '2750850055428', String endTime = '2750866722095'}) => <String, dynamic>{
|
||||
'name': 'VsyncProcessCallback',
|
||||
'ph': 'B',
|
||||
'ph': phase,
|
||||
'ts': timeStamp,
|
||||
'args': <String, dynamic>{
|
||||
'StartTime': startTime,
|
||||
'TargetTime': endTime,
|
||||
}
|
||||
};
|
||||
|
||||
List<Map<String, dynamic>> _genGC(String name, int count, int startTime, int timeDiff) {
|
||||
@ -467,6 +473,11 @@ void main() {
|
||||
'99th_percentile_picture_cache_memory': 0.0,
|
||||
'worst_picture_cache_memory': 0.0,
|
||||
'total_ui_gc_time': 0.4,
|
||||
'30hz_frame_percentage': 0,
|
||||
'60hz_frame_percentage': 0,
|
||||
'90hz_frame_percentage': 0,
|
||||
'120hz_frame_percentage': 0,
|
||||
'illegal_refresh_rate_frame_count': 0,
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -582,6 +593,11 @@ void main() {
|
||||
'99th_percentile_picture_cache_memory': 0.0,
|
||||
'worst_picture_cache_memory': 0.0,
|
||||
'total_ui_gc_time': 0.4,
|
||||
'30hz_frame_percentage': 0,
|
||||
'60hz_frame_percentage': 100,
|
||||
'90hz_frame_percentage': 0,
|
||||
'120hz_frame_percentage': 0,
|
||||
'illegal_refresh_rate_frame_count': 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -734,5 +750,173 @@ void main() {
|
||||
expect(summarizer.computePercentileVsyncFrameLag(99), 990);
|
||||
});
|
||||
});
|
||||
|
||||
group('RefreshRateSummarizer tests', () {
|
||||
|
||||
const double kCompareDelta = 0.01;
|
||||
RefreshRateSummary _summarize(List<Map<String, dynamic>> traceEvents) {
|
||||
final Timeline timeline = Timeline.fromJson(<String, dynamic>{
|
||||
'traceEvents': traceEvents,
|
||||
});
|
||||
return RefreshRateSummary(vsyncEvents: timeline.events!);
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _populateEvents({required int numberOfEvents, required int startTime, required int interval, required int margin}) {
|
||||
final List<Map<String, dynamic>> events = <Map<String, dynamic>>[];
|
||||
int startTimeInNanoseconds = startTime;
|
||||
for (int i = 0; i < numberOfEvents; i ++) {
|
||||
final int randomMargin = margin >= 1 ? (-margin + Random().nextInt(margin*2)) : 0;
|
||||
final int endTime = startTimeInNanoseconds + interval + randomMargin;
|
||||
events.add(vsyncCallback(0, startTime: startTimeInNanoseconds.toString(), endTime: endTime.toString()));
|
||||
startTimeInNanoseconds = endTime;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
test('Recognize 30 hz frames.', () async {
|
||||
const int startTimeInNanoseconds = 2750850055430;
|
||||
const int intervalInNanoseconds = 33333333;
|
||||
// allow some margins
|
||||
const int margin = 3000000;
|
||||
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100,
|
||||
startTime: startTimeInNanoseconds,
|
||||
interval: intervalInNanoseconds,
|
||||
margin: margin,
|
||||
);
|
||||
final RefreshRateSummary summary = _summarize(events);
|
||||
expect(summary.percentageOf30HzFrames, closeTo(100, kCompareDelta));
|
||||
expect(summary.percentageOf60HzFrames, 0);
|
||||
expect(summary.percentageOf90HzFrames, 0);
|
||||
expect(summary.percentageOf120HzFrames, 0);
|
||||
expect(summary.framesWithIllegalRefreshRate, isEmpty);
|
||||
});
|
||||
|
||||
test('Recognize 60 hz frames.', () async {
|
||||
const int startTimeInNanoseconds = 2750850055430;
|
||||
const int intervalInNanoseconds = 16666666;
|
||||
// allow some margins
|
||||
const int margin = 1200000;
|
||||
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100,
|
||||
startTime: startTimeInNanoseconds,
|
||||
interval: intervalInNanoseconds,
|
||||
margin: margin,
|
||||
);
|
||||
|
||||
final RefreshRateSummary summary = _summarize(events);
|
||||
expect(summary.percentageOf30HzFrames, 0);
|
||||
expect(summary.percentageOf60HzFrames, closeTo(100, kCompareDelta));
|
||||
expect(summary.percentageOf90HzFrames, 0);
|
||||
expect(summary.percentageOf120HzFrames, 0);
|
||||
expect(summary.framesWithIllegalRefreshRate, isEmpty);
|
||||
});
|
||||
|
||||
test('Recognize 90 hz frames.', () async {
|
||||
const int startTimeInNanoseconds = 2750850055430;
|
||||
const int intervalInNanoseconds = 11111111;
|
||||
// allow some margins
|
||||
const int margin = 500000;
|
||||
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100,
|
||||
startTime: startTimeInNanoseconds,
|
||||
interval: intervalInNanoseconds,
|
||||
margin: margin,
|
||||
);
|
||||
|
||||
final RefreshRateSummary summary = _summarize(events);
|
||||
expect(summary.percentageOf30HzFrames, 0);
|
||||
expect(summary.percentageOf60HzFrames, 0);
|
||||
expect(summary.percentageOf90HzFrames, closeTo(100, kCompareDelta));
|
||||
expect(summary.percentageOf120HzFrames, 0);
|
||||
expect(summary.framesWithIllegalRefreshRate, isEmpty);
|
||||
});
|
||||
|
||||
test('Recognize 120 hz frames.', () async {
|
||||
const int startTimeInNanoseconds = 2750850055430;
|
||||
const int intervalInNanoseconds = 8333333;
|
||||
// allow some margins
|
||||
const int margin = 300000;
|
||||
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100,
|
||||
startTime: startTimeInNanoseconds,
|
||||
interval: intervalInNanoseconds,
|
||||
margin: margin,
|
||||
);
|
||||
final RefreshRateSummary summary = _summarize(events);
|
||||
expect(summary.percentageOf30HzFrames, 0);
|
||||
expect(summary.percentageOf60HzFrames, 0);
|
||||
expect(summary.percentageOf90HzFrames, 0);
|
||||
expect(summary.percentageOf120HzFrames, closeTo(100, kCompareDelta));
|
||||
expect(summary.framesWithIllegalRefreshRate, isEmpty);
|
||||
});
|
||||
|
||||
test('Identify illegal refresh rates.', () async {
|
||||
const int startTimeInNanoseconds = 2750850055430;
|
||||
const int intervalInNanoseconds = 10000000;
|
||||
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 1,
|
||||
startTime: startTimeInNanoseconds,
|
||||
interval: intervalInNanoseconds,
|
||||
margin: 0,
|
||||
);
|
||||
final RefreshRateSummary summary = _summarize(events);
|
||||
expect(summary.percentageOf30HzFrames, 0);
|
||||
expect(summary.percentageOf60HzFrames, 0);
|
||||
expect(summary.percentageOf90HzFrames, 0);
|
||||
expect(summary.percentageOf120HzFrames, 0);
|
||||
expect(summary.framesWithIllegalRefreshRate, isNotEmpty);
|
||||
expect(summary.framesWithIllegalRefreshRate.first, closeTo(100, kCompareDelta));
|
||||
});
|
||||
|
||||
test('Mixed refresh rates.', () async {
|
||||
|
||||
final List<Map<String, dynamic>> events = <Map<String, dynamic>>[];
|
||||
const int num30Hz = 10;
|
||||
const int num60Hz = 20;
|
||||
const int num90Hz = 20;
|
||||
const int num120Hz = 40;
|
||||
const int numIllegal = 10;
|
||||
|
||||
// Add 30hz frames
|
||||
events.addAll(_populateEvents(numberOfEvents: num30Hz,
|
||||
startTime: 0,
|
||||
interval: 32000000,
|
||||
margin: 0,
|
||||
));
|
||||
|
||||
// Add 60hz frames
|
||||
events.addAll(_populateEvents(numberOfEvents: num60Hz,
|
||||
startTime: 0,
|
||||
interval: 16000000,
|
||||
margin: 0,
|
||||
));
|
||||
|
||||
|
||||
// Add 90hz frames
|
||||
events.addAll(_populateEvents(numberOfEvents: num90Hz,
|
||||
startTime: 0,
|
||||
interval: 11000000,
|
||||
margin: 0,
|
||||
));
|
||||
|
||||
// Add 120hz frames
|
||||
events.addAll(_populateEvents(numberOfEvents: num120Hz,
|
||||
startTime: 0,
|
||||
interval: 8000000,
|
||||
margin: 0,
|
||||
));
|
||||
|
||||
// Add illegal refresh rate frames
|
||||
events.addAll(_populateEvents(numberOfEvents: numIllegal,
|
||||
startTime: 0,
|
||||
interval: 60000,
|
||||
margin: 0,
|
||||
));
|
||||
|
||||
final RefreshRateSummary summary = _summarize(events);
|
||||
expect(summary.percentageOf30HzFrames, closeTo(num30Hz, kCompareDelta));
|
||||
expect(summary.percentageOf60HzFrames, closeTo(num60Hz, kCompareDelta));
|
||||
expect(summary.percentageOf90HzFrames, closeTo(num90Hz, kCompareDelta));
|
||||
expect(summary.percentageOf120HzFrames, closeTo(num120Hz, kCompareDelta));
|
||||
expect(summary.framesWithIllegalRefreshRate, isNotEmpty);
|
||||
expect(summary.framesWithIllegalRefreshRate.length, 10);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user