Unverified Commit 0053b089 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_driver] make empty duration messages more helpful (#110441)

parent 0c6d786e
......@@ -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.'),
)),
);
});
......
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