Unverified Commit 0ce527eb authored by Chris Yang's avatar Chris Yang Committed by GitHub

[flutter_driver] show refresh rate status in timeline summary (#95699)

parent 5f3bee55
// 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);
});
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment