[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 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 {
|
|||||||
/// 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 {
|
|||||||
|
|
||||||
/// 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 {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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.'),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user