diff --git a/packages/flutter_test/lib/src/frame_timing_summarizer.dart b/packages/flutter_test/lib/src/frame_timing_summarizer.dart index 55bb67b97f..c6e3c464b8 100644 --- a/packages/flutter_test/lib/src/frame_timing_summarizer.dart +++ b/packages/flutter_test/lib/src/frame_timing_summarizer.dart @@ -294,16 +294,21 @@ class FrameTimingSummarizer { }; } -// The following helper functions require data sorted - -// return the 100*p-th percentile of the data +/// Returns the 100*p-th percentile of [data]. +/// +/// [data] must be sorted in ascending order. T _findPercentile(List data, double p) { assert(p >= 0 && p <= 1); return data[((data.length - 1) * p).round()]; } -// return the number of items in data that > threshold +/// Returns the number of elements in [data] that exceed [threshold]. +/// +/// [data] must be sorted in ascending order. int _countExceed>(List data, T threshold) { - return data.length - - data.indexWhere((T datum) => datum.compareTo(threshold) > 0); + final int exceedsThresholdIndex = data.indexWhere((T datum) => datum.compareTo(threshold) > 0); + if (exceedsThresholdIndex == -1) { + return 0; + } + return data.length - exceedsThresholdIndex; } diff --git a/packages/flutter_test/test/frame_timing_summarizer_test.dart b/packages/flutter_test/test/frame_timing_summarizer_test.dart index d58548a37f..0f11300936 100644 --- a/packages/flutter_test/test/frame_timing_summarizer_test.dart +++ b/packages/flutter_test/test/frame_timing_summarizer_test.dart @@ -7,51 +7,135 @@ import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; void main() { - test('Test FrameTimingSummarizer', () { - List vsyncTimes = [ - for (int i = 0; i < 100; i += 1) 100 * (i + 1), - ]; - List buildTimes = [ - for (int i = 0; i < 100; i += 1) vsyncTimes[i] + 1000 * (i + 1), - ]; - List rasterTimes = [ - for (int i = 0; i < 100; i += 1) 1000 * (i + 1) + 1000, - ]; - // reversed to make sure sort is working. - buildTimes = buildTimes.reversed.toList(); - rasterTimes = rasterTimes.reversed.toList(); - vsyncTimes = vsyncTimes.reversed.toList(); - final List inputData = [ - for (int i = 0; i < 100; i += 1) - FrameTiming( - vsyncStart: 0, - buildStart: vsyncTimes[i], - buildFinish: buildTimes[i], - rasterStart: 500, - rasterFinish: rasterTimes[i], - // Wall time should not be used in any profiling metrics. - // It is primarily to correlate with external tools' measurement. - rasterFinishWallTime: 0, - ), - ]; + group(FrameTimingSummarizer, () { + test('calculates all fields', () { + List vsyncTimes = [ + for (int i = 0; i < 100; i += 1) 100 * (i + 1), + ]; + List buildTimes = [ + for (int i = 0; i < 100; i += 1) vsyncTimes[i] + 1000 * (i + 1), + ]; + List rasterTimes = [ + for (int i = 0; i < 100; i += 1) 1000 * (i + 1) + 1000, + ]; + // reversed to make sure sort is working. + buildTimes = buildTimes.reversed.toList(); + rasterTimes = rasterTimes.reversed.toList(); + vsyncTimes = vsyncTimes.reversed.toList(); + final List inputData = [ + for (int i = 0; i < 100; i += 1) + FrameTiming( + vsyncStart: 0, + buildStart: vsyncTimes[i], + buildFinish: buildTimes[i], + rasterStart: 500, + rasterFinish: rasterTimes[i], + // Wall time should not be used in any profiling metrics. + // It is primarily to correlate with external tools' measurement. + rasterFinishWallTime: 0, + ), + ]; - final FrameTimingSummarizer summary = FrameTimingSummarizer(inputData); - expect(summary.averageFrameBuildTime.inMicroseconds, 50500); - expect(summary.p90FrameBuildTime.inMicroseconds, 90000); - expect(summary.p99FrameBuildTime.inMicroseconds, 99000); - expect(summary.worstFrameBuildTime.inMicroseconds, 100000); - expect(summary.missedFrameBuildBudget, 84); + final FrameTimingSummarizer summary = FrameTimingSummarizer(inputData); + expect(summary.averageFrameBuildTime.inMicroseconds, 50500); + expect(summary.p90FrameBuildTime.inMicroseconds, 90000); + expect(summary.p99FrameBuildTime.inMicroseconds, 99000); + expect(summary.worstFrameBuildTime.inMicroseconds, 100000); + expect(summary.missedFrameBuildBudget, 84); - expect(summary.averageFrameRasterizerTime.inMicroseconds, 51000); - expect(summary.p90FrameRasterizerTime.inMicroseconds, 90500); - expect(summary.p99FrameRasterizerTime.inMicroseconds, 99500); - expect(summary.worstFrameRasterizerTime.inMicroseconds, 100500); - expect(summary.missedFrameRasterizerBudget, 85); - expect(summary.frameBuildTime.length, 100); + expect(summary.averageFrameRasterizerTime.inMicroseconds, 51000); + expect(summary.p90FrameRasterizerTime.inMicroseconds, 90500); + expect(summary.p99FrameRasterizerTime.inMicroseconds, 99500); + expect(summary.worstFrameRasterizerTime.inMicroseconds, 100500); + expect(summary.missedFrameRasterizerBudget, 85); + expect(summary.frameBuildTime.length, 100); - expect(summary.averageVsyncOverhead.inMicroseconds, 5050); - expect(summary.p90VsyncOverhead.inMicroseconds, 9000); - expect(summary.p99VsyncOverhead.inMicroseconds, 9900); - expect(summary.worstVsyncOverhead.inMicroseconds, 10000); + expect(summary.averageVsyncOverhead.inMicroseconds, 5050); + expect(summary.p90VsyncOverhead.inMicroseconds, 9000); + expect(summary.p99VsyncOverhead.inMicroseconds, 9900); + expect(summary.worstVsyncOverhead.inMicroseconds, 10000); + }); + + group('missed budget count', () { + test('when single element missed budget', () { + final FrameTimingSummarizer summary = FrameTimingSummarizer([ + FrameTiming( + buildStart: 0, + buildFinish: (kBuildBudget + const Duration(microseconds: 1)).inMicroseconds, + vsyncStart: 0, + rasterStart: 0, + rasterFinish: 0, + rasterFinishWallTime: 0, + ), + ]); + expect(summary.missedFrameBuildBudget, 1); + }); + + test('when single element within budget', () { + final FrameTimingSummarizer summary = FrameTimingSummarizer([ + FrameTiming( + buildStart: 0, + buildFinish: 0, + vsyncStart: 0, + rasterStart: 0, + rasterFinish: 0, + rasterFinishWallTime: 0, + ), + ]); + expect(summary.missedFrameBuildBudget, 0); + }); + + test('when single element exactly within budget', () { + final FrameTimingSummarizer summary = FrameTimingSummarizer([ + FrameTiming( + buildStart: 0, + buildFinish: kBuildBudget.inMicroseconds, + vsyncStart: 0, + rasterStart: 0, + rasterFinish: 0, + rasterFinishWallTime: 0, + ), + ]); + expect(summary.missedFrameBuildBudget, 0); + }); + + test('when many missed budget', () { + final FrameTimingSummarizer summary = FrameTimingSummarizer([ + FrameTiming( + buildStart: 0, + buildFinish: 0, + vsyncStart: 0, + rasterStart: 0, + rasterFinish: 0, + rasterFinishWallTime: 0, + ), + FrameTiming( + buildStart: 0, + buildFinish: kBuildBudget.inMicroseconds, + vsyncStart: 0, + rasterStart: 0, + rasterFinish: 0, + rasterFinishWallTime: 0, + ), + FrameTiming( + buildStart: 0, + buildFinish: (kBuildBudget + const Duration(microseconds: 1)).inMicroseconds, + vsyncStart: 0, + rasterStart: 0, + rasterFinish: 0, + rasterFinishWallTime: 0, + ), + FrameTiming( + buildStart: 0, + buildFinish: (kBuildBudget + const Duration(microseconds: 2)).inMicroseconds, + vsyncStart: 0, + rasterStart: 0, + rasterFinish: 0, + rasterFinishWallTime: 0, + ), + ]); + expect(summary.missedFrameBuildBudget, 2); + }); + }); }); }