diff --git a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_test.dart b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_test.dart index 3b4bfc72f0e..99b5a0b4469 100644 --- a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_test.dart +++ b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_test.dart @@ -37,11 +37,11 @@ Future saveDurationsHistogram(List> events, String ou } else if (startEvent != null && eventName == 'Frame') { final String phase = event['ph'] as String; final int timestamp = event['ts'] as int; - if (phase == 'B') { + if (phase == 'B' || phase == 'b') { assert(frameStart == null); frameStart = timestamp; } else { - assert(phase == 'E'); + assert(phase == 'E' || phase == 'e'); final String routeName = (startEvent['args'] as Map)['to'] as String; durations[routeName] ??= []; durations[routeName]!.add(timestamp - frameStart!); diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart index 4b59184ca8f..29629763fd0 100644 --- a/packages/flutter/lib/src/foundation/binding.dart +++ b/packages/flutter/lib/src/foundation/binding.dart @@ -298,7 +298,7 @@ abstract class BindingBase { /// The [Future] returned by the `callback` argument is returned by [lockEvents]. @protected Future lockEvents(Future Function() callback) { - developer.Timeline.startSync('Lock events'); + final developer.TimelineTask timelineTask = developer.TimelineTask()..start('Lock events'); assert(callback != null); _lockCount += 1; @@ -307,7 +307,7 @@ abstract class BindingBase { future.whenComplete(() { _lockCount -= 1; if (!locked) { - developer.Timeline.finishSync(); + timelineTask.finish(); unlocked(); } }); diff --git a/packages/flutter/lib/src/scheduler/binding.dart b/packages/flutter/lib/src/scheduler/binding.dart index 0c104f6417c..fc81dfb5ea0 100644 --- a/packages/flutter/lib/src/scheduler/binding.dart +++ b/packages/flutter/lib/src/scheduler/binding.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:developer' show Flow, Timeline; +import 'dart:developer' show Flow, Timeline, TimelineTask; import 'dart:ui' show AppLifecycleState, FramePhase, FrameTiming, TimingsCallback, PlatformDispatcher; import 'package:collection/collection.dart' show PriorityQueue, HeapPriorityQueue; @@ -851,7 +851,7 @@ mixin SchedulerBinding on BindingBase { return; _warmUpFrame = true; - Timeline.startSync('Warm-up frame'); + final TimelineTask timelineTask = TimelineTask()..start('Warm-up frame'); final bool hadScheduledFrame = _hasScheduledFrame; // We use timers here to ensure that microtasks flush in between. Timer.run(() { @@ -879,7 +879,7 @@ mixin SchedulerBinding on BindingBase { // scheduled frame has finished. lockEvents(() async { await endOfFrame; - Timeline.finishSync(); + timelineTask.finish(); }); } @@ -996,6 +996,9 @@ mixin SchedulerBinding on BindingBase { handleDrawFrame(); } + TimelineTask? _frameTimelineTask; + TimelineTask? _animateTimelineTask; + /// Called by the engine to prepare the framework to produce a new frame. /// /// This function calls all the transient frame callbacks registered by @@ -1020,7 +1023,7 @@ mixin SchedulerBinding on BindingBase { /// statements printed during a frame from those printed between frames (e.g. /// in response to events or timers). void handleBeginFrame(Duration? rawTimeStamp) { - Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent); + _frameTimelineTask = TimelineTask()..start('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent); _firstRawTimeStampInEpoch ??= rawTimeStamp; _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp); if (rawTimeStamp != null) @@ -1047,7 +1050,7 @@ mixin SchedulerBinding on BindingBase { _hasScheduledFrame = false; try { // TRANSIENT FRAME CALLBACKS - Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent); + _animateTimelineTask = TimelineTask()..start('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent); _schedulerPhase = SchedulerPhase.transientCallbacks; final Map callbacks = _transientCallbacks; _transientCallbacks = {}; @@ -1072,7 +1075,8 @@ mixin SchedulerBinding on BindingBase { /// useful when working with frame callbacks. void handleDrawFrame() { assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks); - Timeline.finishSync(); // end the "Animate" phase + _animateTimelineTask?.finish(); // end the "Animate" phase + _animateTimelineTask = null; try { // PERSISTENT FRAME CALLBACKS _schedulerPhase = SchedulerPhase.persistentCallbacks; @@ -1088,7 +1092,8 @@ mixin SchedulerBinding on BindingBase { _invokeFrameCallback(callback, _currentFrameTimeStamp!); } finally { _schedulerPhase = SchedulerPhase.idle; - Timeline.finishSync(); // end the Frame + _frameTimelineTask?.finish(); // end the Frame + _frameTimelineTask = null; assert(() { if (debugPrintEndFrameBanner) debugPrint('▀' * _debugBanner!.length); diff --git a/packages/flutter_driver/lib/src/driver/timeline_summary.dart b/packages/flutter_driver/lib/src/driver/timeline_summary.dart index 18da652f53c..d766c7196de 100644 --- a/packages/flutter_driver/lib/src/driver/timeline_summary.dart +++ b/packages/flutter_driver/lib/src/driver/timeline_summary.dart @@ -351,7 +351,7 @@ class TimelineSummary { // Timeline does not guarantee that the first event is the "begin" event. TimelineEvent? begin; for (final TimelineEvent event in events) { - if (event.phase == 'B') { + if (event.phase == 'B' || event.phase == 'b') { begin = event; } else { if (begin != null) { diff --git a/packages/flutter_tools/test/integration.shard/timeline_test.dart b/packages/flutter_tools/test/integration.shard/timeline_test.dart index b8ac406e260..d06952b55d7 100644 --- a/packages/flutter_tools/test/integration.shard/timeline_test.dart +++ b/packages/flutter_tools/test/integration.shard/timeline_test.dart @@ -79,5 +79,26 @@ void main() { expect(response.json['value'], 'Brightness.light'); } timer.cancel(); + + // Verify that all duration events on the timeline are properly nested. + final Response response = await vmService.callServiceExtension('getVMTimeline'); + final List events = (response as Timeline).traceEvents; + final Map> threadDurationEventStack = >{}; + for (final TimelineEvent e in events) { + final Map event = e.json; + final String phase = event['ph'] as String; + final int tid = event['tid'] as int; + final String name = event['name'] as String; + final List stack = threadDurationEventStack.putIfAbsent(tid, () => []); + if (phase == 'B') { + stack.add(name); + } else if (phase == 'E') { + // The downloaded part of the timeline may contain an end event whose + // corresponding begin event happened before the start of the timeline. + if (stack.isNotEmpty) { + expect(stack.removeLast(), name); + } + } + } }); }