[flutter_driver] make empty duration messages more helpful (#110441)
This commit is contained in:
parent
0c6d786e3a
commit
0053b089a6
@ -20,6 +20,16 @@ import 'vsync_frame_lag_summarizer.dart';
|
||||
|
||||
const JsonEncoder _prettyEncoder = JsonEncoder.withIndent(' ');
|
||||
|
||||
const String _kEmptyDurationMessage = r'''
|
||||
The TimelineSummary had no events to summarize.
|
||||
|
||||
This can happen if the timeline summarization covered too short of a period
|
||||
or if the driver script failed to interact with the application to generate
|
||||
events. For example, if your driver script contained only a "driver.scroll()"
|
||||
command but the app under test was not scrollable then no events would be
|
||||
generated by the interaction.
|
||||
''';
|
||||
|
||||
/// The maximum amount of time considered safe to spend for a frame's build
|
||||
/// phase. Anything past that is in the danger of missing the frame as 60FPS.
|
||||
const Duration kBuildBudget = Duration(milliseconds: 16);
|
||||
@ -40,21 +50,21 @@ class TimelineSummary {
|
||||
/// Average amount of time spent per frame in the framework building widgets,
|
||||
/// updating layout, painting and compositing.
|
||||
///
|
||||
/// Returns null if no frames were recorded.
|
||||
/// Throws a [StateError] if this summary contains no timeline events.
|
||||
double computeAverageFrameBuildTimeMillis() {
|
||||
return _averageInMillis(_extractFrameDurations());
|
||||
}
|
||||
|
||||
/// The [p]-th percentile frame rasterization time in milliseconds.
|
||||
///
|
||||
/// Returns null if no frames were recorded.
|
||||
/// Throws a [StateError] if this summary contains no timeline events.
|
||||
double computePercentileFrameBuildTimeMillis(double p) {
|
||||
return _percentileInMillis(_extractFrameDurations(), p);
|
||||
}
|
||||
|
||||
/// The longest frame build time in milliseconds.
|
||||
///
|
||||
/// Returns null if no frames were recorded.
|
||||
/// Throws a [StateError] if this summary contains no timeline events.
|
||||
double computeWorstFrameBuildTimeMillis() {
|
||||
return _maxInMillis(_extractFrameDurations());
|
||||
}
|
||||
@ -67,21 +77,21 @@ class TimelineSummary {
|
||||
|
||||
/// Average amount of time spent per frame in the engine rasterizer.
|
||||
///
|
||||
/// Returns null if no frames were recorded.
|
||||
/// Throws a [StateError] if this summary contains no timeline events.
|
||||
double computeAverageFrameRasterizerTimeMillis() {
|
||||
return _averageInMillis(_extractGpuRasterizerDrawDurations());
|
||||
}
|
||||
|
||||
/// The longest frame rasterization time in milliseconds.
|
||||
///
|
||||
/// Returns null if no frames were recorded.
|
||||
/// Throws a [StateError] if this summary contains no timeline events.
|
||||
double computeWorstFrameRasterizerTimeMillis() {
|
||||
return _maxInMillis(_extractGpuRasterizerDrawDurations());
|
||||
}
|
||||
|
||||
/// The [p]-th percentile frame rasterization time in milliseconds.
|
||||
///
|
||||
/// Returns null if no frames were recorded.
|
||||
/// Throws a [StateError] if this summary contains no timeline events.
|
||||
double computePercentileFrameRasterizerTimeMillis(double p) {
|
||||
return _percentileInMillis(_extractGpuRasterizerDrawDurations(), p);
|
||||
}
|
||||
@ -409,26 +419,26 @@ class TimelineSummary {
|
||||
return result;
|
||||
}
|
||||
|
||||
double _averageInMillis(Iterable<Duration> durations) {
|
||||
double _averageInMillis(List<Duration> durations) {
|
||||
if (durations.isEmpty) {
|
||||
throw ArgumentError('durations is empty!');
|
||||
throw StateError(_kEmptyDurationMessage);
|
||||
}
|
||||
final double total = durations.fold<double>(0.0, (double t, Duration duration) => t + duration.inMicroseconds.toDouble() / 1000.0);
|
||||
return total / durations.length;
|
||||
}
|
||||
|
||||
double _percentileInMillis(Iterable<Duration> durations, double percentile) {
|
||||
double _percentileInMillis(List<Duration> durations, double percentile) {
|
||||
if (durations.isEmpty) {
|
||||
throw ArgumentError('durations is empty!');
|
||||
throw StateError(_kEmptyDurationMessage);
|
||||
}
|
||||
assert(percentile >= 0.0 && percentile <= 100.0);
|
||||
final List<double> doubles = durations.map<double>((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0).toList();
|
||||
return findPercentile(doubles, percentile);
|
||||
}
|
||||
|
||||
double _maxInMillis(Iterable<Duration> durations) {
|
||||
double _maxInMillis(List<Duration> durations) {
|
||||
if (durations.isEmpty) {
|
||||
throw ArgumentError('durations is empty!');
|
||||
throw StateError(_kEmptyDurationMessage);
|
||||
}
|
||||
return durations
|
||||
.map<double>((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0)
|
||||
|
@ -159,7 +159,12 @@ void main() {
|
||||
test('throws when there is no data', () {
|
||||
expect(
|
||||
() => summarize(<Map<String, dynamic>>[]).computeAverageFrameBuildTimeMillis(),
|
||||
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
|
||||
throwsA(
|
||||
isA<StateError>()
|
||||
.having((StateError e) => e.message,
|
||||
'message',
|
||||
contains('The TimelineSummary had no events to summarize.'),
|
||||
)),
|
||||
);
|
||||
});
|
||||
|
||||
@ -223,7 +228,12 @@ void main() {
|
||||
test('throws when there is no data', () {
|
||||
expect(
|
||||
() => summarize(<Map<String, dynamic>>[]).computeWorstFrameBuildTimeMillis(),
|
||||
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
|
||||
throwsA(
|
||||
isA<StateError>()
|
||||
.having((StateError e) => e.message,
|
||||
'message',
|
||||
contains('The TimelineSummary had no events to summarize.'),
|
||||
)),
|
||||
);
|
||||
});
|
||||
|
||||
@ -282,7 +292,12 @@ void main() {
|
||||
test('throws when there is no data', () {
|
||||
expect(
|
||||
() => summarize(<Map<String, dynamic>>[]).computeAverageFrameRasterizerTimeMillis(),
|
||||
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
|
||||
throwsA(
|
||||
isA<StateError>()
|
||||
.having((StateError e) => e.message,
|
||||
'message',
|
||||
contains('The TimelineSummary had no events to summarize.'),
|
||||
)),
|
||||
);
|
||||
});
|
||||
|
||||
@ -321,7 +336,12 @@ void main() {
|
||||
test('throws when there is no data', () {
|
||||
expect(
|
||||
() => summarize(<Map<String, dynamic>>[]).computeWorstFrameRasterizerTimeMillis(),
|
||||
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
|
||||
throwsA(
|
||||
isA<StateError>()
|
||||
.having((StateError e) => e.message,
|
||||
'message',
|
||||
contains('The TimelineSummary had no events to summarize.'),
|
||||
)),
|
||||
);
|
||||
});
|
||||
|
||||
@ -368,7 +388,12 @@ void main() {
|
||||
test('throws when there is no data', () {
|
||||
expect(
|
||||
() => summarize(<Map<String, dynamic>>[]).computePercentileFrameRasterizerTimeMillis(90.0),
|
||||
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
|
||||
throwsA(
|
||||
isA<StateError>()
|
||||
.having((StateError e) => e.message,
|
||||
'message',
|
||||
contains('The TimelineSummary had no events to summarize.'),
|
||||
)),
|
||||
);
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user