From 5ad308b273aab61e326be68c7f20151c6667d77c Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 18 Apr 2024 13:12:52 +0200 Subject: [PATCH 01/26] commit --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 11 ++- .../io/sentry/samples/flutter/MainActivity.kt | 1 + .../Classes/SentryFlutterPluginApple.swift | 14 ++++ .../native_app_start_event_processor.dart | 62 ++++++++++++++++ .../native_app_start_integration.dart | 72 +++++++++++++++++-- flutter/lib/src/native/sentry_native.dart | 4 ++ .../lib/src/native/sentry_native_binding.dart | 2 + .../lib/src/native/sentry_native_channel.dart | 5 ++ flutter/lib/src/sentry_flutter.dart | 4 ++ 9 files changed, 170 insertions(+), 5 deletions(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index ea9ab9d17e..4cbb5ea5b1 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -40,8 +40,11 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private var activity: WeakReference? = null private var framesTracker: ActivityFramesTracker? = null + private var engineEndTime: Long? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + engineEndTime = System.currentTimeMillis() + context = flutterPluginBinding.applicationContext channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sentry_flutter") channel.setMethodCallHandler(this) @@ -70,6 +73,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { "removeExtra" -> removeExtra(call.argument("key"), result) "setTag" -> setTag(call.argument("key"), call.argument("value"), result) "removeTag" -> removeTag(call.argument("key"), result) + "fetchEngineEndtime" -> fetchEngineEndtime(result) else -> result.notImplemented() } } @@ -142,7 +146,8 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } val appStartTime = AppStartMetrics.getInstance().appStartTimeSpan.startTimestamp - val isColdStart = AppStartMetrics.getInstance().appStartType == AppStartMetrics.AppStartType.COLD + val isColdStart = + AppStartMetrics.getInstance().appStartType == AppStartMetrics.AppStartType.COLD if (appStartTime == null) { Log.w("Sentry", "App start won't be sent due to missing appStartTime") @@ -157,6 +162,10 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } } + private fun fetchEngineEndtime(result: Result) { + result.success(engineEndTime) + } + private fun beginNativeFrames(result: Result) { if (!sentryFlutter.autoPerformanceTracingEnabled) { result.success(null) diff --git a/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt b/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt index 7966a33655..c6696cfb19 100644 --- a/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt +++ b/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt @@ -11,6 +11,7 @@ class MainActivity : FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) + println("aaa configured engine") MethodChannel( flutterEngine.dartExecutor.binaryMessenger, _channel, diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index ec6eb7cdb7..c62791a3d8 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -26,7 +26,13 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { #endif } + private static var engineEndtime: Int64 = 0 + public static func register(with registrar: FlutterPluginRegistrar) { + let currentDate = Date() // Gets the current date and time + let timeInterval = currentDate.timeIntervalSince1970 // Time in seconds since epoch (1970-01-01) + engineEndtime = Int64(timeInterval * 1000) // Convert to milliseconds + #if os(iOS) let channel = FlutterMethodChannel(name: "sentry_flutter", binaryMessenger: registrar.messenger()) #elseif os(macOS) @@ -78,6 +84,11 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { return iso8601FormatterWithMillisecondPrecision.date(from: iso8601String) ?? iso8601Formatter.date(from: iso8601String) // Parse date with low precision formatter for backward compatible } + + + private func fetchEngineEndtime(result: @escaping FlutterResult) { + result(SentryFlutterPluginApple.engineEndtime) + } // swiftlint:disable:next cyclomatic_complexity public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -151,6 +162,9 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { let arguments = call.arguments as? [String: Any?] let key = arguments?["key"] as? String removeTag(key: key, result: result) + + case "fetchEngineEndtime": + fetchEngineEndtime(result: result) #if !os(tvOS) && !os(watchOS) case "discardProfiler": diff --git a/flutter/lib/src/event_processor/native_app_start_event_processor.dart b/flutter/lib/src/event_processor/native_app_start_event_processor.dart index fd1a5ec169..74bbde5ddd 100644 --- a/flutter/lib/src/event_processor/native_app_start_event_processor.dart +++ b/flutter/lib/src/event_processor/native_app_start_event_processor.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:sentry/sentry.dart'; +import '../../sentry_flutter.dart'; import '../integrations/integrations.dart'; import '../native/sentry_native.dart'; @@ -25,6 +26,67 @@ class NativeAppStartEventProcessor implements EventProcessor { event.measurements[measurement.name] = measurement; _native.didAddAppStartMeasurement = true; } + + final op = 'app.start.${appStartInfo?.type.name}'; + + print('NativeAppStartEventProcessor.apply: ${event.tracer}'); + + final tracer = event.tracer; + + // final spanParent = SentrySpan( + // tracer, + // SentrySpanContext( + // operation: op, + // description: 'Cold start', + // parentSpanId: tracer.context.spanId, + // traceId: tracer.context.traceId, + // ), + // Sentry.currentHub, + // startTimestamp: appStartInfo?.start); + // + // final span = SentrySpan( + // tracer, + // SentrySpanContext( + // operation: op, + // description: 'Engine init', + // parentSpanId: spanParent.context.spanId, + // traceId: tracer.context.traceId, + // ), + // Sentry.currentHub, + // startTimestamp: appStartInfo?.start); + // await span.finish(endTimestamp: appStartInfo?.engineEnd); + // + // final span2 = SentrySpan( + // tracer, + // SentrySpanContext( + // operation: op, + // description: 'Dart loading', + // parentSpanId: spanParent.context.spanId, + // traceId: tracer.context.traceId, + // ), + // Sentry.currentHub, + // startTimestamp: appStartInfo?.engineEnd); + // await span2.finish(endTimestamp: SentryFlutter.dartLoadingEnd); + // + // final span3 = SentrySpan( + // tracer, + // SentrySpanContext( + // operation: op, + // description: 'First frame loading', + // parentSpanId: spanParent.context.spanId, + // traceId: tracer.context.traceId, + // ), + // Sentry.currentHub, + // startTimestamp: SentryFlutter.dartLoadingEnd); + // await span3.finish(endTimestamp: appStartInfo?.end); + // + // await spanParent.finish(endTimestamp: appStartInfo?.end); + // + // tracer.children.add(spanParent); + // tracer.children.add(span); + // tracer.children.add(span2); + // tracer.children.add(span3); + return event; } } diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/flutter/lib/src/integrations/native_app_start_integration.dart index 0ba5c77c4f..766df63eed 100644 --- a/flutter/lib/src/integrations/native_app_start_integration.dart +++ b/flutter/lib/src/integrations/native_app_start_integration.dart @@ -53,7 +53,8 @@ class NativeAppStartIntegration extends Integration { if (isIntegrationTest) { final appStartInfo = AppStartInfo(AppStartType.cold, start: DateTime.now(), - end: DateTime.now().add(const Duration(milliseconds: 100))); + end: DateTime.now().add(const Duration(milliseconds: 100)), + engineEnd: DateTime.now().add(const Duration(milliseconds: 50))); setAppStartInfo(appStartInfo); return; } @@ -69,14 +70,19 @@ class NativeAppStartIntegration extends Integration { _native.appStartEnd ??= options.clock(); final appStartEnd = _native.appStartEnd; final nativeAppStart = await _native.fetchNativeAppStart(); + final engineEndtime = await _native.fetchEngineEndtime(); - if (nativeAppStart == null || appStartEnd == null) { + if (nativeAppStart == null || + appStartEnd == null || + engineEndtime == null) { return; } final appStartDateTime = DateTime.fromMillisecondsSinceEpoch( nativeAppStart.appStartTime.toInt()); final duration = appStartEnd.difference(appStartDateTime); + final engineEndDatetime = + DateTime.fromMillisecondsSinceEpoch(engineEndtime); // We filter out app start more than 60s. // This could be due to many different reasons. @@ -95,8 +101,63 @@ class NativeAppStartIntegration extends Integration { nativeAppStart.isColdStart ? AppStartType.cold : AppStartType.warm, start: DateTime.fromMillisecondsSinceEpoch( nativeAppStart.appStartTime.toInt()), - end: appStartEnd); + end: appStartEnd, + engineEnd: engineEndDatetime); setAppStartInfo(appStartInfo); + + final routeName = SentryNavigatorObserver.currentRouteName; + + // null means there is no navigator observer + if (routeName == null) { + final transaction = hub.startTransaction( + 'App Start', + 'ui.load', + startTimestamp: appStartInfo.start, + description: 'root /', + ); + + final op = 'app.start.${appStartInfo.type.name}'; + + final coldStartSpan = transaction.startChild( + op, + description: 'Cold start', + startTimestamp: appStartInfo.start, + ); + + final ttidSpan = transaction.startChild( + SentrySpanOperations.uiTimeToInitialDisplay, + description: 'Time to initial display', + startTimestamp: appStartInfo.start, + ); + + final engineInitSpan = coldStartSpan.startChild( + op, + description: 'Engine init and ready', + startTimestamp: appStartInfo.start, + ); + + final dartLoadingSpan = coldStartSpan.startChild( + op, + description: 'Dart isolate loading', + startTimestamp: appStartInfo.engineEnd, + ); + + final firstFrameRenderSpan = coldStartSpan.startChild( + op, + description: 'First frame render', + startTimestamp: SentryFlutter.dartLoadingEnd, + ); + + await engineInitSpan.finish(endTimestamp: appStartInfo.engineEnd); + await dartLoadingSpan.finish(endTimestamp: SentryFlutter.dartLoadingEnd); + await firstFrameRenderSpan.finish(endTimestamp: appStartInfo.end); + await coldStartSpan.finish(endTimestamp: appStartInfo.end); + await ttidSpan.finish(endTimestamp: appStartInfo.end); + await transaction.finish(endTimestamp: appStartInfo.end); + } + + print('route name: ${SentryNavigatorObserver.currentRouteName}'); + }); } @@ -109,11 +170,14 @@ class NativeAppStartIntegration extends Integration { enum AppStartType { cold, warm } class AppStartInfo { - AppStartInfo(this.type, {required this.start, required this.end}); + AppStartInfo(this.type, + {required this.start, required this.end, required this.engineEnd}); final AppStartType type; final DateTime start; final DateTime end; + final DateTime engineEnd; + Duration get duration => end.difference(start); SentryMeasurement toMeasurement() { diff --git a/flutter/lib/src/native/sentry_native.dart b/flutter/lib/src/native/sentry_native.dart index a7973f8a12..28ccef4068 100644 --- a/flutter/lib/src/native/sentry_native.dart +++ b/flutter/lib/src/native/sentry_native.dart @@ -36,6 +36,10 @@ class SentryNative { return _invoke("fetchNativeAppStart", _binding.fetchNativeAppStart); } + Future fetchEngineEndtime() async { + return _invoke("fetchEngineEndtime", _binding.fetchEngineEndtime); + } + // NativeFrames Future beginNativeFramesCollection() => diff --git a/flutter/lib/src/native/sentry_native_binding.dart b/flutter/lib/src/native/sentry_native_binding.dart index 54d335d529..ee6aae823f 100644 --- a/flutter/lib/src/native/sentry_native_binding.dart +++ b/flutter/lib/src/native/sentry_native_binding.dart @@ -12,6 +12,8 @@ abstract class SentryNativeBinding { Future fetchNativeAppStart(); + Future fetchEngineEndtime(); + Future beginNativeFrames(); Future endNativeFrames(SentryId id); diff --git a/flutter/lib/src/native/sentry_native_channel.dart b/flutter/lib/src/native/sentry_native_channel.dart index 4bf9745cb3..e8ac6f7afb 100644 --- a/flutter/lib/src/native/sentry_native_channel.dart +++ b/flutter/lib/src/native/sentry_native_channel.dart @@ -24,6 +24,11 @@ class SentryNativeChannel implements SentryNativeBinding { return (json != null) ? NativeAppStart.fromJson(json) : null; } + @override + Future fetchEngineEndtime() async { + return _channel.invokeMethod('fetchEngineEndtime'); + } + @override Future beginNativeFrames() => _channel.invokeMethod('beginNativeFrames'); diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 6c4e2c243b..009e12ddb8 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -34,6 +34,8 @@ typedef FlutterOptionsConfiguration = FutureOr Function( mixin SentryFlutter { static const _channel = MethodChannel('sentry_flutter'); + static DateTime? dartLoadingEnd; + static Future init( FlutterOptionsConfiguration optionsConfiguration, { AppRunner? appRunner, @@ -43,6 +45,8 @@ mixin SentryFlutter { }) async { final flutterOptions = SentryFlutterOptions(); + dartLoadingEnd = flutterOptions.clock(); + if (platformChecker != null) { flutterOptions.platformChecker = platformChecker; } From 3e0790ad9608004d9ccc9e8a89a2ac028514cc3d Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 23 Apr 2024 14:57:12 +0200 Subject: [PATCH 02/26] Update --- .../native_app_start_event_processor.dart | 152 +++++++++++------- .../native_app_start_integration.dart | 66 ++------ flutter/lib/src/sentry_flutter.dart | 1 + 3 files changed, 100 insertions(+), 119 deletions(-) diff --git a/flutter/lib/src/event_processor/native_app_start_event_processor.dart b/flutter/lib/src/event_processor/native_app_start_event_processor.dart index ce45cf0e30..8e7c39ab05 100644 --- a/flutter/lib/src/event_processor/native_app_start_event_processor.dart +++ b/flutter/lib/src/event_processor/native_app_start_event_processor.dart @@ -1,17 +1,22 @@ import 'dart:async'; -import 'package:sentry/sentry.dart'; - import '../../sentry_flutter.dart'; import '../integrations/integrations.dart'; import '../native/sentry_native.dart'; +// ignore: implementation_imports +import 'package:sentry/src/sentry_tracer.dart'; + /// EventProcessor that enriches [SentryTransaction] objects with app start /// measurement. class NativeAppStartEventProcessor implements EventProcessor { final SentryNative _native; + final Hub _hub; - NativeAppStartEventProcessor(this._native); + NativeAppStartEventProcessor( + this._native, { + Hub? hub, + }) : _hub = hub ?? HubAdapter(); @override Future apply(SentryEvent event, Hint hint) async { @@ -27,66 +32,89 @@ class NativeAppStartEventProcessor implements EventProcessor { _native.didAddAppStartMeasurement = true; } - final op = 'app.start.${appStartInfo?.type.name}'; - - print('NativeAppStartEventProcessor.apply: ${event.tracer}'); - - final tracer = event.tracer; - - // final spanParent = SentrySpan( - // tracer, - // SentrySpanContext( - // operation: op, - // description: 'Cold start', - // parentSpanId: tracer.context.spanId, - // traceId: tracer.context.traceId, - // ), - // Sentry.currentHub, - // startTimestamp: appStartInfo?.start); - // - // final span = SentrySpan( - // tracer, - // SentrySpanContext( - // operation: op, - // description: 'Engine init', - // parentSpanId: spanParent.context.spanId, - // traceId: tracer.context.traceId, - // ), - // Sentry.currentHub, - // startTimestamp: appStartInfo?.start); - // await span.finish(endTimestamp: appStartInfo?.engineEnd); - // - // final span2 = SentrySpan( - // tracer, - // SentrySpanContext( - // operation: op, - // description: 'Dart loading', - // parentSpanId: spanParent.context.spanId, - // traceId: tracer.context.traceId, - // ), - // Sentry.currentHub, - // startTimestamp: appStartInfo?.engineEnd); - // await span2.finish(endTimestamp: SentryFlutter.dartLoadingEnd); - // - // final span3 = SentrySpan( - // tracer, - // SentrySpanContext( - // operation: op, - // description: 'First frame loading', - // parentSpanId: spanParent.context.spanId, - // traceId: tracer.context.traceId, - // ), - // Sentry.currentHub, - // startTimestamp: SentryFlutter.dartLoadingEnd); - // await span3.finish(endTimestamp: appStartInfo?.end); - // - // await spanParent.finish(endTimestamp: appStartInfo?.end); - // - // tracer.children.add(spanParent); - // tracer.children.add(span); - // tracer.children.add(span2); - // tracer.children.add(span3); + final transaction = event.tracer; + if (appStartInfo != null) { + await _attachAppStartSpans(appStartInfo, transaction); + } return event; } + + Future _attachAppStartSpans( + AppStartInfo appStartInfo, SentryTracer transaction) async { + final op = 'app.start.${appStartInfo.type.name}'; + final transactionTraceId = transaction.context.traceId; + + final appStartSpan = await _createAndFinishSpan( + tracer: transaction, + operation: op, + description: '${appStartInfo.type.name.capitalize()} start', + parentSpanId: transaction.context.spanId, + traceId: transactionTraceId, + startTimestamp: appStartInfo.start, + endTimestamp: appStartInfo.end); + + final engineReadySpan = await _createAndFinishSpan( + tracer: transaction, + operation: op, + description: 'Engine init and ready', + parentSpanId: appStartSpan.context.spanId, + traceId: transactionTraceId, + startTimestamp: appStartInfo.start, + endTimestamp: appStartInfo.engineEnd); + + final dartIsolateLoadingSpan = await _createAndFinishSpan( + tracer: transaction, + operation: op, + description: 'Dart isolate loading', + parentSpanId: appStartSpan.context.spanId, + traceId: transactionTraceId, + startTimestamp: appStartInfo.engineEnd, + endTimestamp: appStartInfo.dartLoadingEnd); + + final firstFrameRenderSpan = await _createAndFinishSpan( + tracer: transaction, + operation: op, + description: 'First frame render', + parentSpanId: appStartSpan.context.spanId, + traceId: transactionTraceId, + startTimestamp: SentryFlutter.dartLoadingEnd!, + endTimestamp: appStartInfo.end); + + transaction.children.addAll([ + appStartSpan, + engineReadySpan, + dartIsolateLoadingSpan, + firstFrameRenderSpan + ]); + } + + Future _createAndFinishSpan({ + required SentryTracer tracer, + required String operation, + required String description, + required SpanId? parentSpanId, + required SentryId? traceId, + required DateTime startTimestamp, + required DateTime endTimestamp, + }) async { + final span = SentrySpan( + tracer, + SentrySpanContext( + operation: operation, + description: description, + parentSpanId: parentSpanId, + traceId: traceId, + ), + _hub, + startTimestamp: startTimestamp); + await span.finish(endTimestamp: endTimestamp); + return span; + } +} + +extension _StringExtension on String { + String capitalize() { + return "${this[0].toUpperCase()}${substring(1).toLowerCase()}"; + } } diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/flutter/lib/src/integrations/native_app_start_integration.dart index 766df63eed..aa055cc32d 100644 --- a/flutter/lib/src/integrations/native_app_start_integration.dart +++ b/flutter/lib/src/integrations/native_app_start_integration.dart @@ -54,7 +54,8 @@ class NativeAppStartIntegration extends Integration { final appStartInfo = AppStartInfo(AppStartType.cold, start: DateTime.now(), end: DateTime.now().add(const Duration(milliseconds: 100)), - engineEnd: DateTime.now().add(const Duration(milliseconds: 50))); + engineEnd: DateTime.now().add(const Duration(milliseconds: 50)), + dartLoadingEnd: DateTime.now().add(const Duration(milliseconds: 60))); setAppStartInfo(appStartInfo); return; } @@ -102,62 +103,9 @@ class NativeAppStartIntegration extends Integration { start: DateTime.fromMillisecondsSinceEpoch( nativeAppStart.appStartTime.toInt()), end: appStartEnd, - engineEnd: engineEndDatetime); + engineEnd: engineEndDatetime, + dartLoadingEnd: SentryFlutter.dartLoadingEnd!); setAppStartInfo(appStartInfo); - - final routeName = SentryNavigatorObserver.currentRouteName; - - // null means there is no navigator observer - if (routeName == null) { - final transaction = hub.startTransaction( - 'App Start', - 'ui.load', - startTimestamp: appStartInfo.start, - description: 'root /', - ); - - final op = 'app.start.${appStartInfo.type.name}'; - - final coldStartSpan = transaction.startChild( - op, - description: 'Cold start', - startTimestamp: appStartInfo.start, - ); - - final ttidSpan = transaction.startChild( - SentrySpanOperations.uiTimeToInitialDisplay, - description: 'Time to initial display', - startTimestamp: appStartInfo.start, - ); - - final engineInitSpan = coldStartSpan.startChild( - op, - description: 'Engine init and ready', - startTimestamp: appStartInfo.start, - ); - - final dartLoadingSpan = coldStartSpan.startChild( - op, - description: 'Dart isolate loading', - startTimestamp: appStartInfo.engineEnd, - ); - - final firstFrameRenderSpan = coldStartSpan.startChild( - op, - description: 'First frame render', - startTimestamp: SentryFlutter.dartLoadingEnd, - ); - - await engineInitSpan.finish(endTimestamp: appStartInfo.engineEnd); - await dartLoadingSpan.finish(endTimestamp: SentryFlutter.dartLoadingEnd); - await firstFrameRenderSpan.finish(endTimestamp: appStartInfo.end); - await coldStartSpan.finish(endTimestamp: appStartInfo.end); - await ttidSpan.finish(endTimestamp: appStartInfo.end); - await transaction.finish(endTimestamp: appStartInfo.end); - } - - print('route name: ${SentryNavigatorObserver.currentRouteName}'); - }); } @@ -171,12 +119,16 @@ enum AppStartType { cold, warm } class AppStartInfo { AppStartInfo(this.type, - {required this.start, required this.end, required this.engineEnd}); + {required this.start, + required this.end, + required this.engineEnd, + required this.dartLoadingEnd}); final AppStartType type; final DateTime start; final DateTime end; final DateTime engineEnd; + final DateTime dartLoadingEnd; Duration get duration => end.difference(start); diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 8b2f96e981..49cc03b4f5 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -34,6 +34,7 @@ typedef FlutterOptionsConfiguration = FutureOr Function( mixin SentryFlutter { static const _channel = MethodChannel('sentry_flutter'); + @internal static DateTime? dartLoadingEnd; static Future init( From d0eb5403bad60fe3ea6155e20eba433d8b222a89 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 23 Apr 2024 14:58:24 +0200 Subject: [PATCH 03/26] Remove print --- .../src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt b/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt index c6696cfb19..7966a33655 100644 --- a/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt +++ b/flutter/example/android/app/src/main/kotlin/io/sentry/samples/flutter/MainActivity.kt @@ -11,7 +11,6 @@ class MainActivity : FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - println("aaa configured engine") MethodChannel( flutterEngine.dartExecutor.binaryMessenger, _channel, From 05c208c647b183d3622cbf09efb8f51a0cb98e54 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Tue, 23 Apr 2024 14:59:06 +0200 Subject: [PATCH 04/26] Remove comments --- flutter/ios/Classes/SentryFlutterPluginApple.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index c62791a3d8..e36416e202 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -29,9 +29,9 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { private static var engineEndtime: Int64 = 0 public static func register(with registrar: FlutterPluginRegistrar) { - let currentDate = Date() // Gets the current date and time - let timeInterval = currentDate.timeIntervalSince1970 // Time in seconds since epoch (1970-01-01) - engineEndtime = Int64(timeInterval * 1000) // Convert to milliseconds + let currentDate = Date() + let timeInterval = currentDate.timeIntervalSince1970 + engineEndtime = Int64(timeInterval * 1000) #if os(iOS) let channel = FlutterMethodChannel(name: "sentry_flutter", binaryMessenger: registrar.messenger()) @@ -84,8 +84,8 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { return iso8601FormatterWithMillisecondPrecision.date(from: iso8601String) ?? iso8601Formatter.date(from: iso8601String) // Parse date with low precision formatter for backward compatible } - - + + private func fetchEngineEndtime(result: @escaping FlutterResult) { result(SentryFlutterPluginApple.engineEndtime) } @@ -162,7 +162,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { let arguments = call.arguments as? [String: Any?] let key = arguments?["key"] as? String removeTag(key: key, result: result) - + case "fetchEngineEndtime": fetchEngineEndtime(result: result) From c796b7172e9e22ca15e7c7e3f5c8c91cef264cfb Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 24 Apr 2024 14:34:43 +0200 Subject: [PATCH 05/26] Update --- .../native_app_start_event_processor.dart | 4 +- .../native_app_start_integration.dart | 4 +- flutter/lib/src/sentry_flutter.dart | 5 +- .../native_app_start_integration_test.dart | 95 +++++++++++++++++++ flutter/test/mocks.dart | 10 ++ .../sentry_display_widget_test.dart | 2 + .../test/sentry_navigator_observer_test.dart | 2 + 7 files changed, 119 insertions(+), 3 deletions(-) diff --git a/flutter/lib/src/event_processor/native_app_start_event_processor.dart b/flutter/lib/src/event_processor/native_app_start_event_processor.dart index 8e7c39ab05..8a2ddc18b3 100644 --- a/flutter/lib/src/event_processor/native_app_start_event_processor.dart +++ b/flutter/lib/src/event_processor/native_app_start_event_processor.dart @@ -1,3 +1,5 @@ +// ignore_for_file: invalid_use_of_internal_member + import 'dart:async'; import '../../sentry_flutter.dart'; @@ -78,7 +80,7 @@ class NativeAppStartEventProcessor implements EventProcessor { description: 'First frame render', parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, - startTimestamp: SentryFlutter.dartLoadingEnd!, + startTimestamp: SentryFlutter.dartLoadingEnd, endTimestamp: appStartInfo.end); transaction.children.addAll([ diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/flutter/lib/src/integrations/native_app_start_integration.dart index aa055cc32d..a074a95924 100644 --- a/flutter/lib/src/integrations/native_app_start_integration.dart +++ b/flutter/lib/src/integrations/native_app_start_integration.dart @@ -72,6 +72,7 @@ class NativeAppStartIntegration extends Integration { final appStartEnd = _native.appStartEnd; final nativeAppStart = await _native.fetchNativeAppStart(); final engineEndtime = await _native.fetchEngineEndtime(); + final dartLoadingEnd = SentryFlutter.dartLoadingEnd; if (nativeAppStart == null || appStartEnd == null || @@ -104,7 +105,8 @@ class NativeAppStartIntegration extends Integration { nativeAppStart.appStartTime.toInt()), end: appStartEnd, engineEnd: engineEndDatetime, - dartLoadingEnd: SentryFlutter.dartLoadingEnd!); + dartLoadingEnd: dartLoadingEnd); + setAppStartInfo(appStartInfo); }); } diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 49cc03b4f5..1e07e24373 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -34,8 +34,10 @@ typedef FlutterOptionsConfiguration = FutureOr Function( mixin SentryFlutter { static const _channel = MethodChannel('sentry_flutter'); + /// Represents the time when the dart isolate stopped loading and is ready to execute. @internal - static DateTime? dartLoadingEnd; + // ignore: invalid_use_of_internal_member + static DateTime dartLoadingEnd = getUtcDateTime(); static Future init( FlutterOptionsConfiguration optionsConfiguration, { @@ -46,6 +48,7 @@ mixin SentryFlutter { }) async { final flutterOptions = SentryFlutterOptions(); + // ignore: invalid_use_of_internal_member dartLoadingEnd = flutterOptions.clock(); if (platformChecker != null) { diff --git a/flutter/test/integrations/native_app_start_integration_test.dart b/flutter/test/integrations/native_app_start_integration_test.dart index 70b088cfec..f526686bde 100644 --- a/flutter/test/integrations/native_app_start_integration_test.dart +++ b/flutter/test/integrations/native_app_start_integration_test.dart @@ -1,4 +1,5 @@ @TestOn('vm') +import 'package:collection/collection.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -109,6 +110,100 @@ void main() { expect(appStartInfo?.end, DateTime.fromMillisecondsSinceEpoch(10)); }); }); + + group('App start spans', () { + late SentrySpan? coldStartSpan, + engineReadySpan, + dartIsolateLoadingSpan, + firstFrameRenderSpan; + // ignore: invalid_use_of_internal_member + late SentryTracer tracer; + late Fixture fixture; + + setUp(() async { + TestWidgetsFlutterBinding.ensureInitialized(); + + fixture = Fixture(); + NativeAppStartIntegration.clearAppStartInfo(); + + fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(50); + fixture.binding.nativeAppStart = NativeAppStart(0, true); + // dartLoadingEnd needs to be set after engine end (see MockNativeChannel) + SentryFlutter.dartLoadingEnd = DateTime.fromMillisecondsSinceEpoch(15); + + fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); + + final processor = fixture.options.eventProcessors.first; + tracer = fixture.createTracer(); + final transaction = SentryTransaction(tracer); + final enriched = + await processor.apply(transaction, Hint()) as SentryTransaction; + + coldStartSpan = enriched.spans.firstWhereOrNull( + (element) => element.context.description == 'Cold start'); + engineReadySpan = enriched.spans.firstWhereOrNull( + (element) => element.context.description == 'Engine init and ready'); + dartIsolateLoadingSpan = enriched.spans.firstWhereOrNull( + (element) => element.context.description == 'Dart isolate loading'); + firstFrameRenderSpan = enriched.spans.firstWhereOrNull( + (element) => element.context.description == 'First frame render'); + }); + + test('are added by event processor', () async { + expect(coldStartSpan, isNotNull); + expect(engineReadySpan, isNotNull); + expect(dartIsolateLoadingSpan, isNotNull); + expect(firstFrameRenderSpan, isNotNull); + }); + + test('have correct op', () async { + const op = 'app.start.cold'; + expect(coldStartSpan?.context.operation, op); + expect(engineReadySpan?.context.operation, op); + expect(dartIsolateLoadingSpan?.context.operation, op); + expect(firstFrameRenderSpan?.context.operation, op); + }); + + test('have correct parents', () async { + expect(coldStartSpan?.context.parentSpanId, tracer.context.spanId); + expect( + engineReadySpan?.context.parentSpanId, coldStartSpan?.context.spanId); + expect(dartIsolateLoadingSpan?.context.parentSpanId, + coldStartSpan?.context.spanId); + expect(firstFrameRenderSpan?.context.parentSpanId, + coldStartSpan?.context.spanId); + }); + + test('have correct traceId', () async { + final traceId = tracer.context.traceId; + expect(coldStartSpan?.context.traceId, traceId); + expect(engineReadySpan?.context.traceId, traceId); + expect(dartIsolateLoadingSpan?.context.traceId, traceId); + expect(firstFrameRenderSpan?.context.traceId, traceId); + }); + + test('have correct startTimestamp', () async { + final appStartTime = DateTime.fromMillisecondsSinceEpoch( + fixture.binding.nativeAppStart!.appStartTime.toInt()) + .toUtc(); + expect(coldStartSpan?.startTimestamp, appStartTime); + expect(engineReadySpan?.startTimestamp, appStartTime); + expect(dartIsolateLoadingSpan?.startTimestamp, + engineReadySpan?.endTimestamp); + expect(firstFrameRenderSpan?.startTimestamp, + dartIsolateLoadingSpan?.endTimestamp); + }); + + test('have correct endTimestamp', () async { + final engineEndtime = await fixture.native.fetchEngineEndtime(); + expect(coldStartSpan?.endTimestamp, fixture.native.appStartEnd?.toUtc()); + expect(engineReadySpan?.endTimestamp, + DateTime.fromMillisecondsSinceEpoch(engineEndtime!).toUtc()); + expect(dartIsolateLoadingSpan?.endTimestamp, + SentryFlutter.dartLoadingEnd.toUtc()); + expect(firstFrameRenderSpan?.endTimestamp, coldStartSpan?.endTimestamp); + }); + }); } class Fixture { diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index 0520a09884..4cf9740b2b 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -294,6 +294,11 @@ class TestMockSentryNative implements SentryNative { numberOfDiscardProfilerCalls++; return Future.value(null); } + + @override + Future fetchEngineEndtime() { + return Future.value(10); + } } // TODO can this be replaced with https://pub.dev/packages/mockito#verifying-exact-number-of-invocations--at-least-x--never @@ -395,6 +400,11 @@ class MockNativeChannel implements SentryNativeBinding { numberOfDiscardProfilerCalls++; return Future.value(null); } + + @override + Future fetchEngineEndtime() { + return Future.value(10); + } } class MockRendererWrapper implements RendererWrapper { diff --git a/flutter/test/navigation/sentry_display_widget_test.dart b/flutter/test/navigation/sentry_display_widget_test.dart index b9c2eb37cb..fbfbfe44d1 100644 --- a/flutter/test/navigation/sentry_display_widget_test.dart +++ b/flutter/test/navigation/sentry_display_widget_test.dart @@ -61,6 +61,8 @@ void main() { AppStartType.cold, start: getUtcDateTime().add(Duration(seconds: 1)), end: getUtcDateTime().add(Duration(seconds: 2)), + engineEnd: getUtcDateTime().add(Duration(seconds: 3)), + dartLoadingEnd: getUtcDateTime().add(Duration(seconds: 4)), ); NativeAppStartIntegration.setAppStartInfo(appStartInfo); diff --git a/flutter/test/sentry_navigator_observer_test.dart b/flutter/test/sentry_navigator_observer_test.dart index 1b41833e20..f169daf4d4 100644 --- a/flutter/test/sentry_navigator_observer_test.dart +++ b/flutter/test/sentry_navigator_observer_test.dart @@ -501,6 +501,8 @@ void main() { AppStartType.cold, start: DateTime.now().add(const Duration(seconds: 1)), end: DateTime.now().add(const Duration(seconds: 2)), + engineEnd: DateTime.now().add(const Duration(seconds: 3)), + dartLoadingEnd: DateTime.now().add(const Duration(seconds: 4)), ), ); From bdc858851c2a4b39742d79a18a3dba757aed5840 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 24 Apr 2024 14:43:31 +0200 Subject: [PATCH 06/26] Add linting --- flutter/ios/Classes/SentryFlutterPluginApple.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index e36416e202..67055b2014 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -85,7 +85,6 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { ?? iso8601Formatter.date(from: iso8601String) // Parse date with low precision formatter for backward compatible } - private func fetchEngineEndtime(result: @escaping FlutterResult) { result(SentryFlutterPluginApple.engineEndtime) } From d1b37cd7889b5d99418f99522d104a1d4e336907 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 24 Apr 2024 14:47:32 +0200 Subject: [PATCH 07/26] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cf3f707c7..ebe4c9a0eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Adds app start spans when using `SentryNavigatorObserver` ([#2009](https://github.com/getsentry/sentry-dart/pull/2009)) + ### Fixes - Timing metric aggregates metrics in the created span ([#1994](https://github.com/getsentry/sentry-dart/pull/1994)) From 6f444787cd4d3eed252b77ba80ac680bfc69d3e0 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 24 Apr 2024 14:55:38 +0200 Subject: [PATCH 08/26] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebe4c9a0eb..bab85d48df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Features -- Adds app start spans when using `SentryNavigatorObserver` ([#2009](https://github.com/getsentry/sentry-dart/pull/2009)) +- Adds app start spans to first transaction ([#2009](https://github.com/getsentry/sentry-dart/pull/2009)) ### Fixes From 805197202889fcb51023e52f1d061a271b03ae17 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 24 Apr 2024 14:58:26 +0200 Subject: [PATCH 09/26] Update naming --- .../main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 4 ++-- flutter/ios/Classes/SentryFlutterPluginApple.swift | 6 +++--- .../lib/src/integrations/native_app_start_integration.dart | 2 +- flutter/lib/src/native/sentry_native.dart | 4 ++-- flutter/lib/src/native/sentry_native_binding.dart | 2 +- flutter/lib/src/native/sentry_native_channel.dart | 4 ++-- .../integrations/native_app_start_integration_test.dart | 2 +- flutter/test/mocks.dart | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 7abcbc5d54..6fe8bf2de9 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -70,7 +70,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { "removeExtra" -> removeExtra(call.argument("key"), result) "setTag" -> setTag(call.argument("key"), call.argument("value"), result) "removeTag" -> removeTag(call.argument("key"), result) - "fetchEngineEndtime" -> fetchEngineEndtime(result) + "fetchEngineReadyEndtime" -> fetchEngineReadyEndtime(result) "loadContexts" -> loadContexts(result) else -> result.notImplemented() } @@ -148,7 +148,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } } - private fun fetchEngineEndtime(result: Result) { + private fun fetchEngineReadyEndtime(result: Result) { result.success(engineEndTime) } diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index 67055b2014..4f61df6284 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -85,7 +85,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { ?? iso8601Formatter.date(from: iso8601String) // Parse date with low precision formatter for backward compatible } - private func fetchEngineEndtime(result: @escaping FlutterResult) { + private func fetchEngineReadyEndtime(result: @escaping FlutterResult) { result(SentryFlutterPluginApple.engineEndtime) } @@ -162,8 +162,8 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { let key = arguments?["key"] as? String removeTag(key: key, result: result) - case "fetchEngineEndtime": - fetchEngineEndtime(result: result) + case "fetchEngineReadyEndtime": + fetchEngineReadyEndtime(result: result) #if !os(tvOS) && !os(watchOS) case "discardProfiler": diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/flutter/lib/src/integrations/native_app_start_integration.dart index a074a95924..24d6bad5e8 100644 --- a/flutter/lib/src/integrations/native_app_start_integration.dart +++ b/flutter/lib/src/integrations/native_app_start_integration.dart @@ -71,7 +71,7 @@ class NativeAppStartIntegration extends Integration { _native.appStartEnd ??= options.clock(); final appStartEnd = _native.appStartEnd; final nativeAppStart = await _native.fetchNativeAppStart(); - final engineEndtime = await _native.fetchEngineEndtime(); + final engineEndtime = await _native.fetchEngineReadyEndtime(); final dartLoadingEnd = SentryFlutter.dartLoadingEnd; if (nativeAppStart == null || diff --git a/flutter/lib/src/native/sentry_native.dart b/flutter/lib/src/native/sentry_native.dart index 28ccef4068..b17f0f7bda 100644 --- a/flutter/lib/src/native/sentry_native.dart +++ b/flutter/lib/src/native/sentry_native.dart @@ -36,8 +36,8 @@ class SentryNative { return _invoke("fetchNativeAppStart", _binding.fetchNativeAppStart); } - Future fetchEngineEndtime() async { - return _invoke("fetchEngineEndtime", _binding.fetchEngineEndtime); + Future fetchEngineReadyEndtime() async { + return _invoke("fetchEngineReadyEndtime", _binding.fetchEngineReadyEndtime); } // NativeFrames diff --git a/flutter/lib/src/native/sentry_native_binding.dart b/flutter/lib/src/native/sentry_native_binding.dart index ee6aae823f..743a272dc5 100644 --- a/flutter/lib/src/native/sentry_native_binding.dart +++ b/flutter/lib/src/native/sentry_native_binding.dart @@ -12,7 +12,7 @@ abstract class SentryNativeBinding { Future fetchNativeAppStart(); - Future fetchEngineEndtime(); + Future fetchEngineReadyEndtime(); Future beginNativeFrames(); diff --git a/flutter/lib/src/native/sentry_native_channel.dart b/flutter/lib/src/native/sentry_native_channel.dart index e8ac6f7afb..e62117724f 100644 --- a/flutter/lib/src/native/sentry_native_channel.dart +++ b/flutter/lib/src/native/sentry_native_channel.dart @@ -25,8 +25,8 @@ class SentryNativeChannel implements SentryNativeBinding { } @override - Future fetchEngineEndtime() async { - return _channel.invokeMethod('fetchEngineEndtime'); + Future fetchEngineReadyEndtime() async { + return _channel.invokeMethod('fetchEngineReadyEndtime'); } @override diff --git a/flutter/test/integrations/native_app_start_integration_test.dart b/flutter/test/integrations/native_app_start_integration_test.dart index f526686bde..358eeeaf06 100644 --- a/flutter/test/integrations/native_app_start_integration_test.dart +++ b/flutter/test/integrations/native_app_start_integration_test.dart @@ -195,7 +195,7 @@ void main() { }); test('have correct endTimestamp', () async { - final engineEndtime = await fixture.native.fetchEngineEndtime(); + final engineEndtime = await fixture.native.fetchEngineReadyEndtime(); expect(coldStartSpan?.endTimestamp, fixture.native.appStartEnd?.toUtc()); expect(engineReadySpan?.endTimestamp, DateTime.fromMillisecondsSinceEpoch(engineEndtime!).toUtc()); diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index 4cf9740b2b..d6682933bd 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -296,7 +296,7 @@ class TestMockSentryNative implements SentryNative { } @override - Future fetchEngineEndtime() { + Future fetchEngineReadyEndtime() { return Future.value(10); } } @@ -402,7 +402,7 @@ class MockNativeChannel implements SentryNativeBinding { } @override - Future fetchEngineEndtime() { + Future fetchEngineReadyEndtime() { return Future.value(10); } } From b401ed0442655584d5e2932f3d5c53ddb6df8892 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 24 Apr 2024 15:09:14 +0200 Subject: [PATCH 10/26] Update naming --- .../ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 0fa7c24eb1..fee8e19903 100644 --- a/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 24 Apr 2024 15:09:50 +0200 Subject: [PATCH 11/26] Update naming --- .../main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 6 +++--- flutter/ios/Classes/SentryFlutterPluginApple.swift | 6 +++--- .../lib/src/integrations/native_app_start_integration.dart | 6 +++--- .../integrations/native_app_start_integration_test.dart | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 6fe8bf2de9..f08e160e3c 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -36,10 +36,10 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private var activity: WeakReference? = null private var framesTracker: ActivityFramesTracker? = null - private var engineEndTime: Long? = null + private var engineReadyEndtime: Long? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - engineEndTime = System.currentTimeMillis() + engineReadyEndtime = System.currentTimeMillis() context = flutterPluginBinding.applicationContext channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sentry_flutter") @@ -149,7 +149,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } private fun fetchEngineReadyEndtime(result: Result) { - result.success(engineEndTime) + result.success(engineReadyEndtime) } private fun beginNativeFrames(result: Result) { diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index 4f61df6284..bd92a07bfa 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -26,12 +26,12 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { #endif } - private static var engineEndtime: Int64 = 0 + private static var engineReadyEndtime: Int64 = 0 public static func register(with registrar: FlutterPluginRegistrar) { let currentDate = Date() let timeInterval = currentDate.timeIntervalSince1970 - engineEndtime = Int64(timeInterval * 1000) + engineReadyEndtime = Int64(timeInterval * 1000) #if os(iOS) let channel = FlutterMethodChannel(name: "sentry_flutter", binaryMessenger: registrar.messenger()) @@ -86,7 +86,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { } private func fetchEngineReadyEndtime(result: @escaping FlutterResult) { - result(SentryFlutterPluginApple.engineEndtime) + result(SentryFlutterPluginApple.engineReadyEndtime) } // swiftlint:disable:next cyclomatic_complexity diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/flutter/lib/src/integrations/native_app_start_integration.dart index 24d6bad5e8..f9009e940d 100644 --- a/flutter/lib/src/integrations/native_app_start_integration.dart +++ b/flutter/lib/src/integrations/native_app_start_integration.dart @@ -71,12 +71,12 @@ class NativeAppStartIntegration extends Integration { _native.appStartEnd ??= options.clock(); final appStartEnd = _native.appStartEnd; final nativeAppStart = await _native.fetchNativeAppStart(); - final engineEndtime = await _native.fetchEngineReadyEndtime(); + final engineReadyEndtime = await _native.fetchEngineReadyEndtime(); final dartLoadingEnd = SentryFlutter.dartLoadingEnd; if (nativeAppStart == null || appStartEnd == null || - engineEndtime == null) { + engineReadyEndtime == null) { return; } @@ -84,7 +84,7 @@ class NativeAppStartIntegration extends Integration { nativeAppStart.appStartTime.toInt()); final duration = appStartEnd.difference(appStartDateTime); final engineEndDatetime = - DateTime.fromMillisecondsSinceEpoch(engineEndtime); + DateTime.fromMillisecondsSinceEpoch(engineReadyEndtime); // We filter out app start more than 60s. // This could be due to many different reasons. diff --git a/flutter/test/integrations/native_app_start_integration_test.dart b/flutter/test/integrations/native_app_start_integration_test.dart index 358eeeaf06..ad9413fef1 100644 --- a/flutter/test/integrations/native_app_start_integration_test.dart +++ b/flutter/test/integrations/native_app_start_integration_test.dart @@ -195,10 +195,10 @@ void main() { }); test('have correct endTimestamp', () async { - final engineEndtime = await fixture.native.fetchEngineReadyEndtime(); + final engineReadyEndtime = await fixture.native.fetchEngineReadyEndtime(); expect(coldStartSpan?.endTimestamp, fixture.native.appStartEnd?.toUtc()); expect(engineReadySpan?.endTimestamp, - DateTime.fromMillisecondsSinceEpoch(engineEndtime!).toUtc()); + DateTime.fromMillisecondsSinceEpoch(engineReadyEndtime!).toUtc()); expect(dartIsolateLoadingSpan?.endTimestamp, SentryFlutter.dartLoadingEnd.toUtc()); expect(firstFrameRenderSpan?.endTimestamp, coldStartSpan?.endTimestamp); From 2bb388790433cb070430a44ddef1565b4cd79266 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 24 Apr 2024 15:30:26 +0200 Subject: [PATCH 12/26] Update description from first frame render to initial frame render --- .../src/event_processor/native_app_start_event_processor.dart | 2 +- .../test/integrations/native_app_start_integration_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/src/event_processor/native_app_start_event_processor.dart b/flutter/lib/src/event_processor/native_app_start_event_processor.dart index 8a2ddc18b3..a585b640e3 100644 --- a/flutter/lib/src/event_processor/native_app_start_event_processor.dart +++ b/flutter/lib/src/event_processor/native_app_start_event_processor.dart @@ -77,7 +77,7 @@ class NativeAppStartEventProcessor implements EventProcessor { final firstFrameRenderSpan = await _createAndFinishSpan( tracer: transaction, operation: op, - description: 'First frame render', + description: 'Initial frame render', parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, startTimestamp: SentryFlutter.dartLoadingEnd, diff --git a/flutter/test/integrations/native_app_start_integration_test.dart b/flutter/test/integrations/native_app_start_integration_test.dart index ad9413fef1..7cfc3d6ca1 100644 --- a/flutter/test/integrations/native_app_start_integration_test.dart +++ b/flutter/test/integrations/native_app_start_integration_test.dart @@ -146,7 +146,7 @@ void main() { dartIsolateLoadingSpan = enriched.spans.firstWhereOrNull( (element) => element.context.description == 'Dart isolate loading'); firstFrameRenderSpan = enriched.spans.firstWhereOrNull( - (element) => element.context.description == 'First frame render'); + (element) => element.context.description == 'Initial frame render'); }); test('are added by event processor', () async { From 801483e1f3cadf572bbf3bd00ac509dbbd5f780c Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 26 Apr 2024 22:34:28 +0200 Subject: [PATCH 13/26] update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 10 +-- flutter/example/ios/Runner/AppDelegate.swift | 1 + flutter/example/lib/main.dart | 2 +- .../Classes/SentryFlutterPluginApple.swift | 16 ++--- .../native_app_start_event_processor.dart | 29 +++++---- .../native_app_start_integration.dart | 28 ++++---- flutter/lib/src/native/sentry_native.dart | 15 +++-- .../lib/src/native/sentry_native_binding.dart | 2 - .../lib/src/native/sentry_native_channel.dart | 5 -- flutter/lib/src/sentry_flutter.dart | 4 +- .../native_app_start_integration_test.dart | 64 +++++++++++-------- flutter/test/mocks.dart | 10 --- .../sentry_display_widget_test.dart | 4 +- flutter/test/sentry_native_test.dart | 2 +- .../test/sentry_navigator_observer_test.dart | 4 +- 15 files changed, 95 insertions(+), 101 deletions(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index f08e160e3c..8d92c9178c 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -36,10 +36,10 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private var activity: WeakReference? = null private var framesTracker: ActivityFramesTracker? = null - private var engineReadyEndtime: Long? = null + private var pluginRegistrationTime: Long? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - engineReadyEndtime = System.currentTimeMillis() + pluginRegistrationTime = System.currentTimeMillis() context = flutterPluginBinding.applicationContext channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sentry_flutter") @@ -70,7 +70,6 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { "removeExtra" -> removeExtra(call.argument("key"), result) "setTag" -> setTag(call.argument("key"), call.argument("value"), result) "removeTag" -> removeTag(call.argument("key"), result) - "fetchEngineReadyEndtime" -> fetchEngineReadyEndtime(result) "loadContexts" -> loadContexts(result) else -> result.notImplemented() } @@ -141,6 +140,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } else { val appStartTimeMillis = DateUtils.nanosToMillis(appStartTime.nanoTimestamp().toDouble()) val item = mapOf( + "pluginRegistrationTime" to pluginRegistrationTime, "appStartTime" to appStartTimeMillis, "isColdStart" to isColdStart, ) @@ -148,10 +148,6 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } } - private fun fetchEngineReadyEndtime(result: Result) { - result.success(engineReadyEndtime) - } - private fun beginNativeFrames(result: Result) { if (!sentryFlutter.autoPerformanceTracingEnabled) { result.success(null) diff --git a/flutter/example/ios/Runner/AppDelegate.swift b/flutter/example/ios/Runner/AppDelegate.swift index a231cc9c60..d0a1ec3516 100644 --- a/flutter/example/ios/Runner/AppDelegate.swift +++ b/flutter/example/ios/Runner/AppDelegate.swift @@ -10,6 +10,7 @@ import Sentry _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + print(Date().timeIntervalSince1970) GeneratedPluginRegistrant.register(with: self) guard let controller = window?.rootViewController as? FlutterViewController else { diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 86da143e31..9ff5e7baf7 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -83,7 +83,7 @@ Future setupSentry( // configuration issues, e.g. finding out why your events are not uploaded. options.debug = true; options.spotlight = Spotlight(enabled: true); - options.enableTimeToFullDisplayTracing = true; + // options.enableTimeToFullDisplayTracing = true; options.enableMetrics = true; options.maxRequestBodySize = MaxRequestBodySize.always; diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index bd92a07bfa..f4cd0a0f59 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -25,13 +25,15 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { return NSApplication.didBecomeActiveNotification #endif } - - private static var engineReadyEndtime: Int64 = 0 + + // Represents the time when the flutter engine starts to register plugins + private static var pluginRegistrationTime: Int64 = 0 public static func register(with registrar: FlutterPluginRegistrar) { let currentDate = Date() let timeInterval = currentDate.timeIntervalSince1970 engineReadyEndtime = Int64(timeInterval * 1000) + pluginRegistrationTime = Int64(timeInterval * 1000) #if os(iOS) let channel = FlutterMethodChannel(name: "sentry_flutter", binaryMessenger: registrar.messenger()) @@ -85,10 +87,6 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { ?? iso8601Formatter.date(from: iso8601String) // Parse date with low precision formatter for backward compatible } - private func fetchEngineReadyEndtime(result: @escaping FlutterResult) { - result(SentryFlutterPluginApple.engineReadyEndtime) - } - // swiftlint:disable:next cyclomatic_complexity public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method as String { @@ -162,9 +160,6 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { let key = arguments?["key"] as? String removeTag(key: key, result: result) - case "fetchEngineReadyEndtime": - fetchEngineReadyEndtime(result: result) - #if !os(tvOS) && !os(watchOS) case "discardProfiler": discardProfiler(call, result) @@ -395,11 +390,12 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { result(nil) return } - + let appStartTime = appStartMeasurement.appStartTimestamp.timeIntervalSince1970 * 1000 let isColdStart = appStartMeasurement.type == .cold let item: [String: Any] = [ + "pluginRegistrationTime" : SentryFlutterPluginApple.pluginRegistrationTime, "appStartTime": appStartTime, "isColdStart": isColdStart ] diff --git a/flutter/lib/src/event_processor/native_app_start_event_processor.dart b/flutter/lib/src/event_processor/native_app_start_event_processor.dart index a585b640e3..6b2ef82338 100644 --- a/flutter/lib/src/event_processor/native_app_start_event_processor.dart +++ b/flutter/lib/src/event_processor/native_app_start_event_processor.dart @@ -56,37 +56,37 @@ class NativeAppStartEventProcessor implements EventProcessor { startTimestamp: appStartInfo.start, endTimestamp: appStartInfo.end); - final engineReadySpan = await _createAndFinishSpan( + final pluginRegistrationSpan = await _createAndFinishSpan( tracer: transaction, operation: op, - description: 'Engine init and ready', + description: AppStartSpanDescriptions.pluginRegistration, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, startTimestamp: appStartInfo.start, - endTimestamp: appStartInfo.engineEnd); + endTimestamp: appStartInfo.pluginRegistration); - final dartIsolateLoadingSpan = await _createAndFinishSpan( + final mainIsolateSetupSpan = await _createAndFinishSpan( tracer: transaction, operation: op, - description: 'Dart isolate loading', + description: AppStartSpanDescriptions.mainIsolateSetup, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, - startTimestamp: appStartInfo.engineEnd, - endTimestamp: appStartInfo.dartLoadingEnd); + startTimestamp: appStartInfo.pluginRegistration, + endTimestamp: appStartInfo.mainIsolateStart); final firstFrameRenderSpan = await _createAndFinishSpan( tracer: transaction, operation: op, - description: 'Initial frame render', + description: AppStartSpanDescriptions.firstFrameRender, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, - startTimestamp: SentryFlutter.dartLoadingEnd, + startTimestamp: SentryFlutter.mainIsolateStartTime, endTimestamp: appStartInfo.end); transaction.children.addAll([ appStartSpan, - engineReadySpan, - dartIsolateLoadingSpan, + pluginRegistrationSpan, + mainIsolateSetupSpan, firstFrameRenderSpan ]); } @@ -120,3 +120,10 @@ extension _StringExtension on String { return "${this[0].toUpperCase()}${substring(1).toLowerCase()}"; } } + +class AppStartSpanDescriptions { + static const String pluginRegistration = 'App start to plugin registration'; + static const String mainIsolateSetup = 'Main isolate setup'; + static const String firstFrameRender = 'First frame render'; + // TODO: Add iOS and Android specific descriptions when implementing native spans +} diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/flutter/lib/src/integrations/native_app_start_integration.dart index f9009e940d..da60f32ab2 100644 --- a/flutter/lib/src/integrations/native_app_start_integration.dart +++ b/flutter/lib/src/integrations/native_app_start_integration.dart @@ -54,8 +54,10 @@ class NativeAppStartIntegration extends Integration { final appStartInfo = AppStartInfo(AppStartType.cold, start: DateTime.now(), end: DateTime.now().add(const Duration(milliseconds: 100)), - engineEnd: DateTime.now().add(const Duration(milliseconds: 50)), - dartLoadingEnd: DateTime.now().add(const Duration(milliseconds: 60))); + pluginRegistration: + DateTime.now().add(const Duration(milliseconds: 50)), + mainIsolateStart: + DateTime.now().add(const Duration(milliseconds: 60))); setAppStartInfo(appStartInfo); return; } @@ -71,20 +73,20 @@ class NativeAppStartIntegration extends Integration { _native.appStartEnd ??= options.clock(); final appStartEnd = _native.appStartEnd; final nativeAppStart = await _native.fetchNativeAppStart(); - final engineReadyEndtime = await _native.fetchEngineReadyEndtime(); - final dartLoadingEnd = SentryFlutter.dartLoadingEnd; + final pluginRegistrationTime = nativeAppStart?.pluginRegistrationTime; + final mainIsolateStartTime = SentryFlutter.mainIsolateStartTime; if (nativeAppStart == null || appStartEnd == null || - engineReadyEndtime == null) { + pluginRegistrationTime == null) { return; } final appStartDateTime = DateTime.fromMillisecondsSinceEpoch( nativeAppStart.appStartTime.toInt()); final duration = appStartEnd.difference(appStartDateTime); - final engineEndDatetime = - DateTime.fromMillisecondsSinceEpoch(engineReadyEndtime); + final pluginRegistrationDateTime = + DateTime.fromMillisecondsSinceEpoch(pluginRegistrationTime); // We filter out app start more than 60s. // This could be due to many different reasons. @@ -104,8 +106,8 @@ class NativeAppStartIntegration extends Integration { start: DateTime.fromMillisecondsSinceEpoch( nativeAppStart.appStartTime.toInt()), end: appStartEnd, - engineEnd: engineEndDatetime, - dartLoadingEnd: dartLoadingEnd); + pluginRegistration: pluginRegistrationDateTime, + mainIsolateStart: mainIsolateStartTime); setAppStartInfo(appStartInfo); }); @@ -123,14 +125,14 @@ class AppStartInfo { AppStartInfo(this.type, {required this.start, required this.end, - required this.engineEnd, - required this.dartLoadingEnd}); + required this.pluginRegistration, + required this.mainIsolateStart}); final AppStartType type; final DateTime start; final DateTime end; - final DateTime engineEnd; - final DateTime dartLoadingEnd; + final DateTime pluginRegistration; + final DateTime mainIsolateStart; Duration get duration => end.difference(start); diff --git a/flutter/lib/src/native/sentry_native.dart b/flutter/lib/src/native/sentry_native.dart index b17f0f7bda..b6711d2446 100644 --- a/flutter/lib/src/native/sentry_native.dart +++ b/flutter/lib/src/native/sentry_native.dart @@ -36,10 +36,6 @@ class SentryNative { return _invoke("fetchNativeAppStart", _binding.fetchNativeAppStart); } - Future fetchEngineReadyEndtime() async { - return _invoke("fetchEngineReadyEndtime", _binding.fetchEngineReadyEndtime); - } - // NativeFrames Future beginNativeFramesCollection() => @@ -133,15 +129,20 @@ class SentryNative { } class NativeAppStart { - NativeAppStart(this.appStartTime, this.isColdStart); + NativeAppStart( + {required this.pluginRegistrationTime, + required this.appStartTime, + required this.isColdStart}); + int pluginRegistrationTime; double appStartTime; bool isColdStart; factory NativeAppStart.fromJson(Map json) { return NativeAppStart( - json['appStartTime'] as double, - json['isColdStart'] as bool, + pluginRegistrationTime: json['pluginRegistrationTime'] as int, + appStartTime: json['appStartTime'] as double, + isColdStart: json['isColdStart'] as bool, ); } } diff --git a/flutter/lib/src/native/sentry_native_binding.dart b/flutter/lib/src/native/sentry_native_binding.dart index 743a272dc5..54d335d529 100644 --- a/flutter/lib/src/native/sentry_native_binding.dart +++ b/flutter/lib/src/native/sentry_native_binding.dart @@ -12,8 +12,6 @@ abstract class SentryNativeBinding { Future fetchNativeAppStart(); - Future fetchEngineReadyEndtime(); - Future beginNativeFrames(); Future endNativeFrames(SentryId id); diff --git a/flutter/lib/src/native/sentry_native_channel.dart b/flutter/lib/src/native/sentry_native_channel.dart index e62117724f..4bf9745cb3 100644 --- a/flutter/lib/src/native/sentry_native_channel.dart +++ b/flutter/lib/src/native/sentry_native_channel.dart @@ -24,11 +24,6 @@ class SentryNativeChannel implements SentryNativeBinding { return (json != null) ? NativeAppStart.fromJson(json) : null; } - @override - Future fetchEngineReadyEndtime() async { - return _channel.invokeMethod('fetchEngineReadyEndtime'); - } - @override Future beginNativeFrames() => _channel.invokeMethod('beginNativeFrames'); diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 1e07e24373..7fe18fec2b 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -37,7 +37,7 @@ mixin SentryFlutter { /// Represents the time when the dart isolate stopped loading and is ready to execute. @internal // ignore: invalid_use_of_internal_member - static DateTime dartLoadingEnd = getUtcDateTime(); + static DateTime mainIsolateStartTime = getUtcDateTime(); static Future init( FlutterOptionsConfiguration optionsConfiguration, { @@ -49,7 +49,7 @@ mixin SentryFlutter { final flutterOptions = SentryFlutterOptions(); // ignore: invalid_use_of_internal_member - dartLoadingEnd = flutterOptions.clock(); + mainIsolateStartTime = flutterOptions.clock(); if (platformChecker != null) { flutterOptions.platformChecker = platformChecker; diff --git a/flutter/test/integrations/native_app_start_integration_test.dart b/flutter/test/integrations/native_app_start_integration_test.dart index 7cfc3d6ca1..fb1245a6ad 100644 --- a/flutter/test/integrations/native_app_start_integration_test.dart +++ b/flutter/test/integrations/native_app_start_integration_test.dart @@ -24,7 +24,8 @@ void main() { test('native app start measurement added to first transaction', () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10); - fixture.binding.nativeAppStart = NativeAppStart(0, true); + fixture.binding.nativeAppStart = NativeAppStart( + appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -43,7 +44,8 @@ void main() { test('native app start measurement not added to following transactions', () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10); - fixture.binding.nativeAppStart = NativeAppStart(0, true); + fixture.binding.nativeAppStart = NativeAppStart( + appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -62,7 +64,8 @@ void main() { test('measurements appended', () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10); - fixture.binding.nativeAppStart = NativeAppStart(0, true); + fixture.binding.nativeAppStart = NativeAppStart( + appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); final measurement = SentryMeasurement.warmAppStart(Duration(seconds: 1)); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -84,7 +87,8 @@ void main() { test('native app start measurement not added if more than 60s', () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(60001); - fixture.binding.nativeAppStart = NativeAppStart(0, true); + fixture.binding.nativeAppStart = NativeAppStart( + appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -101,7 +105,8 @@ void main() { test('native app start integration is called and sets app start info', () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10); - fixture.binding.nativeAppStart = NativeAppStart(0, true); + fixture.binding.nativeAppStart = NativeAppStart( + appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -113,8 +118,8 @@ void main() { group('App start spans', () { late SentrySpan? coldStartSpan, - engineReadySpan, - dartIsolateLoadingSpan, + pluginRegistrationSpan, + mainIsolateSetupSpan, firstFrameRenderSpan; // ignore: invalid_use_of_internal_member late SentryTracer tracer; @@ -127,9 +132,11 @@ void main() { NativeAppStartIntegration.clearAppStartInfo(); fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(50); - fixture.binding.nativeAppStart = NativeAppStart(0, true); + fixture.binding.nativeAppStart = NativeAppStart( + appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); // dartLoadingEnd needs to be set after engine end (see MockNativeChannel) - SentryFlutter.dartLoadingEnd = DateTime.fromMillisecondsSinceEpoch(15); + SentryFlutter.mainIsolateStartTime = + DateTime.fromMillisecondsSinceEpoch(15); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -141,9 +148,9 @@ void main() { coldStartSpan = enriched.spans.firstWhereOrNull( (element) => element.context.description == 'Cold start'); - engineReadySpan = enriched.spans.firstWhereOrNull( + pluginRegistrationSpan = enriched.spans.firstWhereOrNull( (element) => element.context.description == 'Engine init and ready'); - dartIsolateLoadingSpan = enriched.spans.firstWhereOrNull( + mainIsolateSetupSpan = enriched.spans.firstWhereOrNull( (element) => element.context.description == 'Dart isolate loading'); firstFrameRenderSpan = enriched.spans.firstWhereOrNull( (element) => element.context.description == 'Initial frame render'); @@ -151,24 +158,24 @@ void main() { test('are added by event processor', () async { expect(coldStartSpan, isNotNull); - expect(engineReadySpan, isNotNull); - expect(dartIsolateLoadingSpan, isNotNull); + expect(pluginRegistrationSpan, isNotNull); + expect(mainIsolateSetupSpan, isNotNull); expect(firstFrameRenderSpan, isNotNull); }); test('have correct op', () async { const op = 'app.start.cold'; expect(coldStartSpan?.context.operation, op); - expect(engineReadySpan?.context.operation, op); - expect(dartIsolateLoadingSpan?.context.operation, op); + expect(pluginRegistrationSpan?.context.operation, op); + expect(mainIsolateSetupSpan?.context.operation, op); expect(firstFrameRenderSpan?.context.operation, op); }); test('have correct parents', () async { expect(coldStartSpan?.context.parentSpanId, tracer.context.spanId); expect( - engineReadySpan?.context.parentSpanId, coldStartSpan?.context.spanId); - expect(dartIsolateLoadingSpan?.context.parentSpanId, + pluginRegistrationSpan?.context.parentSpanId, coldStartSpan?.context.spanId); + expect(mainIsolateSetupSpan?.context.parentSpanId, coldStartSpan?.context.spanId); expect(firstFrameRenderSpan?.context.parentSpanId, coldStartSpan?.context.spanId); @@ -177,8 +184,8 @@ void main() { test('have correct traceId', () async { final traceId = tracer.context.traceId; expect(coldStartSpan?.context.traceId, traceId); - expect(engineReadySpan?.context.traceId, traceId); - expect(dartIsolateLoadingSpan?.context.traceId, traceId); + expect(pluginRegistrationSpan?.context.traceId, traceId); + expect(mainIsolateSetupSpan?.context.traceId, traceId); expect(firstFrameRenderSpan?.context.traceId, traceId); }); @@ -187,20 +194,21 @@ void main() { fixture.binding.nativeAppStart!.appStartTime.toInt()) .toUtc(); expect(coldStartSpan?.startTimestamp, appStartTime); - expect(engineReadySpan?.startTimestamp, appStartTime); - expect(dartIsolateLoadingSpan?.startTimestamp, - engineReadySpan?.endTimestamp); + expect(pluginRegistrationSpan?.startTimestamp, appStartTime); + expect(mainIsolateSetupSpan?.startTimestamp, + pluginRegistrationSpan?.endTimestamp); expect(firstFrameRenderSpan?.startTimestamp, - dartIsolateLoadingSpan?.endTimestamp); + mainIsolateSetupSpan?.endTimestamp); }); test('have correct endTimestamp', () async { - final engineReadyEndtime = await fixture.native.fetchEngineReadyEndtime(); + final engineReadyEndtime = DateTime.fromMillisecondsSinceEpoch( + fixture.binding.nativeAppStart!.pluginRegistrationTime.toInt()) + .toUtc(); expect(coldStartSpan?.endTimestamp, fixture.native.appStartEnd?.toUtc()); - expect(engineReadySpan?.endTimestamp, - DateTime.fromMillisecondsSinceEpoch(engineReadyEndtime!).toUtc()); - expect(dartIsolateLoadingSpan?.endTimestamp, - SentryFlutter.dartLoadingEnd.toUtc()); + expect(pluginRegistrationSpan?.endTimestamp, engineReadyEndtime); + expect(mainIsolateSetupSpan?.endTimestamp, + SentryFlutter.mainIsolateStartTime.toUtc()); expect(firstFrameRenderSpan?.endTimestamp, coldStartSpan?.endTimestamp); }); }); diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index d6682933bd..0520a09884 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -294,11 +294,6 @@ class TestMockSentryNative implements SentryNative { numberOfDiscardProfilerCalls++; return Future.value(null); } - - @override - Future fetchEngineReadyEndtime() { - return Future.value(10); - } } // TODO can this be replaced with https://pub.dev/packages/mockito#verifying-exact-number-of-invocations--at-least-x--never @@ -400,11 +395,6 @@ class MockNativeChannel implements SentryNativeBinding { numberOfDiscardProfilerCalls++; return Future.value(null); } - - @override - Future fetchEngineReadyEndtime() { - return Future.value(10); - } } class MockRendererWrapper implements RendererWrapper { diff --git a/flutter/test/navigation/sentry_display_widget_test.dart b/flutter/test/navigation/sentry_display_widget_test.dart index fbfbfe44d1..092dc0ab65 100644 --- a/flutter/test/navigation/sentry_display_widget_test.dart +++ b/flutter/test/navigation/sentry_display_widget_test.dart @@ -61,8 +61,8 @@ void main() { AppStartType.cold, start: getUtcDateTime().add(Duration(seconds: 1)), end: getUtcDateTime().add(Duration(seconds: 2)), - engineEnd: getUtcDateTime().add(Duration(seconds: 3)), - dartLoadingEnd: getUtcDateTime().add(Duration(seconds: 4)), + pluginRegistration: getUtcDateTime().add(Duration(seconds: 3)), + mainIsolateStart: getUtcDateTime().add(Duration(seconds: 4)), ); NativeAppStartIntegration.setAppStartInfo(appStartInfo); diff --git a/flutter/test/sentry_native_test.dart b/flutter/test/sentry_native_test.dart index 68dc16fdfa..4e25eccfe1 100644 --- a/flutter/test/sentry_native_test.dart +++ b/flutter/test/sentry_native_test.dart @@ -16,7 +16,7 @@ void main() { }); test('fetchNativeAppStart sets didFetchAppStart', () async { - final nativeAppStart = NativeAppStart(0.0, true); + final nativeAppStart = NativeAppStart(appStartTime: 0.0, pluginRegistrationTime: 10, isColdStart: true); channel.nativeAppStart = nativeAppStart; expect(sut.didFetchAppStart, false); diff --git a/flutter/test/sentry_navigator_observer_test.dart b/flutter/test/sentry_navigator_observer_test.dart index f169daf4d4..94ce9e6107 100644 --- a/flutter/test/sentry_navigator_observer_test.dart +++ b/flutter/test/sentry_navigator_observer_test.dart @@ -501,8 +501,8 @@ void main() { AppStartType.cold, start: DateTime.now().add(const Duration(seconds: 1)), end: DateTime.now().add(const Duration(seconds: 2)), - engineEnd: DateTime.now().add(const Duration(seconds: 3)), - dartLoadingEnd: DateTime.now().add(const Duration(seconds: 4)), + pluginRegistration: DateTime.now().add(const Duration(seconds: 3)), + mainIsolateStart: DateTime.now().add(const Duration(seconds: 4)), ), ); From d28af71eedcdcd7a842df696613fa09e15401026 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 26 Apr 2024 22:36:56 +0200 Subject: [PATCH 14/26] dart format --- .../test/integrations/native_app_start_integration_test.dart | 4 ++-- flutter/test/sentry_native_test.dart | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/flutter/test/integrations/native_app_start_integration_test.dart b/flutter/test/integrations/native_app_start_integration_test.dart index fb1245a6ad..a97d64fa8b 100644 --- a/flutter/test/integrations/native_app_start_integration_test.dart +++ b/flutter/test/integrations/native_app_start_integration_test.dart @@ -173,8 +173,8 @@ void main() { test('have correct parents', () async { expect(coldStartSpan?.context.parentSpanId, tracer.context.spanId); - expect( - pluginRegistrationSpan?.context.parentSpanId, coldStartSpan?.context.spanId); + expect(pluginRegistrationSpan?.context.parentSpanId, + coldStartSpan?.context.spanId); expect(mainIsolateSetupSpan?.context.parentSpanId, coldStartSpan?.context.spanId); expect(firstFrameRenderSpan?.context.parentSpanId, diff --git a/flutter/test/sentry_native_test.dart b/flutter/test/sentry_native_test.dart index 4e25eccfe1..341c8aaad8 100644 --- a/flutter/test/sentry_native_test.dart +++ b/flutter/test/sentry_native_test.dart @@ -16,7 +16,8 @@ void main() { }); test('fetchNativeAppStart sets didFetchAppStart', () async { - final nativeAppStart = NativeAppStart(appStartTime: 0.0, pluginRegistrationTime: 10, isColdStart: true); + final nativeAppStart = NativeAppStart( + appStartTime: 0.0, pluginRegistrationTime: 10, isColdStart: true); channel.nativeAppStart = nativeAppStart; expect(sut.didFetchAppStart, false); From 21c61a63b406b48aaf47ed227f1e656897aa17c6 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 26 Apr 2024 22:38:21 +0200 Subject: [PATCH 15/26] Update comments --- flutter/ios/Classes/SentryFlutterPluginApple.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index f4cd0a0f59..af08d73a81 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -26,13 +26,11 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { #endif } - // Represents the time when the flutter engine starts to register plugins private static var pluginRegistrationTime: Int64 = 0 public static func register(with registrar: FlutterPluginRegistrar) { let currentDate = Date() let timeInterval = currentDate.timeIntervalSince1970 - engineReadyEndtime = Int64(timeInterval * 1000) pluginRegistrationTime = Int64(timeInterval * 1000) #if os(iOS) From 88a881905d2637d5ec6046f68ae1c09a8eb81a68 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 26 Apr 2024 22:41:09 +0200 Subject: [PATCH 16/26] Update --- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- flutter/example/ios/Runner/AppDelegate.swift | 1 - flutter/example/lib/main.dart | 2 +- flutter/ios/Classes/SentryFlutterPluginApple.swift | 8 +++----- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index fee8e19903..0fa7c24eb1 100644 --- a/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Bool { - print(Date().timeIntervalSince1970) GeneratedPluginRegistrant.register(with: self) guard let controller = window?.rootViewController as? FlutterViewController else { diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 9ff5e7baf7..86da143e31 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -83,7 +83,7 @@ Future setupSentry( // configuration issues, e.g. finding out why your events are not uploaded. options.debug = true; options.spotlight = Spotlight(enabled: true); - // options.enableTimeToFullDisplayTracing = true; + options.enableTimeToFullDisplayTracing = true; options.enableMetrics = true; options.maxRequestBodySize = MaxRequestBodySize.always; diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index af08d73a81..c40b94de2e 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -25,13 +25,11 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { return NSApplication.didBecomeActiveNotification #endif } - + private static var pluginRegistrationTime: Int64 = 0 public static func register(with registrar: FlutterPluginRegistrar) { - let currentDate = Date() - let timeInterval = currentDate.timeIntervalSince1970 - pluginRegistrationTime = Int64(timeInterval * 1000) + pluginRegistrationTime = Int64(Date().timeIntervalSince1970 * 1000) #if os(iOS) let channel = FlutterMethodChannel(name: "sentry_flutter", binaryMessenger: registrar.messenger()) @@ -388,7 +386,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { result(nil) return } - + let appStartTime = appStartMeasurement.appStartTimestamp.timeIntervalSince1970 * 1000 let isColdStart = appStartMeasurement.type == .cold From 0d73052b922f2bd1f69aacaf2057d053f601621f Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 26 Apr 2024 22:44:12 +0200 Subject: [PATCH 17/26] Update --- .../native_app_start_integration.dart | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/flutter/lib/src/integrations/native_app_start_integration.dart index da60f32ab2..5b4c572d27 100644 --- a/flutter/lib/src/integrations/native_app_start_integration.dart +++ b/flutter/lib/src/integrations/native_app_start_integration.dart @@ -71,20 +71,20 @@ class NativeAppStartIntegration extends Integration { // We only assign the current time if it's not already set - this is useful in tests // ignore: invalid_use_of_internal_member _native.appStartEnd ??= options.clock(); - final appStartEnd = _native.appStartEnd; + final appStartEndDateTime = _native.appStartEnd; final nativeAppStart = await _native.fetchNativeAppStart(); final pluginRegistrationTime = nativeAppStart?.pluginRegistrationTime; - final mainIsolateStartTime = SentryFlutter.mainIsolateStartTime; + final mainIsolateStartDateTime = SentryFlutter.mainIsolateStartTime; if (nativeAppStart == null || - appStartEnd == null || + appStartEndDateTime == null || pluginRegistrationTime == null) { return; } final appStartDateTime = DateTime.fromMillisecondsSinceEpoch( nativeAppStart.appStartTime.toInt()); - final duration = appStartEnd.difference(appStartDateTime); + final duration = appStartEndDateTime.difference(appStartDateTime); final pluginRegistrationDateTime = DateTime.fromMillisecondsSinceEpoch(pluginRegistrationTime); @@ -103,11 +103,10 @@ class NativeAppStartIntegration extends Integration { final appStartInfo = AppStartInfo( nativeAppStart.isColdStart ? AppStartType.cold : AppStartType.warm, - start: DateTime.fromMillisecondsSinceEpoch( - nativeAppStart.appStartTime.toInt()), - end: appStartEnd, + start: appStartDateTime, + end: appStartEndDateTime, pluginRegistration: pluginRegistrationDateTime, - mainIsolateStart: mainIsolateStartTime); + mainIsolateStart: mainIsolateStartDateTime); setAppStartInfo(appStartInfo); }); From 1e05ae5a9f97760cbb8af674649a5cbf1a115d08 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 26 Apr 2024 22:45:13 +0200 Subject: [PATCH 18/26] Update --- .../event_processor/native_app_start_event_processor.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flutter/lib/src/event_processor/native_app_start_event_processor.dart b/flutter/lib/src/event_processor/native_app_start_event_processor.dart index 6b2ef82338..faf0b678e0 100644 --- a/flutter/lib/src/event_processor/native_app_start_event_processor.dart +++ b/flutter/lib/src/event_processor/native_app_start_event_processor.dart @@ -59,7 +59,7 @@ class NativeAppStartEventProcessor implements EventProcessor { final pluginRegistrationSpan = await _createAndFinishSpan( tracer: transaction, operation: op, - description: AppStartSpanDescriptions.pluginRegistration, + description: _AppStartSpanDescriptions.pluginRegistration, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, startTimestamp: appStartInfo.start, @@ -68,7 +68,7 @@ class NativeAppStartEventProcessor implements EventProcessor { final mainIsolateSetupSpan = await _createAndFinishSpan( tracer: transaction, operation: op, - description: AppStartSpanDescriptions.mainIsolateSetup, + description: _AppStartSpanDescriptions.mainIsolateSetup, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, startTimestamp: appStartInfo.pluginRegistration, @@ -77,7 +77,7 @@ class NativeAppStartEventProcessor implements EventProcessor { final firstFrameRenderSpan = await _createAndFinishSpan( tracer: transaction, operation: op, - description: AppStartSpanDescriptions.firstFrameRender, + description: _AppStartSpanDescriptions.firstFrameRender, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, startTimestamp: SentryFlutter.mainIsolateStartTime, @@ -121,7 +121,7 @@ extension _StringExtension on String { } } -class AppStartSpanDescriptions { +class _AppStartSpanDescriptions { static const String pluginRegistration = 'App start to plugin registration'; static const String mainIsolateSetup = 'Main isolate setup'; static const String firstFrameRender = 'First frame render'; From 73d830aa7a79414e109832235795367741750573 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 26 Apr 2024 22:46:43 +0200 Subject: [PATCH 19/26] Update --- .../event_processor/native_app_start_event_processor.dart | 3 +-- flutter/lib/src/native/sentry_native.dart | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/flutter/lib/src/event_processor/native_app_start_event_processor.dart b/flutter/lib/src/event_processor/native_app_start_event_processor.dart index faf0b678e0..caffdd8dcf 100644 --- a/flutter/lib/src/event_processor/native_app_start_event_processor.dart +++ b/flutter/lib/src/event_processor/native_app_start_event_processor.dart @@ -34,9 +34,8 @@ class NativeAppStartEventProcessor implements EventProcessor { _native.didAddAppStartMeasurement = true; } - final transaction = event.tracer; if (appStartInfo != null) { - await _attachAppStartSpans(appStartInfo, transaction); + await _attachAppStartSpans(appStartInfo, event.tracer); } return event; diff --git a/flutter/lib/src/native/sentry_native.dart b/flutter/lib/src/native/sentry_native.dart index b6711d2446..4d26239a60 100644 --- a/flutter/lib/src/native/sentry_native.dart +++ b/flutter/lib/src/native/sentry_native.dart @@ -130,18 +130,18 @@ class SentryNative { class NativeAppStart { NativeAppStart( - {required this.pluginRegistrationTime, - required this.appStartTime, + {required this.appStartTime, + required this.pluginRegistrationTime, required this.isColdStart}); - int pluginRegistrationTime; double appStartTime; + int pluginRegistrationTime; bool isColdStart; factory NativeAppStart.fromJson(Map json) { return NativeAppStart( - pluginRegistrationTime: json['pluginRegistrationTime'] as int, appStartTime: json['appStartTime'] as double, + pluginRegistrationTime: json['pluginRegistrationTime'] as int, isColdStart: json['isColdStart'] as bool, ); } From 62cb2be89af6dd4f8a7d3b0b69262a6519fd2917 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 26 Apr 2024 22:53:51 +0200 Subject: [PATCH 20/26] Update --- flutter/lib/src/sentry_flutter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 7fe18fec2b..1fd1931a8e 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -34,7 +34,7 @@ typedef FlutterOptionsConfiguration = FutureOr Function( mixin SentryFlutter { static const _channel = MethodChannel('sentry_flutter'); - /// Represents the time when the dart isolate stopped loading and is ready to execute. + /// Represents the time when the main isolate is set up and ready to execute. @internal // ignore: invalid_use_of_internal_member static DateTime mainIsolateStartTime = getUtcDateTime(); From 111125c555074e8114d00123e419633f1f52c8c5 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 29 Apr 2024 10:30:56 +0200 Subject: [PATCH 21/26] Fix tests --- .../Classes/SentryFlutterPluginApple.swift | 2 +- .../native_app_start_event_processor.dart | 30 +++++-------------- .../native_app_start_integration.dart | 8 +++++ .../native_app_start_integration_test.dart | 22 +++++++++----- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index c40b94de2e..97a10c3324 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -391,7 +391,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { let isColdStart = appStartMeasurement.type == .cold let item: [String: Any] = [ - "pluginRegistrationTime" : SentryFlutterPluginApple.pluginRegistrationTime, + "pluginRegistrationTime": SentryFlutterPluginApple.pluginRegistrationTime, "appStartTime": appStartTime, "isColdStart": isColdStart ] diff --git a/flutter/lib/src/event_processor/native_app_start_event_processor.dart b/flutter/lib/src/event_processor/native_app_start_event_processor.dart index caffdd8dcf..baba4b6e47 100644 --- a/flutter/lib/src/event_processor/native_app_start_event_processor.dart +++ b/flutter/lib/src/event_processor/native_app_start_event_processor.dart @@ -43,13 +43,12 @@ class NativeAppStartEventProcessor implements EventProcessor { Future _attachAppStartSpans( AppStartInfo appStartInfo, SentryTracer transaction) async { - final op = 'app.start.${appStartInfo.type.name}'; final transactionTraceId = transaction.context.traceId; final appStartSpan = await _createAndFinishSpan( tracer: transaction, - operation: op, - description: '${appStartInfo.type.name.capitalize()} start', + operation: appStartInfo.appStartTypeOperation, + description: appStartInfo.appStartTypeDescription, parentSpanId: transaction.context.spanId, traceId: transactionTraceId, startTimestamp: appStartInfo.start, @@ -57,8 +56,8 @@ class NativeAppStartEventProcessor implements EventProcessor { final pluginRegistrationSpan = await _createAndFinishSpan( tracer: transaction, - operation: op, - description: _AppStartSpanDescriptions.pluginRegistration, + operation: appStartInfo.appStartTypeOperation, + description: appStartInfo.pluginRegistrationDescription, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, startTimestamp: appStartInfo.start, @@ -66,8 +65,8 @@ class NativeAppStartEventProcessor implements EventProcessor { final mainIsolateSetupSpan = await _createAndFinishSpan( tracer: transaction, - operation: op, - description: _AppStartSpanDescriptions.mainIsolateSetup, + operation: appStartInfo.appStartTypeOperation, + description: appStartInfo.mainIsolateSetupDescription, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, startTimestamp: appStartInfo.pluginRegistration, @@ -75,8 +74,8 @@ class NativeAppStartEventProcessor implements EventProcessor { final firstFrameRenderSpan = await _createAndFinishSpan( tracer: transaction, - operation: op, - description: _AppStartSpanDescriptions.firstFrameRender, + operation: appStartInfo.appStartTypeOperation, + description: appStartInfo.firstFrameRenderDescription, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, startTimestamp: SentryFlutter.mainIsolateStartTime, @@ -113,16 +112,3 @@ class NativeAppStartEventProcessor implements EventProcessor { return span; } } - -extension _StringExtension on String { - String capitalize() { - return "${this[0].toUpperCase()}${substring(1).toLowerCase()}"; - } -} - -class _AppStartSpanDescriptions { - static const String pluginRegistration = 'App start to plugin registration'; - static const String mainIsolateSetup = 'Main isolate setup'; - static const String firstFrameRender = 'First frame render'; - // TODO: Add iOS and Android specific descriptions when implementing native spans -} diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/flutter/lib/src/integrations/native_app_start_integration.dart index 5b4c572d27..923d1a1bfa 100644 --- a/flutter/lib/src/integrations/native_app_start_integration.dart +++ b/flutter/lib/src/integrations/native_app_start_integration.dart @@ -140,4 +140,12 @@ class AppStartInfo { ? SentryMeasurement.coldAppStart(duration) : SentryMeasurement.warmAppStart(duration); } + + String get appStartTypeOperation => 'app.start.${type.name}'; + + String get appStartTypeDescription => + type == AppStartType.cold ? 'Cold start' : 'Warm start'; + final pluginRegistrationDescription = 'App start to plugin registration'; + final mainIsolateSetupDescription = 'Main isolate setup'; + final firstFrameRenderDescription = 'First frame render'; } diff --git a/flutter/test/integrations/native_app_start_integration_test.dart b/flutter/test/integrations/native_app_start_integration_test.dart index a97d64fa8b..6f46bd7870 100644 --- a/flutter/test/integrations/native_app_start_integration_test.dart +++ b/flutter/test/integrations/native_app_start_integration_test.dart @@ -3,6 +3,7 @@ import 'package:collection/collection.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/event_processor/native_app_start_event_processor.dart'; import 'package:sentry_flutter/src/integrations/native_app_start_integration.dart'; import 'package:sentry_flutter/src/native/sentry_native.dart'; import 'package:sentry/src/sentry_tracer.dart'; @@ -146,14 +147,19 @@ void main() { final enriched = await processor.apply(transaction, Hint()) as SentryTransaction; - coldStartSpan = enriched.spans.firstWhereOrNull( - (element) => element.context.description == 'Cold start'); - pluginRegistrationSpan = enriched.spans.firstWhereOrNull( - (element) => element.context.description == 'Engine init and ready'); - mainIsolateSetupSpan = enriched.spans.firstWhereOrNull( - (element) => element.context.description == 'Dart isolate loading'); - firstFrameRenderSpan = enriched.spans.firstWhereOrNull( - (element) => element.context.description == 'Initial frame render'); + final appStartInfo = await NativeAppStartIntegration.getAppStartInfo(); + + coldStartSpan = enriched.spans.firstWhereOrNull((element) => + element.context.description == appStartInfo?.appStartTypeDescription); + pluginRegistrationSpan = enriched.spans.firstWhereOrNull((element) => + element.context.description == + appStartInfo?.pluginRegistrationDescription); + mainIsolateSetupSpan = enriched.spans.firstWhereOrNull((element) => + element.context.description == + appStartInfo?.mainIsolateSetupDescription); + firstFrameRenderSpan = enriched.spans.firstWhereOrNull((element) => + element.context.description == + appStartInfo?.firstFrameRenderDescription); }); test('are added by event processor', () async { From 04ad0234137d49408ca8989a120ee70207ad59d2 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 29 Apr 2024 10:37:09 +0200 Subject: [PATCH 22/26] Fix test --- flutter/test/sentry_native_channel_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/flutter/test/sentry_native_channel_test.dart b/flutter/test/sentry_native_channel_test.dart index 04ec723feb..4d5e539838 100644 --- a/flutter/test/sentry_native_channel_test.dart +++ b/flutter/test/sentry_native_channel_test.dart @@ -20,6 +20,7 @@ void main() { test('fetchNativeAppStart', () async { final map = { + 'pluginRegistrationTime': 1, 'appStartTime': 0.1, 'isColdStart': true, }; From e9e95c468a7c73b5457e4812a5af7cded14e2af1 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 29 Apr 2024 10:38:24 +0200 Subject: [PATCH 23/26] Add unused import --- flutter/test/integrations/native_app_start_integration_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/flutter/test/integrations/native_app_start_integration_test.dart b/flutter/test/integrations/native_app_start_integration_test.dart index 6f46bd7870..a6b712af12 100644 --- a/flutter/test/integrations/native_app_start_integration_test.dart +++ b/flutter/test/integrations/native_app_start_integration_test.dart @@ -3,7 +3,6 @@ import 'package:collection/collection.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:sentry_flutter/src/event_processor/native_app_start_event_processor.dart'; import 'package:sentry_flutter/src/integrations/native_app_start_integration.dart'; import 'package:sentry_flutter/src/native/sentry_native.dart'; import 'package:sentry/src/sentry_tracer.dart'; From 2683aa9f8c334dc4b9fd620497dcfccadbdc7fe6 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 3 May 2024 17:37:20 +0200 Subject: [PATCH 24/26] Review improvements --- .../event_processor/native_app_start_event_processor.dart | 6 +++--- flutter/lib/src/sentry_flutter.dart | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/flutter/lib/src/event_processor/native_app_start_event_processor.dart b/flutter/lib/src/event_processor/native_app_start_event_processor.dart index baba4b6e47..d22226f605 100644 --- a/flutter/lib/src/event_processor/native_app_start_event_processor.dart +++ b/flutter/lib/src/event_processor/native_app_start_event_processor.dart @@ -78,7 +78,7 @@ class NativeAppStartEventProcessor implements EventProcessor { description: appStartInfo.firstFrameRenderDescription, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, - startTimestamp: SentryFlutter.mainIsolateStartTime, + startTimestamp: appStartInfo.mainIsolateStart, endTimestamp: appStartInfo.end); transaction.children.addAll([ @@ -93,8 +93,8 @@ class NativeAppStartEventProcessor implements EventProcessor { required SentryTracer tracer, required String operation, required String description, - required SpanId? parentSpanId, - required SentryId? traceId, + required SpanId parentSpanId, + required SentryId traceId, required DateTime startTimestamp, required DateTime endTimestamp, }) async { diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 1fd1931a8e..60c9811bf0 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -48,9 +48,6 @@ mixin SentryFlutter { }) async { final flutterOptions = SentryFlutterOptions(); - // ignore: invalid_use_of_internal_member - mainIsolateStartTime = flutterOptions.clock(); - if (platformChecker != null) { flutterOptions.platformChecker = platformChecker; } From 9f26694a0ae127b3b3f62e84b458389ec21ddf56 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 3 May 2024 17:57:49 +0200 Subject: [PATCH 25/26] Update CHANGELOG --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abc256867e..2e0e34b9be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Changelog +## Unreleased + +- Adds app start spans to first transaction ([#2009](https://github.com/getsentry/sentry-dart/pull/2009)) + ## 8.1.0 ### Features -- Adds app start spans to first transaction ([#2009](https://github.com/getsentry/sentry-dart/pull/2009)) - Set snapshot to `true` if stacktrace is not provided ([#2000](https://github.com/getsentry/sentry-dart/pull/2000)) - If the stacktrace is not provided, the Sentry SDK will fetch the current stacktrace via `StackTrace.current` and the snapshot will be set to `true` - **this may change the grouping behavior** - `snapshot = true` means it's a synthetic exception, reflecting the current state of the thread rather than the stack trace of a real exception From c5768ab38a86dbafae6185950cd0ee038209315b Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 3 May 2024 17:58:03 +0200 Subject: [PATCH 26/26] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e0e34b9be..c545ea3e39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +### Features + - Adds app start spans to first transaction ([#2009](https://github.com/getsentry/sentry-dart/pull/2009)) ## 8.1.0