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'; ...@@ -20,6 +20,16 @@ import 'vsync_frame_lag_summarizer.dart';
const JsonEncoder _prettyEncoder = JsonEncoder.withIndent(' '); 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 /// 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. /// phase. Anything past that is in the danger of missing the frame as 60FPS.
const Duration kBuildBudget = Duration(milliseconds: 16); const Duration kBuildBudget = Duration(milliseconds: 16);
...@@ -40,21 +50,21 @@ class TimelineSummary { ...@@ -40,21 +50,21 @@ class TimelineSummary {
/// Average amount of time spent per frame in the framework building widgets, /// Average amount of time spent per frame in the framework building widgets,
/// updating layout, painting and compositing. /// updating layout, painting and compositing.
/// ///
/// Returns null if no frames were recorded. /// Throws a [StateError] if this summary contains no timeline events.
double computeAverageFrameBuildTimeMillis() { double computeAverageFrameBuildTimeMillis() {
return _averageInMillis(_extractFrameDurations()); return _averageInMillis(_extractFrameDurations());
} }
/// The [p]-th percentile frame rasterization time in milliseconds. /// 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) { double computePercentileFrameBuildTimeMillis(double p) {
return _percentileInMillis(_extractFrameDurations(), p); return _percentileInMillis(_extractFrameDurations(), p);
} }
/// The longest frame build time in milliseconds. /// 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() { double computeWorstFrameBuildTimeMillis() {
return _maxInMillis(_extractFrameDurations()); return _maxInMillis(_extractFrameDurations());
} }
...@@ -67,21 +77,21 @@ class TimelineSummary { ...@@ -67,21 +77,21 @@ class TimelineSummary {
/// Average amount of time spent per frame in the engine rasterizer. /// 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() { double computeAverageFrameRasterizerTimeMillis() {
return _averageInMillis(_extractGpuRasterizerDrawDurations()); return _averageInMillis(_extractGpuRasterizerDrawDurations());
} }
/// The longest frame rasterization time in milliseconds. /// 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() { double computeWorstFrameRasterizerTimeMillis() {
return _maxInMillis(_extractGpuRasterizerDrawDurations()); return _maxInMillis(_extractGpuRasterizerDrawDurations());
} }
/// The [p]-th percentile frame rasterization time in milliseconds. /// 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) { double computePercentileFrameRasterizerTimeMillis(double p) {
return _percentileInMillis(_extractGpuRasterizerDrawDurations(), p); return _percentileInMillis(_extractGpuRasterizerDrawDurations(), p);
} }
...@@ -409,26 +419,26 @@ class TimelineSummary { ...@@ -409,26 +419,26 @@ class TimelineSummary {
return result; return result;
} }
double _averageInMillis(Iterable<Duration> durations) { double _averageInMillis(List<Duration> durations) {
if (durations.isEmpty) { 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); final double total = durations.fold<double>(0.0, (double t, Duration duration) => t + duration.inMicroseconds.toDouble() / 1000.0);
return total / durations.length; return total / durations.length;
} }
double _percentileInMillis(Iterable<Duration> durations, double percentile) { double _percentileInMillis(List<Duration> durations, double percentile) {
if (durations.isEmpty) { if (durations.isEmpty) {
throw ArgumentError('durations is empty!'); throw StateError(_kEmptyDurationMessage);
} }
assert(percentile >= 0.0 && percentile <= 100.0); assert(percentile >= 0.0 && percentile <= 100.0);
final List<double> doubles = durations.map<double>((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0).toList(); final List<double> doubles = durations.map<double>((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0).toList();
return findPercentile(doubles, percentile); return findPercentile(doubles, percentile);
} }
double _maxInMillis(Iterable<Duration> durations) { double _maxInMillis(List<Duration> durations) {
if (durations.isEmpty) { if (durations.isEmpty) {
throw ArgumentError('durations is empty!'); throw StateError(_kEmptyDurationMessage);
} }
return durations return durations
.map<double>((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0) .map<double>((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0)
......
...@@ -159,7 +159,12 @@ void main() { ...@@ -159,7 +159,12 @@ void main() {
test('throws when there is no data', () { test('throws when there is no data', () {
expect( expect(
() => summarize(<Map<String, dynamic>>[]).computeAverageFrameBuildTimeMillis(), () => 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() { ...@@ -223,7 +228,12 @@ void main() {
test('throws when there is no data', () { test('throws when there is no data', () {
expect( expect(
() => summarize(<Map<String, dynamic>>[]).computeWorstFrameBuildTimeMillis(), () => 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() { ...@@ -282,7 +292,12 @@ void main() {
test('throws when there is no data', () { test('throws when there is no data', () {
expect( expect(
() => summarize(<Map<String, dynamic>>[]).computeAverageFrameRasterizerTimeMillis(), () => 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() { ...@@ -321,7 +336,12 @@ void main() {
test('throws when there is no data', () { test('throws when there is no data', () {
expect( expect(
() => summarize(<Map<String, dynamic>>[]).computeWorstFrameRasterizerTimeMillis(), () => 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() { ...@@ -368,7 +388,12 @@ void main() {
test('throws when there is no data', () { test('throws when there is no data', () {
expect( expect(
() => summarize(<Map<String, dynamic>>[]).computePercentileFrameRasterizerTimeMillis(90.0), () => 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