Skip to content

Commit

Permalink
App start: native spans (#2027)
Browse files Browse the repository at this point in the history
* 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 <stefano.siano@sentry.io>

---------

Co-authored-by: Stefano <stefano.siano@sentry.io>
  • Loading branch information
buenaflor and stefanosiano authored May 9, 2024
1 parent c2c93b3 commit 4656f10
Show file tree
Hide file tree
Showing 14 changed files with 354 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String, Any?>(
"pluginRegistrationTime" to pluginRegistrationTime,
"appStartTime" to appStartTimeMillis,
"isColdStart" to isColdStart,
)
val item =
mutableMapOf<String, Any?>(
"pluginRegistrationTime" to pluginRegistrationTime,
"appStartTime" to appStartTimeMillis,
"isColdStart" to isColdStart,
)

val androidNativeSpans = mutableMapOf<String, Any?>()

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<String, Any?>) {
if (startTimestamp == null) return

description?.let { description ->
map[description] =
mapOf<String, Any?>(
"startTimestampMsSinceEpoch" to startTimestampMs,
"stopTimestampMsSinceEpoch" to projectedStopTimestampMs,
)
}
}

private fun beginNativeFrames(result: Result) {
if (!sentryFlutter.autoPerformanceTracingEnabled) {
result.success(null)
Expand Down
3 changes: 1 addition & 2 deletions flutter/example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -32,4 +32,3 @@ subprojects {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
61 changes: 60 additions & 1 deletion flutter/ios/Classes/SentryFlutterPluginApple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,19 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
return
}

struct TimeSpan {
var startTimestampMsSinceEpoch: NSNumber
var stopTimestampMsSinceEpoch: NSNumber
var description: String

func addToMap(_ map: inout [String: Any]) {
map[description] = [
"startTimestampMsSinceEpoch": startTimestampMsSinceEpoch,
"stopTimestampMsSinceEpoch": stopTimestampMsSinceEpoch
]
}
}

private func fetchNativeAppStart(result: @escaping FlutterResult) {
#if os(iOS) || os(tvOS)
guard let appStartMeasurement = PrivateSentrySDKOnly.appStartMeasurement else {
Expand All @@ -387,13 +400,53 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
return
}

var nativeSpanTimes: [String: Any] = [:]

let appStartTimeMs = appStartMeasurement.appStartTimestamp.timeIntervalSince1970.toMilliseconds()
let runtimeInitTimeMs = appStartMeasurement.runtimeInitTimestamp.timeIntervalSince1970.toMilliseconds()
let moduleInitializationTimeMs =
appStartMeasurement.moduleInitializationTimestamp.timeIntervalSince1970.toMilliseconds()
let sdkStartTimeMs = appStartMeasurement.sdkStartTimestamp.timeIntervalSince1970.toMilliseconds()

if !appStartMeasurement.isPreWarmed {
let preRuntimeInitDescription = "Pre Runtime Init"
let preRuntimeInitSpan = TimeSpan(
startTimestampMsSinceEpoch: NSNumber(value: appStartTimeMs),
stopTimestampMsSinceEpoch: NSNumber(value: runtimeInitTimeMs),
description: preRuntimeInitDescription
)
preRuntimeInitSpan.addToMap(&nativeSpanTimes)

let moduleInitializationDescription = "Runtime init to Pre Main initializers"
let moduleInitializationSpan = TimeSpan(
startTimestampMsSinceEpoch: NSNumber(value: runtimeInitTimeMs),
stopTimestampMsSinceEpoch: NSNumber(value: moduleInitializationTimeMs),
description: moduleInitializationDescription
)
moduleInitializationSpan.addToMap(&nativeSpanTimes)
}

let uiKitInitDescription = "UIKit init"
let uiKitInitSpan = TimeSpan(
startTimestampMsSinceEpoch: NSNumber(value: moduleInitializationTimeMs),
stopTimestampMsSinceEpoch: NSNumber(value: sdkStartTimeMs),
description: uiKitInitDescription
)
uiKitInitSpan.addToMap(&nativeSpanTimes)

// Info: We don't have access to didFinishLaunchingTimestamp,
// On HybridSDKs, the Cocoa SDK misses the didFinishLaunchNotification and the
// didBecomeVisibleNotification. Therefore, we can't set the
// didFinishLaunchingTimestamp

let appStartTime = appStartMeasurement.appStartTimestamp.timeIntervalSince1970 * 1000
let isColdStart = appStartMeasurement.type == .cold

let item: [String: Any] = [
"pluginRegistrationTime": SentryFlutterPluginApple.pluginRegistrationTime,
"appStartTime": appStartTime,
"isColdStart": isColdStart
"isColdStart": isColdStart,
"nativeSpanTimes": nativeSpanTimes
]

result(item)
Expand Down Expand Up @@ -601,3 +654,9 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
}

// swiftlint:enable function_body_length

private extension TimeInterval {
func toMilliseconds() -> Int64 {
return Int64(self * 1000)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ class NativeAppStartEventProcessor implements EventProcessor {
}

final measurement = appStartInfo?.toMeasurement();

if (measurement != null) {
event.measurements[measurement.name] = measurement;
_native.didAddAppStartMeasurement = true;
Expand Down Expand Up @@ -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,
Expand All @@ -81,32 +82,54 @@ 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,
operation: appStartInfo.appStartTypeOperation,
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<void> _attachNativeSpans(AppStartInfo appStartInfo,
SentryTracer transaction, SentrySpan parent) async {
await Future.forEach<TimeSpan>(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<SentrySpan> _createAndFinishSpan({
required SentryTracer tracer,
required String operation,
Expand Down
Loading

0 comments on commit 4656f10

Please sign in to comment.