diff --git a/CHANGELOG.md b/CHANGELOG.md index dbfcbdd00a..3de2f68fae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Features +- Adds native spans to app start transaction ([#2027](https://github.com/getsentry/sentry-dart/pull/2027)) - Adds app start spans to first transaction ([#2009](https://github.com/getsentry/sentry-dart/pull/2009)) ### Fixes 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 8d92c9178c..424e51c6b7 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -23,6 +23,7 @@ import io.sentry.android.core.LoadClass import io.sentry.android.core.SentryAndroid import io.sentry.android.core.SentryAndroidOptions import io.sentry.android.core.performance.AppStartMetrics +import io.sentry.android.core.performance.TimeSpan import io.sentry.protocol.DebugImage import io.sentry.protocol.SdkVersion import io.sentry.protocol.SentryId @@ -130,24 +131,66 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { return } - val appStartTime = AppStartMetrics.getInstance().appStartTimeSpan.startTimestamp - val isColdStart = - AppStartMetrics.getInstance().appStartType == AppStartMetrics.AppStartType.COLD + val appStartMetrics = AppStartMetrics.getInstance() + + val appStartTimeSpan = appStartMetrics.appStartTimeSpan + val appStartTime = appStartTimeSpan.startTimestamp + val isColdStart = appStartMetrics.appStartType == AppStartMetrics.AppStartType.COLD if (appStartTime == null) { Log.w("Sentry", "App start won't be sent due to missing appStartTime") result.success(null) } else { val appStartTimeMillis = DateUtils.nanosToMillis(appStartTime.nanoTimestamp().toDouble()) - val item = mapOf( - "pluginRegistrationTime" to pluginRegistrationTime, - "appStartTime" to appStartTimeMillis, - "isColdStart" to isColdStart, - ) + val item = + mutableMapOf( + "pluginRegistrationTime" to pluginRegistrationTime, + "appStartTime" to appStartTimeMillis, + "isColdStart" to isColdStart, + ) + + val androidNativeSpans = mutableMapOf() + + val processInitSpan = + TimeSpan().apply { + description = "Process Initialization" + setStartUnixTimeMs(appStartTimeSpan.startTimestampMs) + setStartedAt(appStartTimeSpan.startUptimeMs) + setStoppedAt(appStartMetrics.classLoadedUptimeMs) + } + processInitSpan.addToMap(androidNativeSpans) + + val applicationOnCreateSpan = appStartMetrics.applicationOnCreateTimeSpan + applicationOnCreateSpan.addToMap(androidNativeSpans) + + val contentProviderSpans = appStartMetrics.contentProviderOnCreateTimeSpans + contentProviderSpans.forEach { span -> + span.addToMap(androidNativeSpans) + } + + appStartMetrics.activityLifecycleTimeSpans.forEach { span -> + span.onCreate.addToMap(androidNativeSpans) + span.onStart.addToMap(androidNativeSpans) + } + + item["nativeSpanTimes"] = androidNativeSpans + result.success(item) } } + private fun TimeSpan.addToMap(map: MutableMap) { + if (startTimestamp == null) return + + description?.let { description -> + map[description] = + mapOf( + "startTimestampMsSinceEpoch" to startTimestampMs, + "stopTimestampMsSinceEpoch" to projectedStopTimestampMs, + ) + } + } + private fun beginNativeFrames(result: Result) { if (!sentryFlutter.autoPerformanceTracingEnabled) { result.success(null) diff --git a/flutter/example/android/build.gradle b/flutter/example/android/build.gradle index 50bb21eda7..693111bbb3 100644 --- a/flutter/example/android/build.gradle +++ b/flutter/example/android/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'io.sentry:sentry-android-gradle-plugin:4.2.0' + classpath 'io.sentry:sentry-android-gradle-plugin:4.5.0' classpath 'com.android.tools.build:gradle:7.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'io.github.howardpang:androidNativeBundle:1.1.3' @@ -32,4 +32,3 @@ subprojects { tasks.register("clean", Delete) { delete rootProject.buildDir } - 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 @@ Int64 { + return Int64(self * 1000) + } +} 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 57e2eb4ed6..f8ea23b265 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 @@ -42,7 +42,6 @@ class NativeAppStartEventProcessor implements EventProcessor { } final measurement = appStartInfo?.toMeasurement(); - if (measurement != null) { event.measurements[measurement.name] = measurement; _native.didAddAppStartMeasurement = true; @@ -72,6 +71,8 @@ class NativeAppStartEventProcessor implements EventProcessor { startTimestamp: appStartInfo.start, endTimestamp: appStartEnd); + await _attachNativeSpans(appStartInfo, transaction, appStartSpan); + final pluginRegistrationSpan = await _createAndFinishSpan( tracer: transaction, operation: appStartInfo.appStartTypeOperation, @@ -81,14 +82,14 @@ class NativeAppStartEventProcessor implements EventProcessor { startTimestamp: appStartInfo.start, endTimestamp: appStartInfo.pluginRegistration); - final mainIsolateSetupSpan = await _createAndFinishSpan( + final sentrySetupSpan = await _createAndFinishSpan( tracer: transaction, operation: appStartInfo.appStartTypeOperation, - description: appStartInfo.mainIsolateSetupDescription, + description: appStartInfo.sentrySetupDescription, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, startTimestamp: appStartInfo.pluginRegistration, - endTimestamp: appStartInfo.mainIsolateStart); + endTimestamp: appStartInfo.sentrySetupStart); final firstFrameRenderSpan = await _createAndFinishSpan( tracer: transaction, @@ -96,17 +97,39 @@ class NativeAppStartEventProcessor implements EventProcessor { description: appStartInfo.firstFrameRenderDescription, parentSpanId: appStartSpan.context.spanId, traceId: transactionTraceId, - startTimestamp: appStartInfo.mainIsolateStart, + startTimestamp: appStartInfo.sentrySetupStart, endTimestamp: appStartEnd); transaction.children.addAll([ appStartSpan, pluginRegistrationSpan, - mainIsolateSetupSpan, + sentrySetupSpan, firstFrameRenderSpan ]); } + Future _attachNativeSpans(AppStartInfo appStartInfo, + SentryTracer transaction, SentrySpan parent) async { + await Future.forEach(appStartInfo.nativeSpanTimes, + (timeSpan) async { + try { + final span = await _createAndFinishSpan( + tracer: transaction, + operation: appStartInfo.appStartTypeOperation, + description: timeSpan.description, + parentSpanId: parent.context.spanId, + traceId: transaction.context.traceId, + startTimestamp: timeSpan.start, + endTimestamp: timeSpan.end); + span.data.putIfAbsent('native', () => true); + transaction.children.add(span); + } catch (e) { + _hub.options.logger(SentryLevel.warning, + 'Failed to attach native span to app start transaction: $e'); + } + }); + } + Future _createAndFinishSpan({ required SentryTracer tracer, required String operation, diff --git a/flutter/lib/src/integrations/native_app_start_integration.dart b/flutter/lib/src/integrations/native_app_start_integration.dart index 7b015220a9..13ca865422 100644 --- a/flutter/lib/src/integrations/native_app_start_integration.dart +++ b/flutter/lib/src/integrations/native_app_start_integration.dart @@ -10,10 +10,13 @@ import '../event_processor/native_app_start_event_processor.dart'; /// Integration which handles communication with native frameworks in order to /// enrich [SentryTransaction] objects with app start data for mobile vitals. class NativeAppStartIntegration extends Integration { - NativeAppStartIntegration(this._native, this._frameCallbackHandler); + NativeAppStartIntegration(this._native, this._frameCallbackHandler, + {Hub? hub}) + : _hub = hub ?? HubAdapter(); final SentryNative _native; final FrameCallbackHandler _frameCallbackHandler; + final Hub _hub; /// Timeout duration to wait for the app start info to be fetched. static const _timeoutDuration = Duration(seconds: 30); @@ -55,13 +58,15 @@ class NativeAppStartIntegration extends Integration { @override void call(Hub hub, SentryFlutterOptions options) { if (isIntegrationTest) { - final appStartInfo = AppStartInfo(AppStartType.cold, - start: DateTime.now(), - end: DateTime.now().add(const Duration(milliseconds: 100)), - pluginRegistration: - DateTime.now().add(const Duration(milliseconds: 50)), - mainIsolateStart: - DateTime.now().add(const Duration(milliseconds: 60))); + final appStartInfo = AppStartInfo( + AppStartType.cold, + start: DateTime.now(), + end: DateTime.now().add(const Duration(milliseconds: 100)), + pluginRegistration: + DateTime.now().add(const Duration(milliseconds: 50)), + sentrySetupStart: DateTime.now().add(const Duration(milliseconds: 60)), + nativeSpanTimes: [], + ); setAppStartInfo(appStartInfo); return; } @@ -77,7 +82,12 @@ class NativeAppStartIntegration extends Integration { return; } - final mainIsolateStartDateTime = SentryFlutter.mainIsolateStartTime; + final sentrySetupStartDateTime = SentryFlutter.sentrySetupStartTime; + if (sentrySetupStartDateTime == null) { + setAppStartInfo(null); + return; + } + final appStartDateTime = DateTime.fromMillisecondsSinceEpoch( nativeAppStart.appStartTime.toInt()); final pluginRegistrationDateTime = DateTime.fromMillisecondsSinceEpoch( @@ -106,12 +116,37 @@ class NativeAppStartIntegration extends Integration { } } + List nativeSpanTimes = []; + for (final entry in nativeAppStart.nativeSpanTimes.entries) { + try { + final startTimestampMs = + entry.value['startTimestampMsSinceEpoch'] as int; + final endTimestampMs = + entry.value['stopTimestampMsSinceEpoch'] as int; + nativeSpanTimes.add(TimeSpan( + start: DateTime.fromMillisecondsSinceEpoch(startTimestampMs), + end: DateTime.fromMillisecondsSinceEpoch(endTimestampMs), + description: entry.key as String, + )); + } catch (e) { + // ignore: invalid_use_of_internal_member + _hub.options.logger( + SentryLevel.warning, 'Failed to parse native span times: $e'); + continue; + } + } + + // We want to sort because the native spans are not guaranteed to be in order. + // Performance wise this won't affect us since the native span amount is very low. + nativeSpanTimes.sort((a, b) => a.start.compareTo(b.start)); + final appStartInfo = AppStartInfo( nativeAppStart.isColdStart ? AppStartType.cold : AppStartType.warm, start: appStartDateTime, end: appStartEndDateTime, pluginRegistration: pluginRegistrationDateTime, - mainIsolateStart: mainIsolateStartDateTime); + sentrySetupStart: sentrySetupStartDateTime, + nativeSpanTimes: nativeSpanTimes); setAppStartInfo(appStartInfo); }); @@ -129,19 +164,21 @@ class AppStartInfo { this.type, { required this.start, required this.pluginRegistration, - required this.mainIsolateStart, + required this.sentrySetupStart, + required this.nativeSpanTimes, this.end, }); final AppStartType type; final DateTime start; + final List nativeSpanTimes; // We allow the end to be null, since it might be set at a later time // with setAppStartEnd when autoAppStart is disabled DateTime? end; final DateTime pluginRegistration; - final DateTime mainIsolateStart; + final DateTime sentrySetupStart; Duration? get duration => end?.difference(start); @@ -160,6 +197,14 @@ class AppStartInfo { String get appStartTypeDescription => type == AppStartType.cold ? 'Cold start' : 'Warm start'; final pluginRegistrationDescription = 'App start to plugin registration'; - final mainIsolateSetupDescription = 'Main isolate setup'; + final sentrySetupDescription = 'Before Sentry Init Setup'; final firstFrameRenderDescription = 'First frame render'; } + +class TimeSpan { + TimeSpan({required this.start, required this.end, required this.description}); + + final DateTime start; + final DateTime end; + final String description; +} diff --git a/flutter/lib/src/native/sentry_native.dart b/flutter/lib/src/native/sentry_native.dart index 893c55d6bc..d8d9c5a964 100644 --- a/flutter/lib/src/native/sentry_native.dart +++ b/flutter/lib/src/native/sentry_native.dart @@ -137,17 +137,20 @@ class NativeAppStart { NativeAppStart( {required this.appStartTime, required this.pluginRegistrationTime, - required this.isColdStart}); + required this.isColdStart, + required this.nativeSpanTimes}); double appStartTime; int pluginRegistrationTime; bool isColdStart; + Map nativeSpanTimes; factory NativeAppStart.fromJson(Map json) { return NativeAppStart( appStartTime: json['appStartTime'] as double, pluginRegistrationTime: json['pluginRegistrationTime'] as int, isColdStart: json['isColdStart'] as bool, + nativeSpanTimes: json['nativeSpanTimes'] as Map, ); } } diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 9b0792c7e3..e0d4415de5 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -34,10 +34,10 @@ typedef FlutterOptionsConfiguration = FutureOr Function( mixin SentryFlutter { static const _channel = MethodChannel('sentry_flutter'); - /// Represents the time when the main isolate is set up and ready to execute. + /// Represents the time when the Sentry init set up has started. @internal // ignore: invalid_use_of_internal_member - static DateTime mainIsolateStartTime = getUtcDateTime(); + static DateTime? sentrySetupStartTime; static Future init( FlutterOptionsConfiguration optionsConfiguration, { @@ -48,6 +48,9 @@ mixin SentryFlutter { }) async { final flutterOptions = SentryFlutterOptions(); + // ignore: invalid_use_of_internal_member + sentrySetupStartTime ??= 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 0ebc3cb4c2..ce555ceb9d 100644 --- a/flutter/test/integrations/native_app_start_integration_test.dart +++ b/flutter/test/integrations/native_app_start_integration_test.dart @@ -25,7 +25,10 @@ void main() { test('native app start measurement added to first transaction', () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10); fixture.binding.nativeAppStart = NativeAppStart( - appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); + appStartTime: 0, + pluginRegistrationTime: 10, + isColdStart: true, + nativeSpanTimes: {}); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -45,7 +48,10 @@ void main() { () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10); fixture.binding.nativeAppStart = NativeAppStart( - appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); + appStartTime: 0, + pluginRegistrationTime: 10, + isColdStart: true, + nativeSpanTimes: {}); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -65,7 +71,10 @@ void main() { test('measurements appended', () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10); fixture.binding.nativeAppStart = NativeAppStart( - appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); + appStartTime: 0, + pluginRegistrationTime: 10, + isColdStart: true, + nativeSpanTimes: {}); final measurement = SentryMeasurement.warmAppStart(Duration(seconds: 1)); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -88,7 +97,10 @@ void main() { test('native app start measurement not added if more than 60s', () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(60001); fixture.binding.nativeAppStart = NativeAppStart( - appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); + appStartTime: 0, + pluginRegistrationTime: 10, + isColdStart: true, + nativeSpanTimes: {}); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -106,7 +118,10 @@ void main() { () async { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10); fixture.binding.nativeAppStart = NativeAppStart( - appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); + appStartTime: 0, + pluginRegistrationTime: 10, + isColdStart: true, + nativeSpanTimes: {}); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -120,7 +135,10 @@ void main() { () async { fixture.options.autoAppStart = false; fixture.binding.nativeAppStart = NativeAppStart( - appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); + appStartTime: 0, + pluginRegistrationTime: 10, + isColdStart: true, + nativeSpanTimes: {}); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -140,7 +158,10 @@ void main() { () async { fixture.options.autoAppStart = false; fixture.binding.nativeAppStart = NativeAppStart( - appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); + appStartTime: 0, + pluginRegistrationTime: 10, + isColdStart: true, + nativeSpanTimes: {}); SentryFlutter.native = fixture.native; fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -166,16 +187,15 @@ void main() { (element) => element.context.description == appStartInfo!.pluginRegistrationDescription); - final mainIsolateSetupSpan = enriched.spans.firstWhereOrNull((element) => - element.context.description == - appStartInfo!.mainIsolateSetupDescription); + final sentrySetupSpan = enriched.spans.firstWhereOrNull((element) => + element.context.description == appStartInfo!.sentrySetupDescription); final firstFrameRenderSpan = enriched.spans.firstWhereOrNull((element) => element.context.description == appStartInfo!.firstFrameRenderDescription); expect(appStartSpan, isNotNull); expect(pluginRegistrationSpan, isNotNull); - expect(mainIsolateSetupSpan, isNotNull); + expect(sentrySetupSpan, isNotNull); expect(firstFrameRenderSpan, isNotNull); }); }); @@ -183,11 +203,43 @@ void main() { group('App start spans', () { late SentrySpan? coldStartSpan, pluginRegistrationSpan, - mainIsolateSetupSpan, + sentrySetupSpan, firstFrameRenderSpan; // ignore: invalid_use_of_internal_member late SentryTracer tracer; late Fixture fixture; + late SentryTransaction enriched; + + final validNativeSpanTimes = { + 'correct span description': { + 'startTimestampMsSinceEpoch': 1, + 'stopTimestampMsSinceEpoch': 2, + }, + 'correct span description 2': { + 'startTimestampMsSinceEpoch': 4, + 'stopTimestampMsSinceEpoch': 6, + }, + 'correct span description 3': { + 'startTimestampMsSinceEpoch': 3, + 'stopTimestampMsSinceEpoch': 4, + }, + }; + + final invalidNativeSpanTimes = { + 'failing span with null timestamp': { + 'startTimestampMsSinceEpoch': null, + 'stopTimestampMsSinceEpoch': 3, + }, + 'failing span with string timestamp': { + 'startTimestampMsSinceEpoch': '1', + 'stopTimestampMsSinceEpoch': 3, + }, + }; + + final allNativeSpanTimes = { + ...validNativeSpanTimes, + ...invalidNativeSpanTimes, + }; setUp(() async { TestWidgetsFlutterBinding.ensureInitialized(); @@ -197,9 +249,12 @@ void main() { fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(50); fixture.binding.nativeAppStart = NativeAppStart( - appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true); + appStartTime: 0, + pluginRegistrationTime: 10, + isColdStart: true, + nativeSpanTimes: allNativeSpanTimes); // dartLoadingEnd needs to be set after engine end (see MockNativeChannel) - SentryFlutter.mainIsolateStartTime = + SentryFlutter.sentrySetupStartTime = DateTime.fromMillisecondsSinceEpoch(15); fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options); @@ -207,7 +262,7 @@ void main() { final processor = fixture.options.eventProcessors.first; tracer = fixture.createTracer(); final transaction = SentryTransaction(tracer); - final enriched = + enriched = await processor.apply(transaction, Hint()) as SentryTransaction; final appStartInfo = await NativeAppStartIntegration.getAppStartInfo(); @@ -217,18 +272,68 @@ void main() { pluginRegistrationSpan = enriched.spans.firstWhereOrNull((element) => element.context.description == appStartInfo?.pluginRegistrationDescription); - mainIsolateSetupSpan = enriched.spans.firstWhereOrNull((element) => - element.context.description == - appStartInfo?.mainIsolateSetupDescription); + sentrySetupSpan = enriched.spans.firstWhereOrNull((element) => + element.context.description == appStartInfo?.sentrySetupDescription); firstFrameRenderSpan = enriched.spans.firstWhereOrNull((element) => element.context.description == appStartInfo?.firstFrameRenderDescription); }); + test('native app start spans not added to following transactions', + () async { + final processor = fixture.options.eventProcessors.first; + + final transaction = SentryTransaction(fixture.createTracer()); + + final secondEnriched = + await processor.apply(transaction, Hint()) as SentryTransaction; + + expect(secondEnriched.spans.length, 0); + }); + + test('includes only valid native spans', () async { + final spans = + enriched.spans.where((element) => element.data['native'] == true); + + expect(spans.length, validNativeSpanTimes.length); + + for (final span in spans) { + final validSpan = validNativeSpanTimes[span.context.description]; + expect(validSpan, isNotNull); + expect( + span.startTimestamp, + DateTime.fromMillisecondsSinceEpoch( + validSpan!['startTimestampMsSinceEpoch']!) + .toUtc()); + expect( + span.endTimestamp, + DateTime.fromMillisecondsSinceEpoch( + validSpan['stopTimestampMsSinceEpoch']!) + .toUtc()); + } + }); + + test('are correctly ordered', () async { + final spans = + enriched.spans.where((element) => element.data['native'] == true); + + final orderedSpans = spans.toList() + ..sort((a, b) => a.startTimestamp.compareTo(b.startTimestamp)); + + expect(spans, orderedEquals(orderedSpans)); + }); + + test('ignores invalid spans', () async { + final spans = + enriched.spans.where((element) => element.data['native'] == true); + + expect(spans, isNot(contains('failing span'))); + }); + test('are added by event processor', () async { expect(coldStartSpan, isNotNull); expect(pluginRegistrationSpan, isNotNull); - expect(mainIsolateSetupSpan, isNotNull); + expect(sentrySetupSpan, isNotNull); expect(firstFrameRenderSpan, isNotNull); }); @@ -236,7 +341,7 @@ void main() { const op = 'app.start.cold'; expect(coldStartSpan?.context.operation, op); expect(pluginRegistrationSpan?.context.operation, op); - expect(mainIsolateSetupSpan?.context.operation, op); + expect(sentrySetupSpan?.context.operation, op); expect(firstFrameRenderSpan?.context.operation, op); }); @@ -244,8 +349,8 @@ void main() { expect(coldStartSpan?.context.parentSpanId, tracer.context.spanId); expect(pluginRegistrationSpan?.context.parentSpanId, coldStartSpan?.context.spanId); - expect(mainIsolateSetupSpan?.context.parentSpanId, - coldStartSpan?.context.spanId); + expect( + sentrySetupSpan?.context.parentSpanId, coldStartSpan?.context.spanId); expect(firstFrameRenderSpan?.context.parentSpanId, coldStartSpan?.context.spanId); }); @@ -254,7 +359,7 @@ void main() { final traceId = tracer.context.traceId; expect(coldStartSpan?.context.traceId, traceId); expect(pluginRegistrationSpan?.context.traceId, traceId); - expect(mainIsolateSetupSpan?.context.traceId, traceId); + expect(sentrySetupSpan?.context.traceId, traceId); expect(firstFrameRenderSpan?.context.traceId, traceId); }); @@ -264,10 +369,10 @@ void main() { .toUtc(); expect(coldStartSpan?.startTimestamp, appStartTime); expect(pluginRegistrationSpan?.startTimestamp, appStartTime); - expect(mainIsolateSetupSpan?.startTimestamp, + expect(sentrySetupSpan?.startTimestamp, pluginRegistrationSpan?.endTimestamp); - expect(firstFrameRenderSpan?.startTimestamp, - mainIsolateSetupSpan?.endTimestamp); + expect( + firstFrameRenderSpan?.startTimestamp, sentrySetupSpan?.endTimestamp); }); test('have correct endTimestamp', () async { @@ -276,8 +381,8 @@ void main() { .toUtc(); expect(coldStartSpan?.endTimestamp, fixture.native.appStartEnd?.toUtc()); expect(pluginRegistrationSpan?.endTimestamp, engineReadyEndtime); - expect(mainIsolateSetupSpan?.endTimestamp, - SentryFlutter.mainIsolateStartTime.toUtc()); + expect(sentrySetupSpan?.endTimestamp, + SentryFlutter.sentrySetupStartTime?.toUtc()); expect(firstFrameRenderSpan?.endTimestamp, coldStartSpan?.endTimestamp); }); }); @@ -292,6 +397,7 @@ class Fixture { Fixture() { native.reset(); when(hub.options).thenReturn(options); + SentryFlutter.sentrySetupStartTime = DateTime.now().toUtc(); } NativeAppStartIntegration getNativeAppStartIntegration() { diff --git a/flutter/test/navigation/sentry_display_widget_test.dart b/flutter/test/navigation/sentry_display_widget_test.dart index 6a0e912d5e..2cafb706d5 100644 --- a/flutter/test/navigation/sentry_display_widget_test.dart +++ b/flutter/test/navigation/sentry_display_widget_test.dart @@ -62,7 +62,8 @@ void main() { start: getUtcDateTime().add(Duration(seconds: 1)), end: getUtcDateTime().add(Duration(seconds: 2)), pluginRegistration: getUtcDateTime().add(Duration(seconds: 3)), - mainIsolateStart: getUtcDateTime().add(Duration(seconds: 4)), + sentrySetupStart: getUtcDateTime().add(Duration(seconds: 4)), + nativeSpanTimes: [], ); NativeAppStartIntegration.setAppStartInfo(appStartInfo); diff --git a/flutter/test/sentry_native_channel_test.dart b/flutter/test/sentry_native_channel_test.dart index 4d5e539838..a8debae3e4 100644 --- a/flutter/test/sentry_native_channel_test.dart +++ b/flutter/test/sentry_native_channel_test.dart @@ -23,6 +23,8 @@ void main() { 'pluginRegistrationTime': 1, 'appStartTime': 0.1, 'isColdStart': true, + // ignore: inference_failure_on_collection_literal + 'nativeSpanTimes': {}, }; final future = Future.value(map); diff --git a/flutter/test/sentry_native_test.dart b/flutter/test/sentry_native_test.dart index 341c8aaad8..92b7c2e28e 100644 --- a/flutter/test/sentry_native_test.dart +++ b/flutter/test/sentry_native_test.dart @@ -1,5 +1,4 @@ @TestOn('vm') - import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/native/sentry_native.dart'; @@ -17,7 +16,10 @@ void main() { test('fetchNativeAppStart sets didFetchAppStart', () async { final nativeAppStart = NativeAppStart( - appStartTime: 0.0, pluginRegistrationTime: 10, isColdStart: true); + appStartTime: 0.0, + pluginRegistrationTime: 10, + isColdStart: true, + nativeSpanTimes: {}); 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 94ce9e6107..e2b1488cca 100644 --- a/flutter/test/sentry_navigator_observer_test.dart +++ b/flutter/test/sentry_navigator_observer_test.dart @@ -502,7 +502,8 @@ void main() { start: DateTime.now().add(const Duration(seconds: 1)), end: DateTime.now().add(const Duration(seconds: 2)), pluginRegistration: DateTime.now().add(const Duration(seconds: 3)), - mainIsolateStart: DateTime.now().add(const Duration(seconds: 4)), + sentrySetupStart: DateTime.now().add(const Duration(seconds: 4)), + nativeSpanTimes: [], ), );