Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5ad308b
commit
buenaflor Apr 18, 2024
7bc756b
Merge branch 'main' into feat/app-start-improve
buenaflor Apr 23, 2024
3e0790a
Update
buenaflor Apr 23, 2024
d0eb540
Remove print
buenaflor Apr 23, 2024
05c208c
Remove comments
buenaflor Apr 23, 2024
c796b71
Update
buenaflor Apr 24, 2024
da8297e
Merge branch 'main' into feat/app-start-improve
buenaflor Apr 24, 2024
bdc8588
Add linting
buenaflor Apr 24, 2024
d1b37cd
Update CHANGELOG
buenaflor Apr 24, 2024
6f44478
Update CHANGELOG.md
buenaflor Apr 24, 2024
8051972
Update naming
buenaflor Apr 24, 2024
29671ff
Merge branch 'main' into feat/app-start-improve
buenaflor Apr 24, 2024
b401ed0
Update naming
buenaflor Apr 24, 2024
c06973c
Update naming
buenaflor Apr 24, 2024
2bb3887
Update description from first frame render to initial frame render
buenaflor Apr 24, 2024
3aadc19
Initial
buenaflor Apr 25, 2024
86bc3bf
Merge branch 'feat/app-start-improve' into feat/app-start-native-spans
buenaflor Apr 25, 2024
801483e
update
buenaflor Apr 26, 2024
d28af71
dart format
buenaflor Apr 26, 2024
21c61a6
Update comments
buenaflor Apr 26, 2024
88a8819
Update
buenaflor Apr 26, 2024
0d73052
Update
buenaflor Apr 26, 2024
1e05ae5
Update
buenaflor Apr 26, 2024
73d830a
Update
buenaflor Apr 26, 2024
62cb2be
Update
buenaflor Apr 26, 2024
111125c
Fix tests
buenaflor Apr 29, 2024
04ad023
Fix test
buenaflor Apr 29, 2024
e9e95c4
Add unused import
buenaflor Apr 29, 2024
bb785b6
Merge branch 'feat/app-start-improve' into feat/app-start-native-spans
buenaflor Apr 29, 2024
d0ce3a4
Fix tests
buenaflor Apr 29, 2024
b22a9c0
Update
buenaflor May 2, 2024
d4c6b1a
Updaet
buenaflor May 2, 2024
06325a2
Update
buenaflor May 3, 2024
bc7ba9e
Update
buenaflor May 3, 2024
4367089
Merge branch 'main' into feat/app-start-native-spans
buenaflor May 6, 2024
a847325
Update CHANGELOG
buenaflor May 6, 2024
7703c4e
Update CHANGELOG
buenaflor May 6, 2024
4ae3212
Update formatting of kotlin file
buenaflor May 6, 2024
342b518
Update
buenaflor May 6, 2024
7a1084f
Update test
buenaflor May 6, 2024
16303bd
format
buenaflor May 6, 2024
20e595f
Update SentryFlutterPlugin.kt
buenaflor May 6, 2024
0cf5d12
Update SentryFlutterPlugin.kt
buenaflor May 6, 2024
2820fc8
Update
buenaflor May 6, 2024
c122240
Update main.dart
buenaflor May 6, 2024
939ed17
Merge branch 'main' into feat/app-start-native-spans
buenaflor May 8, 2024
cdad9a8
Update
buenaflor May 8, 2024
ffb3695
Update
buenaflor May 8, 2024
e3197a7
Update
buenaflor May 8, 2024
427d516
Updaet
buenaflor May 8, 2024
ab63c3b
Format
buenaflor May 8, 2024
93b4516
Update flutter/lib/src/sentry_flutter.dart
buenaflor May 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Update
  • Loading branch information
buenaflor committed May 2, 2024
commit b22a9c0044fd06f378d05ebc170effca55770fa1
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 @@ -71,10 +72,19 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
"setTag" -> setTag(call.argument("key"), call.argument("value"), result)
"removeTag" -> removeTag(call.argument("key"), result)
"loadContexts" -> loadContexts(result)
"loadNativeData" -> loadNativeData()
else -> result.notImplemented()
}
}

private fun loadNativeData() {
val appStartMetrics = AppStartMetrics.getInstance()

appStartMetrics.contentProviderOnCreateTimeSpans.forEach {
Log.d("noob", it.description.toString())
}
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
if (!this::channel.isInitialized) {
return
Expand Down Expand Up @@ -132,13 +142,9 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {

val appStartMetrics = AppStartMetrics.getInstance()

val applicationOnCreateStartTime = appStartMetrics.applicationOnCreateTimeSpan.startTimestamp
val classInitUptimeMs = appStartMetrics.classLoadedUptimeMs
val appStartUptimeMs = appStartMetrics.appStartTimeSpan.startUptimeMs
val appStartTime = appStartMetrics.appStartTimeSpan.startTimestamp

val isColdStart =
AppStartMetrics.getInstance().appStartType == AppStartMetrics.AppStartType.COLD
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")
Expand All @@ -150,15 +156,47 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
"appStartTime" to appStartTimeMillis,
"isColdStart" to isColdStart,
)
item["nativeSpanTimes"] = mapOf<String, Any?>(
"classInitUptimeMs" to classInitUptimeMs,
"appStartUptimeMs" to appStartUptimeMs,
)

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.onStart.addToMap(androidNativeSpans)
span.onCreate.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
12 changes: 7 additions & 5 deletions flutter/example/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
plugins {
id 'io.sentry.android.gradle'
}

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
Expand Down Expand Up @@ -100,8 +104,6 @@ dependencies {
implementation "androidx.annotation:annotation:1.1.0"
}

// uncomment this to upload debug symbols to Sentry
// sentry {
// uploadNativeSymbols = true
// includeNativeSources = true
// }
sentry {
autoInstallation.enabled = false
}
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
69 changes: 57 additions & 12 deletions flutter/ios/Classes/SentryFlutterPluginApple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -379,26 +379,65 @@ 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 {
print("warning: appStartMeasurement is null")
result(nil)
return
}

let runtimeInitTimestamp = appStartMeasurement.runtimeInitTimestamp.timeIntervalSince1970 * 1000
let moduleInitTimestamp = appStartMeasurement.moduleInitializationTimestamp.timeIntervalSince1970 * 1000
let sdkStartTimestamp = appStartMeasurement.sdkStartTimestamp.timeIntervalSince1970 * 1000
let didFinishLaunchingTimestamp = appStartMeasurement.didFinishLaunchingTimestamp.timeIntervalSince1970 * 1000

let nativeSpanTimes: [String: Any] = [
"runtimeInitTimestamp": runtimeInitTimestamp,
"moduleInitTimestamp": moduleInitTimestamp,
"sdkStartTimestamp": sdkStartTimestamp,
"didFinishLaunchingTimestamp": didFinishLaunchingTimestamp
]

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

Expand Down Expand Up @@ -614,3 +653,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 @@ -116,73 +116,32 @@ class NativeAppStartEventProcessor implements EventProcessor {

Future<void> _attachNativeSpans(AppStartInfo appStartInfo,
SentryTracer transaction, SentrySpan parent) async {
final runTimeInitTimestamp =
appStartInfo.nativeSpanTimes['runtimeInitTimestamp'] as double?;
final moduleInitTimestamp =
appStartInfo.nativeSpanTimes['moduleInitTimestamp'] as double?;
final sdkStartTimestamp =
appStartInfo.nativeSpanTimes['sdkStartTimestamp'] as double?;
final didFinishLaunchingTimestamp =
appStartInfo.nativeSpanTimes['didFinishLaunchingTimestamp'] as double?;

if (runTimeInitTimestamp != null &&
moduleInitTimestamp != null &&
sdkStartTimestamp != null &&
didFinishLaunchingTimestamp != null) {
final aa =
DateTime.fromMillisecondsSinceEpoch(runTimeInitTimestamp.toInt());
final bb =
DateTime.fromMillisecondsSinceEpoch(moduleInitTimestamp.toInt());
final sdkStart =
DateTime.fromMillisecondsSinceEpoch(sdkStartTimestamp.toInt());
final didFinishLaunching = DateTime.fromMillisecondsSinceEpoch(
didFinishLaunchingTimestamp.toInt());

final op = 'app.start.${appStartInfo.type.name}';

final preRuntimeInitSpan = await _createAndFinishSpan(
tracer: transaction,
operation: op,
description: 'Pre runtime init',
parentSpanId: parent.context.spanId,
traceId: transaction.context.traceId,
startTimestamp: appStartInfo.start,
endTimestamp: aa);

final runtimeInitSpan = await _createAndFinishSpan(
tracer: transaction,
operation: op,
description: 'Runtime init',
parentSpanId: parent.context.spanId,
traceId: transaction.context.traceId,
startTimestamp: aa,
endTimestamp: bb);

final appInitSpan = await _createAndFinishSpan(
tracer: transaction,
operation: op,
description: 'UIKit init',
parentSpanId: parent.context.spanId,
traceId: transaction.context.traceId,
startTimestamp: bb,
endTimestamp: sdkStart);

final didFinishLaunchingSpan = await _createAndFinishSpan(
tracer: transaction,
operation: op,
description: 'Application init',
parentSpanId: parent.context.spanId,
traceId: transaction.context.traceId,
startTimestamp: sdkStart,
endTimestamp: didFinishLaunching);

transaction.children.addAll([
preRuntimeInitSpan,
runtimeInitSpan,
appInitSpan,
didFinishLaunchingSpan,
]);
}
await Future.forEach(appStartInfo.nativeSpanTimes.keys, (key) async {
try {
final description = key as String;
final spanContent =
appStartInfo.nativeSpanTimes[key] as Map<dynamic, dynamic>;
final startTimestampMs =
spanContent['startTimestampMsSinceEpoch'] as int;
final endTimestampMs = spanContent['stopTimestampMsSinceEpoch'] as int;
final startTimestamp =
DateTime.fromMillisecondsSinceEpoch(startTimestampMs.toInt());
final endTimestamp =
DateTime.fromMillisecondsSinceEpoch(endTimestampMs.toInt());
final span = await _createAndFinishSpan(
tracer: transaction,
operation: appStartInfo.appStartTypeOperation,
description: description,
parentSpanId: parent.context.spanId,
traceId: transaction.context.traceId,
startTimestamp: startTimestamp,
endTimestamp: endTimestamp);
transaction.children.add(span);
} catch (e) {
_hub.options.logger(SentryLevel.warning,
'Failed to attach native span to app start transaction: $e');
}
});
}

Future<SentrySpan> _createAndFinishSpan({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, nativeSpanTimes: {});
appStartTime: 0,
pluginRegistrationTime: 10,
isColdStart: true,
nativeSpanTimes: {});

fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options);

Expand All @@ -45,7 +48,10 @@ void main() {
() async {
fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10);
fixture.binding.nativeAppStart = NativeAppStart(
appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true, nativeSpanTimes: {});
appStartTime: 0,
pluginRegistrationTime: 10,
isColdStart: true,
nativeSpanTimes: {});

fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options);

Expand All @@ -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, nativeSpanTimes: {});
appStartTime: 0,
pluginRegistrationTime: 10,
isColdStart: true,
nativeSpanTimes: {});
final measurement = SentryMeasurement.warmAppStart(Duration(seconds: 1));

fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options);
Expand All @@ -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, nativeSpanTimes: {});
appStartTime: 0,
pluginRegistrationTime: 10,
isColdStart: true,
nativeSpanTimes: {});

fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options);

Expand All @@ -106,7 +118,10 @@ void main() {
() async {
fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(10);
fixture.binding.nativeAppStart = NativeAppStart(
appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true, nativeSpanTimes: {});
appStartTime: 0,
pluginRegistrationTime: 10,
isColdStart: true,
nativeSpanTimes: {});

fixture.getNativeAppStartIntegration().call(fixture.hub, fixture.options);

Expand All @@ -133,7 +148,10 @@ void main() {

fixture.native.appStartEnd = DateTime.fromMillisecondsSinceEpoch(50);
fixture.binding.nativeAppStart = NativeAppStart(
appStartTime: 0, pluginRegistrationTime: 10, isColdStart: true, nativeSpanTimes: {});
appStartTime: 0,
pluginRegistrationTime: 10,
isColdStart: true,
nativeSpanTimes: {});
// dartLoadingEnd needs to be set after engine end (see MockNativeChannel)
SentryFlutter.mainIsolateStartTime =
DateTime.fromMillisecondsSinceEpoch(15);
Expand Down