From 80e2efeb9497b6f93779c4d7bd546d51838678ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Thu, 13 Jan 2022 18:17:56 +0100 Subject: [PATCH 1/4] Wip --- dart/lib/sentry.dart | 1 + dart/lib/src/protocol/sentry_transaction.dart | 18 +++ dart/lib/src/sentry_measurement.dart | 16 +++ dart/lib/src/sentry_tracer.dart | 4 +- flutter/example/lib/main.dart | 2 + flutter/lib/src/frame_tracker.dart | 105 ++++++++++++++++++ flutter/lib/src/navigation/frame_tracker.dart | 75 +++++++++++++ .../navigation/sentry_navigator_observer.dart | 16 ++- flutter/pubspec.yaml | 2 +- 9 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 dart/lib/src/sentry_measurement.dart create mode 100644 flutter/lib/src/frame_tracker.dart create mode 100644 flutter/lib/src/navigation/frame_tracker.dart diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index 40fc29d8e8..6a5435d009 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -27,3 +27,4 @@ export 'src/sentry_attachment/sentry_attachment.dart'; export 'src/sentry_user_feedback.dart'; // tracing export 'src/tracing.dart'; +export 'src/sentry_measurement.dart'; diff --git a/dart/lib/src/protocol/sentry_transaction.dart b/dart/lib/src/protocol/sentry_transaction.dart index 2bbdbfc7bb..865248890d 100644 --- a/dart/lib/src/protocol/sentry_transaction.dart +++ b/dart/lib/src/protocol/sentry_transaction.dart @@ -3,6 +3,7 @@ import 'package:meta/meta.dart'; import '../protocol.dart'; import '../sentry_tracer.dart'; import '../utils.dart'; +import '../sentry_measurement.dart'; @immutable class SentryTransaction extends SentryEvent { @@ -30,6 +31,7 @@ class SentryTransaction extends SentryEvent { SdkVersion? sdk, SentryRequest? request, String? type, + this.measurements, }) : super( eventId: eventId, timestamp: timestamp ?? _tracer.endTimestamp, @@ -70,6 +72,15 @@ class SentryTransaction extends SentryEvent { json['start_timestamp'] = formatDateAsIso8601WithMillisPrecision(startTimestamp); + final ms = measurements; + if (ms != null && ms.isNotEmpty) { + final map = {}; + for (final m in ms) { + map[m.name] = m.toJson(); + } + json['measurements'] = map; + } + return json; } @@ -77,6 +88,8 @@ class SentryTransaction extends SentryEvent { bool get sampled => contexts.trace?.sampled == true; + final List? measurements; + @override SentryTransaction copyWith({ SentryId? eventId, @@ -105,6 +118,7 @@ class SentryTransaction extends SentryEvent { List? exceptions, List? threads, String? type, + List? measurements, }) => SentryTransaction( _tracer, @@ -126,5 +140,9 @@ class SentryTransaction extends SentryEvent { sdk: sdk ?? this.sdk, request: request ?? this.request, type: type ?? this.type, + measurements: (measurements != null + ? List.from(measurements) + : null) ?? + this.measurements, ); } diff --git a/dart/lib/src/sentry_measurement.dart b/dart/lib/src/sentry_measurement.dart new file mode 100644 index 0000000000..c5d302550f --- /dev/null +++ b/dart/lib/src/sentry_measurement.dart @@ -0,0 +1,16 @@ +class SentryMeasurement { + SentryMeasurement(this.name, this.value); + + SentryMeasurement.totalFrames(this.value) : name = 'frames_total'; + SentryMeasurement.slowFrames(this.value) : name = 'frames_slow'; + SentryMeasurement.frozenFrames(this.value) : name = 'frames_frozen'; + + final String name; + final double value; + + Map toJson() { + return { + 'value': value, + }; + } +} diff --git a/dart/lib/src/sentry_tracer.dart b/dart/lib/src/sentry_tracer.dart index d6a37eda07..67b59030b8 100644 --- a/dart/lib/src/sentry_tracer.dart +++ b/dart/lib/src/sentry_tracer.dart @@ -57,7 +57,7 @@ class SentryTracer extends ISentrySpan { } }); - final transaction = SentryTransaction(this); + final transaction = SentryTransaction(this, measurements: measurements); await _hub.captureTransaction(transaction); } } @@ -98,6 +98,8 @@ class SentryTracer extends ISentrySpan { _rootSpan.setTag(key, value); } + final List measurements = []; + @override ISentrySpan startChild( String operation, { diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index f6e0a9afc3..7befe43534 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -37,6 +37,8 @@ class _MyAppState extends State { @override void initState() { super.initState(); + //var frameTracker = FrameTracker(); + //frameTracker.start(); } @override diff --git a/flutter/lib/src/frame_tracker.dart b/flutter/lib/src/frame_tracker.dart new file mode 100644 index 0000000000..cb7e40d92a --- /dev/null +++ b/flutter/lib/src/frame_tracker.dart @@ -0,0 +1,105 @@ +import 'dart:ui'; + +import 'package:flutter/widgets.dart'; + +/// Records the time which a frame takes to draw, if it's above +/// a certain threshold, i.e. [FrameTracker.slowFrameThreshold]. +/// +/// This should not be added in debug mode because the performance of the debug +/// mode is not indicativ of the performance in release mode. +/// +/// Updates are occuring approximately once a second in release mode and +/// approximately once every 100ms in debug and profile builds. +/// +/// Remarks: +/// See [SchedulerBinding.addTimingsCallback](https://api.flutter.dev/flutter/scheduler/SchedulerBinding/addTimingsCallback.html) +/// to learn more about the performance impact of using this. +/// +/// Adding a `timingsCallback` has a real significant performance impact as +/// noted above. Thus this integration should only be added if it's enabled. +/// The enabled check should not happen inside the `timingsCallback`. +class FrameTracker { + FrameTracker({ + this.slowFrameThreshold = const Duration(milliseconds: 16), + this.frozenFrameThreshold = const Duration(milliseconds: 500), + }) { + binding.addTimingsCallback(_timingsCallback); + } + + // Checks wether [FrameTracker] is supported on this Flutter version + static bool isSupported() { + try { + (WidgetsBinding.instance!.window as dynamic).frameData.frameNumber as int; + } on NoSuchMethodError catch (_) { + return false; + } + return true; + } + + final WidgetsBinding binding = WidgetsBinding.instance!; + SingletonFlutterWindow get window => binding.window; + + final Duration slowFrameThreshold; + final Duration frozenFrameThreshold; + + final List _frameTimings = []; + + var startFrameNumber = -1; + var endFrameNumber = -1; + + void start() { + startFrameNumber = window.currentFrameNumber; + } + + Map finish() { + if (startFrameNumber == -1) { + return {}; + } + endFrameNumber = window.currentFrameNumber; + _reset(); + return _listToMetrics(endFrameNumber); + } + + void _reset() { + startFrameNumber = -1; + endFrameNumber = -1; + } + + Map _listToMetrics(int endFrameNumber) { + final frameInTimeSpan = _frameTimings.where((element) { + final frame = element.currentFrameNumber; + return frame > startFrameNumber && frame < endFrameNumber; + }); + return { + 'measurements': { + 'frames_frozen': { + 'value': frameInTimeSpan + .where((frame) => frame.totalSpan > frozenFrameThreshold) + .length, + }, + 'frames_slow': { + 'value': frameInTimeSpan + .where((frame) => frame.totalSpan > slowFrameThreshold) + .length, + }, + 'frames_total': { + 'value': frameInTimeSpan.length, + } + } + }; + } + + /// The first frame is sent without batching as per + /// [WidgetsBinding.addTimingsCallback] docs. + void _timingsCallback(List timings) { + _frameTimings.addAll(timings); + } +} + +extension _SingletonFlutterWindowExtension on SingletonFlutterWindow { + int get currentFrameNumber => (this as dynamic).frameData.frameNumber as int; +} + +extension _FrameTimingExtension on FrameTiming { + int get currentFrameNumber => (window as dynamic).frameNumber as int; +} diff --git a/flutter/lib/src/navigation/frame_tracker.dart b/flutter/lib/src/navigation/frame_tracker.dart new file mode 100644 index 0000000000..d2397ba0a4 --- /dev/null +++ b/flutter/lib/src/navigation/frame_tracker.dart @@ -0,0 +1,75 @@ +import 'dart:ui'; + +import 'package:flutter/widgets.dart'; +import 'package:sentry/sentry.dart'; + +class FrameTracker { + FrameTracker({ + this.slowFrameThreshold = const Duration(milliseconds: 16), + this.frozenFrameThreshold = const Duration(milliseconds: 500), + required WidgetsBinding binding, + }) : _binding = binding; + + static const dataKey = 'measurements'; + final WidgetsBinding _binding; + bool? _isSupported; + + // Checks wether [FrameTracker] is supported on this Flutter version + bool get isSupported { + try { + (_binding.window as dynamic).frameData.frameNumber as int; + } on NoSuchMethodError catch (_) { + return _isSupported ??= false; + } + return _isSupported ??= true; + } + + SingletonFlutterWindow get window => _binding.window; + + final Duration slowFrameThreshold; + final Duration frozenFrameThreshold; + + var startFrameNumber = -1; + var endFrameNumber = -1; + + void start() { + if (!isSupported) { + return; + } + startFrameNumber = window.currentFrameNumber; + } + + List? finish() { + if (!isSupported || startFrameNumber == -1) { + // Either Flutter doesn't support frame numbers yet + // or frame tracking hasn't started yet. + return null; + } + endFrameNumber = window.currentFrameNumber; + + final metrics = _listToMetrics( + startFrameNumber, + endFrameNumber, + ); + _reset(); + return metrics; + } + + void _reset() { + startFrameNumber = -1; + endFrameNumber = -1; + } + + static List _listToMetrics( + int startFrameNumber, + int endFrameNumber, + ) { + final frameCount = endFrameNumber - startFrameNumber; + + return [SentryMeasurement.totalFrames(frameCount.toDouble())]; + } +} + +extension _SingletonFlutterWindowExtension on SingletonFlutterWindow { + int get currentFrameNumber => (this as dynamic).frameData.frameNumber as int; +} diff --git a/flutter/lib/src/navigation/sentry_navigator_observer.dart b/flutter/lib/src/navigation/sentry_navigator_observer.dart index 1d64b6e356..d4c4158e56 100644 --- a/flutter/lib/src/navigation/sentry_navigator_observer.dart +++ b/flutter/lib/src/navigation/sentry_navigator_observer.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; -import 'package:sentry/sentry.dart'; +// ignore: implementation_imports +import 'package:sentry/src/sentry_tracer.dart'; import '../../sentry_flutter.dart'; +import 'frame_tracker.dart' as ft; /// This key must be used so that the web interface displays the events nicely /// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ @@ -71,6 +73,8 @@ class SentryNavigatorObserver extends RouteObserver> { final bool _setRouteNameAsTransaction; final RouteNameExtractor? _routeNameExtractor; final AdditionalInfoExtractor? _additionalInfoProvider; + final ft.FrameTracker _frameTracker = + ft.FrameTracker(binding: WidgetsBinding.instance!); ISentrySpan? _transaction; @@ -154,6 +158,7 @@ class SentryNavigatorObserver extends RouteObserver> { waitForChildren: true, autoFinishAfter: Duration(seconds: 3), ); + _frameTracker.start(); if (arguments != null) { _transaction?.setData('route_settings_arguments', arguments); } @@ -165,6 +170,15 @@ class SentryNavigatorObserver extends RouteObserver> { Future _finishTransaction() async { _transaction?.status ??= SpanStatus.ok(); + // ignore: invalid_use_of_internal_member + if (_transaction is SentryTracer) { + // ignore: invalid_use_of_internal_member + final transaction = _transaction as SentryTracer; + final measurements = _frameTracker.finish(); + if (measurements != null) { + transaction.measurements.addAll(measurements); + } + } return await _transaction?.finish(); } } diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index b43c3f96ba..29d17c2f71 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: package_info_plus: ^1.0.0 dev_dependencies: - build_runner: ^2.1.5 + build_runner: ^2.1.0 flutter_test: sdk: flutter mockito: ^5.0.16 From 803ab039e4ffbaca5266919e427186daa82467e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Sat, 15 Jan 2022 16:09:54 +0100 Subject: [PATCH 2/4] frame tracking cleanup --- dart/lib/src/sentry_measurement.dart | 37 +++++++++++++++++++ flutter/lib/src/navigation/frame_tracker.dart | 7 ++-- .../navigation/sentry_navigator_observer.dart | 6 +-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/dart/lib/src/sentry_measurement.dart b/dart/lib/src/sentry_measurement.dart index c5d302550f..dc6dba66e1 100644 --- a/dart/lib/src/sentry_measurement.dart +++ b/dart/lib/src/sentry_measurement.dart @@ -1,10 +1,47 @@ class SentryMeasurement { SentryMeasurement(this.name, this.value); + // Mobile / Desktop Vitals + + /// Amount of frames drawn during a transaction SentryMeasurement.totalFrames(this.value) : name = 'frames_total'; + + /// Amount of slow frames drawn during a transaction. + /// A slow frame is any frame longer than 1s / refreshrate. + /// So for example any frame slower than 16ms for a refresh rate of 60hz. SentryMeasurement.slowFrames(this.value) : name = 'frames_slow'; + + /// Amount of frozen frames drawn during a transaction. + /// Typically defined as frames slower than 500ms. SentryMeasurement.frozenFrames(this.value) : name = 'frames_frozen'; + SentryMeasurement.coldAppStart(Duration duration) + : assert(!duration.isNegative), + name = 'app_start_cold', + value = duration.inMilliseconds; + + SentryMeasurement.warmAppStart(Duration duration) + : assert(!duration.isNegative), + name = 'app_start_warm', + value = duration.inMilliseconds; + + // Web Vitals + + SentryMeasurement.firstContentfulPaint(Duration duration) + : assert(!duration.isNegative), + name = 'fcp', + value = duration.inMilliseconds; + + SentryMeasurement.firstPaint(Duration duration) + : assert(!duration.isNegative), + name = 'fp', + value = duration.inMilliseconds; + + SentryMeasurement.timeToFirstByte(Duration duration) + : assert(!duration.isNegative), + name = 'ttfb', + value = duration.inMilliseconds; + final String name; final double value; diff --git a/flutter/lib/src/navigation/frame_tracker.dart b/flutter/lib/src/navigation/frame_tracker.dart index d2397ba0a4..0eb5081a2c 100644 --- a/flutter/lib/src/navigation/frame_tracker.dart +++ b/flutter/lib/src/navigation/frame_tracker.dart @@ -10,12 +10,12 @@ class FrameTracker { required WidgetsBinding binding, }) : _binding = binding; - static const dataKey = 'measurements'; final WidgetsBinding _binding; bool? _isSupported; // Checks wether [FrameTracker] is supported on this Flutter version bool get isSupported { + // TODO: Check if this works on web try { (_binding.window as dynamic).frameData.frameNumber as int; } on NoSuchMethodError catch (_) { @@ -41,8 +41,8 @@ class FrameTracker { List? finish() { if (!isSupported || startFrameNumber == -1) { - // Either Flutter doesn't support frame numbers yet - // or frame tracking hasn't started yet. + // Either the current Flutter version doesn't support frame numbers yet + // or frame tracking hasn't started. return null; } endFrameNumber = window.currentFrameNumber; @@ -71,5 +71,6 @@ class FrameTracker { } extension _SingletonFlutterWindowExtension on SingletonFlutterWindow { + // Code to make this compatible with Flutter < 2.8 int get currentFrameNumber => (this as dynamic).frameData.frameNumber as int; } diff --git a/flutter/lib/src/navigation/sentry_navigator_observer.dart b/flutter/lib/src/navigation/sentry_navigator_observer.dart index d4c4158e56..3cc2ad0a5d 100644 --- a/flutter/lib/src/navigation/sentry_navigator_observer.dart +++ b/flutter/lib/src/navigation/sentry_navigator_observer.dart @@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart'; // ignore: implementation_imports import 'package:sentry/src/sentry_tracer.dart'; import '../../sentry_flutter.dart'; -import 'frame_tracker.dart' as ft; +import 'frame_tracker.dart'; /// This key must be used so that the web interface displays the events nicely /// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ @@ -73,8 +73,8 @@ class SentryNavigatorObserver extends RouteObserver> { final bool _setRouteNameAsTransaction; final RouteNameExtractor? _routeNameExtractor; final AdditionalInfoExtractor? _additionalInfoProvider; - final ft.FrameTracker _frameTracker = - ft.FrameTracker(binding: WidgetsBinding.instance!); + final FrameTracker _frameTracker = + FrameTracker(binding: WidgetsBinding.instance!); ISentrySpan? _transaction; From 667d63f1be39676162b9cc160ba4e9f1f3ba3f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Sat, 15 Jan 2022 16:14:12 +0100 Subject: [PATCH 3/4] fix types --- dart/lib/src/sentry_measurement.dart | 4 +- flutter/lib/src/frame_tracker.dart | 105 --------------------------- 2 files changed, 2 insertions(+), 107 deletions(-) delete mode 100644 flutter/lib/src/frame_tracker.dart diff --git a/dart/lib/src/sentry_measurement.dart b/dart/lib/src/sentry_measurement.dart index dc6dba66e1..69378fb416 100644 --- a/dart/lib/src/sentry_measurement.dart +++ b/dart/lib/src/sentry_measurement.dart @@ -43,10 +43,10 @@ class SentryMeasurement { value = duration.inMilliseconds; final String name; - final double value; + final num value; Map toJson() { - return { + return { 'value': value, }; } diff --git a/flutter/lib/src/frame_tracker.dart b/flutter/lib/src/frame_tracker.dart deleted file mode 100644 index cb7e40d92a..0000000000 --- a/flutter/lib/src/frame_tracker.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/widgets.dart'; - -/// Records the time which a frame takes to draw, if it's above -/// a certain threshold, i.e. [FrameTracker.slowFrameThreshold]. -/// -/// This should not be added in debug mode because the performance of the debug -/// mode is not indicativ of the performance in release mode. -/// -/// Updates are occuring approximately once a second in release mode and -/// approximately once every 100ms in debug and profile builds. -/// -/// Remarks: -/// See [SchedulerBinding.addTimingsCallback](https://api.flutter.dev/flutter/scheduler/SchedulerBinding/addTimingsCallback.html) -/// to learn more about the performance impact of using this. -/// -/// Adding a `timingsCallback` has a real significant performance impact as -/// noted above. Thus this integration should only be added if it's enabled. -/// The enabled check should not happen inside the `timingsCallback`. -class FrameTracker { - FrameTracker({ - this.slowFrameThreshold = const Duration(milliseconds: 16), - this.frozenFrameThreshold = const Duration(milliseconds: 500), - }) { - binding.addTimingsCallback(_timingsCallback); - } - - // Checks wether [FrameTracker] is supported on this Flutter version - static bool isSupported() { - try { - (WidgetsBinding.instance!.window as dynamic).frameData.frameNumber as int; - } on NoSuchMethodError catch (_) { - return false; - } - return true; - } - - final WidgetsBinding binding = WidgetsBinding.instance!; - SingletonFlutterWindow get window => binding.window; - - final Duration slowFrameThreshold; - final Duration frozenFrameThreshold; - - final List _frameTimings = []; - - var startFrameNumber = -1; - var endFrameNumber = -1; - - void start() { - startFrameNumber = window.currentFrameNumber; - } - - Map finish() { - if (startFrameNumber == -1) { - return {}; - } - endFrameNumber = window.currentFrameNumber; - _reset(); - return _listToMetrics(endFrameNumber); - } - - void _reset() { - startFrameNumber = -1; - endFrameNumber = -1; - } - - Map _listToMetrics(int endFrameNumber) { - final frameInTimeSpan = _frameTimings.where((element) { - final frame = element.currentFrameNumber; - return frame > startFrameNumber && frame < endFrameNumber; - }); - return { - 'measurements': { - 'frames_frozen': { - 'value': frameInTimeSpan - .where((frame) => frame.totalSpan > frozenFrameThreshold) - .length, - }, - 'frames_slow': { - 'value': frameInTimeSpan - .where((frame) => frame.totalSpan > slowFrameThreshold) - .length, - }, - 'frames_total': { - 'value': frameInTimeSpan.length, - } - } - }; - } - - /// The first frame is sent without batching as per - /// [WidgetsBinding.addTimingsCallback] docs. - void _timingsCallback(List timings) { - _frameTimings.addAll(timings); - } -} - -extension _SingletonFlutterWindowExtension on SingletonFlutterWindow { - int get currentFrameNumber => (this as dynamic).frameData.frameNumber as int; -} - -extension _FrameTimingExtension on FrameTiming { - int get currentFrameNumber => (window as dynamic).frameNumber as int; -} From d4352998c22556629a28c5fceb51677dc3c88a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Tue, 25 Jan 2022 17:17:48 +0100 Subject: [PATCH 4/4] track total, slow and frozen frames --- flutter/lib/src/navigation/frame_tracker.dart | 82 +++++++++---------- .../navigation/sentry_navigator_observer.dart | 4 +- 2 files changed, 39 insertions(+), 47 deletions(-) diff --git a/flutter/lib/src/navigation/frame_tracker.dart b/flutter/lib/src/navigation/frame_tracker.dart index 0eb5081a2c..e53c5e8dff 100644 --- a/flutter/lib/src/navigation/frame_tracker.dart +++ b/flutter/lib/src/navigation/frame_tracker.dart @@ -1,5 +1,4 @@ import 'dart:ui'; - import 'package:flutter/widgets.dart'; import 'package:sentry/sentry.dart'; @@ -11,66 +10,61 @@ class FrameTracker { }) : _binding = binding; final WidgetsBinding _binding; - bool? _isSupported; - - // Checks wether [FrameTracker] is supported on this Flutter version - bool get isSupported { - // TODO: Check if this works on web - try { - (_binding.window as dynamic).frameData.frameNumber as int; - } on NoSuchMethodError catch (_) { - return _isSupported ??= false; - } - return _isSupported ??= true; - } SingletonFlutterWindow get window => _binding.window; final Duration slowFrameThreshold; final Duration frozenFrameThreshold; - var startFrameNumber = -1; - var endFrameNumber = -1; + DateTime? _timeStamp; - void start() { - if (!isSupported) { + var _frameCount = 0; + var _slowCount = 0; + var _frozenCount = 0; + + void _frameCallback(Duration _) { + final timeStamp = _timeStamp; + if (timeStamp == null) { return; } - startFrameNumber = window.currentFrameNumber; - } - List? finish() { - if (!isSupported || startFrameNumber == -1) { - // Either the current Flutter version doesn't support frame numbers yet - // or frame tracking hasn't started. - return null; + // postFrameCallbacks are called just once, + // so we have to add it each frame. + _binding.addPostFrameCallback(_frameCallback); + + final now = DateTime.now(); + _timeStamp = now; + final duration = timeStamp.difference(now).abs(); + + if (duration > frozenFrameThreshold) { + _frozenCount++; + } else if (duration > slowFrameThreshold) { + _slowCount++; } - endFrameNumber = window.currentFrameNumber; - final metrics = _listToMetrics( - startFrameNumber, - endFrameNumber, - ); - _reset(); - return metrics; + _frameCount++; } - void _reset() { - startFrameNumber = -1; - endFrameNumber = -1; + void start() { + _timeStamp = DateTime.now(); + _binding.addPostFrameCallback(_frameCallback); } - static List _listToMetrics( - int startFrameNumber, - int endFrameNumber, - ) { - final frameCount = endFrameNumber - startFrameNumber; + List finish() { + final metrics = [ + SentryMeasurement.totalFrames(_frameCount), + SentryMeasurement.frozenFrames(_frozenCount), + SentryMeasurement.slowFrames(_slowCount), + ]; - return [SentryMeasurement.totalFrames(frameCount.toDouble())]; + _reset(); + return metrics; } -} -extension _SingletonFlutterWindowExtension on SingletonFlutterWindow { - // Code to make this compatible with Flutter < 2.8 - int get currentFrameNumber => (this as dynamic).frameData.frameNumber as int; + void _reset() { + _timeStamp = null; + _frameCount = 0; + _slowCount = 0; + _frozenCount = 0; + } } diff --git a/flutter/lib/src/navigation/sentry_navigator_observer.dart b/flutter/lib/src/navigation/sentry_navigator_observer.dart index 1bbfb18e8d..2b3a92b7f6 100644 --- a/flutter/lib/src/navigation/sentry_navigator_observer.dart +++ b/flutter/lib/src/navigation/sentry_navigator_observer.dart @@ -179,9 +179,7 @@ class SentryNavigatorObserver extends RouteObserver> { // ignore: invalid_use_of_internal_member final transaction = _transaction as SentryTracer; final measurements = _frameTracker.finish(); - if (measurements != null) { - transaction.measurements.addAll(measurements); - } + transaction.measurements.addAll(measurements); } return await _transaction?.finish(); }