Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Features

- Adds app start spans to first transaction ([#2009](https://github.com/getsentry/sentry-dart/pull/2009))
- Set snapshot to `true` when Sentry attaches a stacktrace ([#2000](https://github.com/getsentry/sentry-dart/pull/2000))
- This may change grouping

## 8.1.0

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 @@ -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 @@ -130,24 +140,63 @@ 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?>(
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.sentry.samples.flutter

import android.app.Application

class MyApplication : Application()
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
2 changes: 1 addition & 1 deletion flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Future<void> 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;
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 @@ -54,6 +54,8 @@ class NativeAppStartEventProcessor implements EventProcessor {
startTimestamp: appStartInfo.start,
endTimestamp: appStartInfo.end);

await _attachNativeSpans(appStartInfo, transaction, appStartSpan);

final pluginRegistrationSpan = await _createAndFinishSpan(
tracer: transaction,
operation: appStartInfo.appStartTypeOperation,
Expand Down Expand Up @@ -89,6 +91,28 @@ class NativeAppStartEventProcessor implements EventProcessor {
]);
}

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
60 changes: 50 additions & 10 deletions flutter/lib/src/integrations/native_app_start_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<SentryFlutterOptions> {
NativeAppStartIntegration(this._native, this._frameCallbackHandler);
NativeAppStartIntegration(this._native, this._frameCallbackHandler,
{Hub? hub})
: _hub = hub ?? HubAdapter();

final SentryNative _native;
final FrameCallbackHandler _frameCallbackHandler;
final Hub _hub;

/// We filter out App starts more than 60s
static const _maxAppStartMillis = 60000;
Expand Down Expand Up @@ -51,13 +54,15 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
@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)),
mainIsolateStart: DateTime.now().add(const Duration(milliseconds: 60)),
nativeSpanTimes: [],
);
setAppStartInfo(appStartInfo);
return;
}
Expand Down Expand Up @@ -101,12 +106,37 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
return;
}

List<TimeSpan> 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);
mainIsolateStart: mainIsolateStartDateTime,
nativeSpanTimes: nativeSpanTimes);

setAppStartInfo(appStartInfo);
});
Expand All @@ -125,11 +155,13 @@ class AppStartInfo {
{required this.start,
required this.end,
required this.pluginRegistration,
required this.mainIsolateStart});
required this.mainIsolateStart,
required this.nativeSpanTimes});

final AppStartType type;
final DateTime start;
final DateTime end;
final List<TimeSpan> nativeSpanTimes;
final DateTime pluginRegistration;
final DateTime mainIsolateStart;

Expand All @@ -149,3 +181,11 @@ class AppStartInfo {
final mainIsolateSetupDescription = 'Main isolate 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;
}
5 changes: 4 additions & 1 deletion flutter/lib/src/native/sentry_native.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,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<dynamic, dynamic> nativeSpanTimes;

factory NativeAppStart.fromJson(Map<String, dynamic> json) {
return NativeAppStart(
appStartTime: json['appStartTime'] as double,
pluginRegistrationTime: json['pluginRegistrationTime'] as int,
isColdStart: json['isColdStart'] as bool,
nativeSpanTimes: json['nativeSpanTimes'] as Map<dynamic, dynamic>,
);
}
}
Expand Down
Loading