diff --git a/packages/flutter_driver/lib/src/driver/timeline_summary.dart b/packages/flutter_driver/lib/src/driver/timeline_summary.dart index 051a73d7968..5a613e11052 100644 --- a/packages/flutter_driver/lib/src/driver/timeline_summary.dart +++ b/packages/flutter_driver/lib/src/driver/timeline_summary.dart @@ -60,6 +60,13 @@ class TimelineSummary { return _maxInMillis(_extractDuration(_extractGpuRasterizerDrawEvents())); } + /// The [p]-th percentile frame rasterization time in milliseconds. + /// + /// Returns null if no frames were recorded. + double computePercentileFrameRasterizerTimeMillis(double p) { + return _percentileInMillis(_extractDuration(_extractGpuRasterizerDrawEvents()), p); + } + /// The number of frames that missed the [kBuildBudget] on the GPU and /// therefore are in the danger of missing frames. int computeMissedFrameRasterizerBudgetCount([Duration frameBuildBudget = kBuildBudget]) => _extractGpuRasterizerDrawEvents() @@ -76,6 +83,8 @@ class TimelineSummary { 'worst_frame_build_time_millis': computeWorstFrameBuildTimeMillis(), 'missed_frame_build_budget_count': computeMissedFrameBuildBudgetCount(), 'average_frame_rasterizer_time_millis': computeAverageFrameRasterizerTimeMillis(), + '90th_percentile_frame_rasterizer_time_millis': computePercentileFrameRasterizerTimeMillis(90.0), + '99th_percentile_frame_rasterizer_time_millis': computePercentileFrameRasterizerTimeMillis(99.0), 'worst_frame_rasterizer_time_millis': computeWorstFrameRasterizerTimeMillis(), 'missed_frame_rasterizer_budget_count': computeMissedFrameRasterizerBudgetCount(), 'frame_count': countFrames(), @@ -159,6 +168,17 @@ class TimelineSummary { return total / durations.length; } + double _percentileInMillis(Iterable durations, double percentile) { + if (durations.isEmpty) + return null; + + assert(percentile >= 0.0 && percentile <= 100.0); + final List doubles = durations.map((Duration duration) => duration.inMilliseconds.toDouble()).toList(); + doubles.sort(); + return doubles[((doubles.length - 1) * (percentile / 100)).round()]; + + } + double _maxInMillis(Iterable durations) { if (durations.isEmpty) return null; diff --git a/packages/flutter_driver/test/src/timeline_summary_test.dart b/packages/flutter_driver/test/src/timeline_summary_test.dart index dc9dd8ea223..4a35dbf5943 100644 --- a/packages/flutter_driver/test/src/timeline_summary_test.dart +++ b/packages/flutter_driver/test/src/timeline_summary_test.dart @@ -31,6 +31,17 @@ void main() { 'name': 'GPURasterizer::Draw', 'ph': 'E', 'ts': timeStamp }; + List> rasterizeTimeSequenceInMillis(List sequence) { + final List> result = >[]; + int t = 0; + for(int duration in sequence) { + result.add(begin(t)); + t += duration * 1000; + result.add(end(t)); + } + return result; + } + group('frame_count', () { test('counts frames', () { expect( @@ -174,6 +185,44 @@ void main() { }); }); + group('percentile_frame_rasterizer_time_millis', () { + test('returns null when there is no data', () { + expect(summarize(>[]).computeWorstFrameRasterizerTimeMillis(), isNull); + }); + + const List> sequences = >[ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] + ]; + + const List p90s = [ + 9, + 5, + 18 + ]; + + test('computes 90th frame rasterizer time in milliseconds', () { + for(int i = 0; i < sequences.length; ++i) { + expect( + summarize(rasterizeTimeSequenceInMillis(sequences[i])).computePercentileFrameRasterizerTimeMillis(90.0), + p90s[i] + ); + } + }); + + test('compute 99th frame rasterizer time in milliseconds', () { + final List sequence = []; + for(int i = 1; i <= 100; ++i) { + sequence.add(i); + } + expect( + summarize(rasterizeTimeSequenceInMillis(sequence)).computePercentileFrameRasterizerTimeMillis(99.0), + 99 + ); + }); + }); + group('computeMissedFrameRasterizerBudgetCount', () { test('computes the number of missed rasterizer budgets', () { final TimelineSummary summary = summarize(>[ @@ -202,6 +251,8 @@ void main() { 'worst_frame_build_time_millis': 11.0, 'missed_frame_build_budget_count': 2, 'average_frame_rasterizer_time_millis': 8.0, + '90th_percentile_frame_rasterizer_time_millis': 12.0, + '99th_percentile_frame_rasterizer_time_millis': 12.0, 'worst_frame_rasterizer_time_millis': 12.0, 'missed_frame_rasterizer_budget_count': 2, 'frame_count': 3, @@ -250,6 +301,8 @@ void main() { 'worst_frame_build_time_millis': 11.0, 'missed_frame_build_budget_count': 2, 'average_frame_rasterizer_time_millis': 8.0, + '90th_percentile_frame_rasterizer_time_millis': 12.0, + '99th_percentile_frame_rasterizer_time_millis': 12.0, 'worst_frame_rasterizer_time_millis': 12.0, 'missed_frame_rasterizer_budget_count': 2, 'frame_count': 3,