Skip to content

Commit 4656f10

Browse files
App start: native spans (#2027)
* commit * Update * Remove print * Remove comments * Update * Add linting * Update CHANGELOG * Update CHANGELOG.md * Update naming * Update naming * Update naming * Update description from first frame render to initial frame render * Initial * update * dart format * Update comments * Update * Update * Update * Update * Update * Fix tests * Fix test * Add unused import * Fix tests * Update * Updaet * Update * Update * Update CHANGELOG * Update CHANGELOG * Update formatting of kotlin file * Update * Update test * format * Update SentryFlutterPlugin.kt * Update SentryFlutterPlugin.kt * Update * Update main.dart * Update * Update * Update * Updaet * Format * Update flutter/lib/src/sentry_flutter.dart Co-authored-by: Stefano <[email protected]> --------- Co-authored-by: Stefano <[email protected]>
1 parent c2c93b3 commit 4656f10

File tree

14 files changed

+354
-66
lines changed

14 files changed

+354
-66
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
### Features
1010

11+
- Adds native spans to app start transaction ([#2027](https://github.com/getsentry/sentry-dart/pull/2027))
1112
- Adds app start spans to first transaction ([#2009](https://github.com/getsentry/sentry-dart/pull/2009))
1213

1314
### Fixes

flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.sentry.android.core.LoadClass
2323
import io.sentry.android.core.SentryAndroid
2424
import io.sentry.android.core.SentryAndroidOptions
2525
import io.sentry.android.core.performance.AppStartMetrics
26+
import io.sentry.android.core.performance.TimeSpan
2627
import io.sentry.protocol.DebugImage
2728
import io.sentry.protocol.SdkVersion
2829
import io.sentry.protocol.SentryId
@@ -130,24 +131,66 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
130131
return
131132
}
132133

133-
val appStartTime = AppStartMetrics.getInstance().appStartTimeSpan.startTimestamp
134-
val isColdStart =
135-
AppStartMetrics.getInstance().appStartType == AppStartMetrics.AppStartType.COLD
134+
val appStartMetrics = AppStartMetrics.getInstance()
135+
136+
val appStartTimeSpan = appStartMetrics.appStartTimeSpan
137+
val appStartTime = appStartTimeSpan.startTimestamp
138+
val isColdStart = appStartMetrics.appStartType == AppStartMetrics.AppStartType.COLD
136139

137140
if (appStartTime == null) {
138141
Log.w("Sentry", "App start won't be sent due to missing appStartTime")
139142
result.success(null)
140143
} else {
141144
val appStartTimeMillis = DateUtils.nanosToMillis(appStartTime.nanoTimestamp().toDouble())
142-
val item = mapOf<String, Any?>(
143-
"pluginRegistrationTime" to pluginRegistrationTime,
144-
"appStartTime" to appStartTimeMillis,
145-
"isColdStart" to isColdStart,
146-
)
145+
val item =
146+
mutableMapOf<String, Any?>(
147+
"pluginRegistrationTime" to pluginRegistrationTime,
148+
"appStartTime" to appStartTimeMillis,
149+
"isColdStart" to isColdStart,
150+
)
151+
152+
val androidNativeSpans = mutableMapOf<String, Any?>()
153+
154+
val processInitSpan =
155+
TimeSpan().apply {
156+
description = "Process Initialization"
157+
setStartUnixTimeMs(appStartTimeSpan.startTimestampMs)
158+
setStartedAt(appStartTimeSpan.startUptimeMs)
159+
setStoppedAt(appStartMetrics.classLoadedUptimeMs)
160+
}
161+
processInitSpan.addToMap(androidNativeSpans)
162+
163+
val applicationOnCreateSpan = appStartMetrics.applicationOnCreateTimeSpan
164+
applicationOnCreateSpan.addToMap(androidNativeSpans)
165+
166+
val contentProviderSpans = appStartMetrics.contentProviderOnCreateTimeSpans
167+
contentProviderSpans.forEach { span ->
168+
span.addToMap(androidNativeSpans)
169+
}
170+
171+
appStartMetrics.activityLifecycleTimeSpans.forEach { span ->
172+
span.onCreate.addToMap(androidNativeSpans)
173+
span.onStart.addToMap(androidNativeSpans)
174+
}
175+
176+
item["nativeSpanTimes"] = androidNativeSpans
177+
147178
result.success(item)
148179
}
149180
}
150181

182+
private fun TimeSpan.addToMap(map: MutableMap<String, Any?>) {
183+
if (startTimestamp == null) return
184+
185+
description?.let { description ->
186+
map[description] =
187+
mapOf<String, Any?>(
188+
"startTimestampMsSinceEpoch" to startTimestampMs,
189+
"stopTimestampMsSinceEpoch" to projectedStopTimestampMs,
190+
)
191+
}
192+
}
193+
151194
private fun beginNativeFrames(result: Result) {
152195
if (!sentryFlutter.autoPerformanceTracingEnabled) {
153196
result.success(null)

flutter/example/android/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ buildscript {
77
}
88

99
dependencies {
10-
classpath 'io.sentry:sentry-android-gradle-plugin:4.2.0'
10+
classpath 'io.sentry:sentry-android-gradle-plugin:4.5.0'
1111
classpath 'com.android.tools.build:gradle:7.2.2'
1212
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1313
classpath 'io.github.howardpang:androidNativeBundle:1.1.3'
@@ -32,4 +32,3 @@ subprojects {
3232
tasks.register("clean", Delete) {
3333
delete rootProject.buildDir
3434
}
35-

flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1430"
3+
LastUpgradeVersion = "1510"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

flutter/ios/Classes/SentryFlutterPluginApple.swift

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,19 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
379379
return
380380
}
381381

382+
struct TimeSpan {
383+
var startTimestampMsSinceEpoch: NSNumber
384+
var stopTimestampMsSinceEpoch: NSNumber
385+
var description: String
386+
387+
func addToMap(_ map: inout [String: Any]) {
388+
map[description] = [
389+
"startTimestampMsSinceEpoch": startTimestampMsSinceEpoch,
390+
"stopTimestampMsSinceEpoch": stopTimestampMsSinceEpoch
391+
]
392+
}
393+
}
394+
382395
private func fetchNativeAppStart(result: @escaping FlutterResult) {
383396
#if os(iOS) || os(tvOS)
384397
guard let appStartMeasurement = PrivateSentrySDKOnly.appStartMeasurement else {
@@ -387,13 +400,53 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
387400
return
388401
}
389402

403+
var nativeSpanTimes: [String: Any] = [:]
404+
405+
let appStartTimeMs = appStartMeasurement.appStartTimestamp.timeIntervalSince1970.toMilliseconds()
406+
let runtimeInitTimeMs = appStartMeasurement.runtimeInitTimestamp.timeIntervalSince1970.toMilliseconds()
407+
let moduleInitializationTimeMs =
408+
appStartMeasurement.moduleInitializationTimestamp.timeIntervalSince1970.toMilliseconds()
409+
let sdkStartTimeMs = appStartMeasurement.sdkStartTimestamp.timeIntervalSince1970.toMilliseconds()
410+
411+
if !appStartMeasurement.isPreWarmed {
412+
let preRuntimeInitDescription = "Pre Runtime Init"
413+
let preRuntimeInitSpan = TimeSpan(
414+
startTimestampMsSinceEpoch: NSNumber(value: appStartTimeMs),
415+
stopTimestampMsSinceEpoch: NSNumber(value: runtimeInitTimeMs),
416+
description: preRuntimeInitDescription
417+
)
418+
preRuntimeInitSpan.addToMap(&nativeSpanTimes)
419+
420+
let moduleInitializationDescription = "Runtime init to Pre Main initializers"
421+
let moduleInitializationSpan = TimeSpan(
422+
startTimestampMsSinceEpoch: NSNumber(value: runtimeInitTimeMs),
423+
stopTimestampMsSinceEpoch: NSNumber(value: moduleInitializationTimeMs),
424+
description: moduleInitializationDescription
425+
)
426+
moduleInitializationSpan.addToMap(&nativeSpanTimes)
427+
}
428+
429+
let uiKitInitDescription = "UIKit init"
430+
let uiKitInitSpan = TimeSpan(
431+
startTimestampMsSinceEpoch: NSNumber(value: moduleInitializationTimeMs),
432+
stopTimestampMsSinceEpoch: NSNumber(value: sdkStartTimeMs),
433+
description: uiKitInitDescription
434+
)
435+
uiKitInitSpan.addToMap(&nativeSpanTimes)
436+
437+
// Info: We don't have access to didFinishLaunchingTimestamp,
438+
// On HybridSDKs, the Cocoa SDK misses the didFinishLaunchNotification and the
439+
// didBecomeVisibleNotification. Therefore, we can't set the
440+
// didFinishLaunchingTimestamp
441+
390442
let appStartTime = appStartMeasurement.appStartTimestamp.timeIntervalSince1970 * 1000
391443
let isColdStart = appStartMeasurement.type == .cold
392444

393445
let item: [String: Any] = [
394446
"pluginRegistrationTime": SentryFlutterPluginApple.pluginRegistrationTime,
395447
"appStartTime": appStartTime,
396-
"isColdStart": isColdStart
448+
"isColdStart": isColdStart,
449+
"nativeSpanTimes": nativeSpanTimes
397450
]
398451

399452
result(item)
@@ -601,3 +654,9 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
601654
}
602655

603656
// swiftlint:enable function_body_length
657+
658+
private extension TimeInterval {
659+
func toMilliseconds() -> Int64 {
660+
return Int64(self * 1000)
661+
}
662+
}

flutter/lib/src/event_processor/native_app_start_event_processor.dart

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ class NativeAppStartEventProcessor implements EventProcessor {
4242
}
4343

4444
final measurement = appStartInfo?.toMeasurement();
45-
4645
if (measurement != null) {
4746
event.measurements[measurement.name] = measurement;
4847
_native.didAddAppStartMeasurement = true;
@@ -72,6 +71,8 @@ class NativeAppStartEventProcessor implements EventProcessor {
7271
startTimestamp: appStartInfo.start,
7372
endTimestamp: appStartEnd);
7473

74+
await _attachNativeSpans(appStartInfo, transaction, appStartSpan);
75+
7576
final pluginRegistrationSpan = await _createAndFinishSpan(
7677
tracer: transaction,
7778
operation: appStartInfo.appStartTypeOperation,
@@ -81,32 +82,54 @@ class NativeAppStartEventProcessor implements EventProcessor {
8182
startTimestamp: appStartInfo.start,
8283
endTimestamp: appStartInfo.pluginRegistration);
8384

84-
final mainIsolateSetupSpan = await _createAndFinishSpan(
85+
final sentrySetupSpan = await _createAndFinishSpan(
8586
tracer: transaction,
8687
operation: appStartInfo.appStartTypeOperation,
87-
description: appStartInfo.mainIsolateSetupDescription,
88+
description: appStartInfo.sentrySetupDescription,
8889
parentSpanId: appStartSpan.context.spanId,
8990
traceId: transactionTraceId,
9091
startTimestamp: appStartInfo.pluginRegistration,
91-
endTimestamp: appStartInfo.mainIsolateStart);
92+
endTimestamp: appStartInfo.sentrySetupStart);
9293

9394
final firstFrameRenderSpan = await _createAndFinishSpan(
9495
tracer: transaction,
9596
operation: appStartInfo.appStartTypeOperation,
9697
description: appStartInfo.firstFrameRenderDescription,
9798
parentSpanId: appStartSpan.context.spanId,
9899
traceId: transactionTraceId,
99-
startTimestamp: appStartInfo.mainIsolateStart,
100+
startTimestamp: appStartInfo.sentrySetupStart,
100101
endTimestamp: appStartEnd);
101102

102103
transaction.children.addAll([
103104
appStartSpan,
104105
pluginRegistrationSpan,
105-
mainIsolateSetupSpan,
106+
sentrySetupSpan,
106107
firstFrameRenderSpan
107108
]);
108109
}
109110

111+
Future<void> _attachNativeSpans(AppStartInfo appStartInfo,
112+
SentryTracer transaction, SentrySpan parent) async {
113+
await Future.forEach<TimeSpan>(appStartInfo.nativeSpanTimes,
114+
(timeSpan) async {
115+
try {
116+
final span = await _createAndFinishSpan(
117+
tracer: transaction,
118+
operation: appStartInfo.appStartTypeOperation,
119+
description: timeSpan.description,
120+
parentSpanId: parent.context.spanId,
121+
traceId: transaction.context.traceId,
122+
startTimestamp: timeSpan.start,
123+
endTimestamp: timeSpan.end);
124+
span.data.putIfAbsent('native', () => true);
125+
transaction.children.add(span);
126+
} catch (e) {
127+
_hub.options.logger(SentryLevel.warning,
128+
'Failed to attach native span to app start transaction: $e');
129+
}
130+
});
131+
}
132+
110133
Future<SentrySpan> _createAndFinishSpan({
111134
required SentryTracer tracer,
112135
required String operation,

0 commit comments

Comments
 (0)