diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 23ed4e9b5cc..23091d2bf93 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -1611,7 +1611,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { /// the approximate bounding box of the clip rect that would be /// applied to the given child during the paint phase, if any. /// - /// Returns null if the child would not be clipped. + /// Returns `null` if the child would not be clipped. /// /// This is used in the semantics phase to avoid including children /// that are not physically visible. diff --git a/packages/flutter/lib/src/services/shell.dart b/packages/flutter/lib/src/services/shell.dart index 45004b33f2d..a8c633fd925 100644 --- a/packages/flutter/lib/src/services/shell.dart +++ b/packages/flutter/lib/src/services/shell.dart @@ -54,7 +54,7 @@ class MojoShell { /// Attempts to connect to an application via the Mojo shell. /// - /// Returns null if [canConnectToOtherApplications] is false. + /// Returns `null` if [canConnectToOtherApplications] is false. ApplicationConnection connectToApplication(String url) { if (_shell == null) return null; diff --git a/packages/flutter/lib/src/widgets/page_storage.dart b/packages/flutter/lib/src/widgets/page_storage.dart index 57dc887d867..597edda27f7 100644 --- a/packages/flutter/lib/src/widgets/page_storage.dart +++ b/packages/flutter/lib/src/widgets/page_storage.dart @@ -104,7 +104,7 @@ class PageStorage extends StatelessWidget { /// The bucket from the closest instance of this class that encloses the given context. /// - /// Returns null if none exists. + /// Returns `null` if none exists. static PageStorageBucket of(BuildContext context) { PageStorage widget = context.ancestorWidgetOfExactType(PageStorage); return widget?.bucket; diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 052c90ca937..444b5772ea5 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -468,7 +468,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute of(BuildContext context) { _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus); return widget?.route; diff --git a/packages/flutter/lib/src/widgets/scroll_behavior.dart b/packages/flutter/lib/src/widgets/scroll_behavior.dart index da960727624..740ee7dac94 100644 --- a/packages/flutter/lib/src/widgets/scroll_behavior.dart +++ b/packages/flutter/lib/src/widgets/scroll_behavior.dart @@ -18,7 +18,7 @@ abstract class ScrollBehavior { /// /// This function is called when a drag gesture ends. /// - /// Returns null if the behavior is to do nothing. + /// Returns `null` if the behavior is to do nothing. Simulation createScrollSimulation(T position, U velocity) => null; /// Returns an animation that ends at the snap offset. @@ -26,7 +26,7 @@ abstract class ScrollBehavior { /// This function is called when a drag gesture ends and a /// [SnapOffsetCallback] is specified for the scrollable. /// - /// Returns null if the behavior is to do nothing. + /// Returns `null` if the behavior is to do nothing. Simulation createSnapScrollSimulation(T startOffset, T endOffset, U startVelocity, U endVelocity) => null; /// Returns the scroll offset to use when the user attempts to scroll diff --git a/packages/flutter_driver/lib/src/timeline_summary.dart b/packages/flutter_driver/lib/src/timeline_summary.dart index fcb07008a6b..476f4b95bed 100644 --- a/packages/flutter_driver/lib/src/timeline_summary.dart +++ b/packages/flutter_driver/lib/src/timeline_summary.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert' show JSON, JsonEncoder; +import 'dart:math' as math; import 'package:file/file.dart'; import 'package:path/path.dart' as path; @@ -26,6 +27,8 @@ 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. double computeAverageFrameBuildTimeMillis() { int totalBuildTimeMicros = 0; int frameCount = 0; @@ -40,6 +43,24 @@ class TimelineSummary { : null; } + /// Find amount of time spent in the framework building widgets, + /// updating layout, painting and compositing on worst frame. + /// + /// Returns `null` if no frames were recorded. + double computeWorstFrameBuildTimeMillis() { + int maxBuildTimeMicros = 0; + int frameCount = 0; + + for (TimedEvent event in _extractBeginFrameEvents()) { + frameCount++; + maxBuildTimeMicros = math.max(maxBuildTimeMicros, event.duration.inMicroseconds); + } + + return frameCount > 0 + ? maxBuildTimeMicros / 1000 + : null; + } + /// The total number of frames recorded in the timeline. int countFrames() => _extractBeginFrameEvents().length; @@ -55,6 +76,7 @@ class TimelineSummary { Map get summaryJson { return { 'average_frame_build_time_millis': computeAverageFrameBuildTimeMillis(), + 'worst_frame_build_time_millis': computeWorstFrameBuildTimeMillis(), 'missed_frame_build_budget_count': computeMissedFrameBuildBudgetCount(), 'frame_count': countFrames(), 'frame_build_times': _extractBeginFrameEvents() diff --git a/packages/flutter_driver/test/src/timeline_summary_test.dart b/packages/flutter_driver/test/src/timeline_summary_test.dart index 6894c234860..a39b1f28937 100644 --- a/packages/flutter_driver/test/src/timeline_summary_test.dart +++ b/packages/flutter_driver/test/src/timeline_summary_test.dart @@ -58,7 +58,7 @@ void main() { end(1000), begin(2000), end(4000), ]).computeAverageFrameBuildTimeMillis(), - 2 + 2.0 ); }); @@ -68,7 +68,50 @@ void main() { begin(2000), end(4000), begin(5000), ]).computeAverageFrameBuildTimeMillis(), - 2 + 2.0 + ); + }); + }); + + group('worst_frame_build_time_millis', () { + test('returns null when there is no data', () { + expect(summarize([]).computeWorstFrameBuildTimeMillis(), isNull); + }); + + test('computes worst frame build time in milliseconds', () { + expect( + summarize([ + begin(1000), end(2000), + begin(3000), end(5000), + ]).computeWorstFrameBuildTimeMillis(), + 2.0 + ); + expect( + summarize([ + begin(3000), end(5000), + begin(1000), end(2000), + ]).computeWorstFrameBuildTimeMillis(), + 2.0 + ); + }); + + test('skips leading "end" events', () { + expect( + summarize([ + end(1000), + begin(2000), end(4000), + ]).computeWorstFrameBuildTimeMillis(), + 2.0 + ); + }); + + test('skips trailing "begin" events', () { + expect( + summarize([ + begin(2000), end(4000), + begin(5000), + ]).computeWorstFrameBuildTimeMillis(), + 2.0 ); }); }); @@ -96,6 +139,7 @@ void main() { ]).summaryJson, { 'average_frame_build_time_millis': 7.0, + 'worst_frame_build_time_millis': 11.0, 'missed_frame_build_budget_count': 2, 'frame_count': 3, 'frame_build_times': [9000, 1000, 11000], @@ -131,6 +175,7 @@ void main() { await fs.file('/temp/test.timeline_summary.json').readAsString(); expect(JSON.decode(written), { 'average_frame_build_time_millis': 7.0, + 'worst_frame_build_time_millis': 11.0, 'missed_frame_build_budget_count': 2, 'frame_count': 3, 'frame_build_times': [9000, 1000, 11000],