Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App start: Add spans to first transaction #2009

Merged
merged 31 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 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
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
6413e81
Merge branch 'main' into feat/app-start-improve
buenaflor Apr 29, 2024
2683aa9
Review improvements
buenaflor May 3, 2024
aa7b44a
Merge branch 'main' into feat/app-start-improve
buenaflor May 3, 2024
9f26694
Update CHANGELOG
buenaflor May 3, 2024
c5768ab
Update CHANGELOG
buenaflor May 3, 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

## Unreleased

### Feature
### 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {

private var activity: WeakReference<Activity>? = null
private var framesTracker: ActivityFramesTracker? = null
private var pluginRegistrationTime: Long? = null

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
pluginRegistrationTime = System.currentTimeMillis()

context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sentry_flutter")
channel.setMethodCallHandler(this)
Expand Down Expand Up @@ -137,6 +140,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
} else {
val appStartTimeMillis = DateUtils.nanosToMillis(appStartTime.nanoTimestamp().toDouble())
val item = mapOf<String, Any?>(
"pluginRegistrationTime" to pluginRegistrationTime,
"appStartTime" to appStartTimeMillis,
"isColdStart" to isColdStart,
)
Expand Down
5 changes: 5 additions & 0 deletions flutter/ios/Classes/SentryFlutterPluginApple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
#endif
}

private static var pluginRegistrationTime: Int64 = 0

public static func register(with registrar: FlutterPluginRegistrar) {
pluginRegistrationTime = Int64(Date().timeIntervalSince1970 * 1000)

#if os(iOS)
let channel = FlutterMethodChannel(name: "sentry_flutter", binaryMessenger: registrar.messenger())
#elseif os(macOS)
Expand Down Expand Up @@ -387,6 +391,7 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
let isColdStart = appStartMeasurement.type == .cold

let item: [String: Any] = [
"pluginRegistrationTime": SentryFlutterPluginApple.pluginRegistrationTime,
"appStartTime": appStartTime,
"isColdStart": isColdStart
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import 'dart:async';
// ignore_for_file: invalid_use_of_internal_member

import 'package:sentry/sentry.dart';
import 'dart:async';

import '../../sentry_flutter.dart';
import '../integrations/integrations.dart';
import '../native/sentry_native.dart';

// ignore: implementation_imports
import 'package:sentry/src/sentry_tracer.dart';

/// EventProcessor that enriches [SentryTransaction] objects with app start
/// measurement.
class NativeAppStartEventProcessor implements EventProcessor {
final SentryNative _native;
final Hub _hub;

NativeAppStartEventProcessor(this._native);
NativeAppStartEventProcessor(
this._native, {
Hub? hub,
}) : _hub = hub ?? HubAdapter();

@override
Future<SentryEvent?> apply(SentryEvent event, Hint hint) async {
Expand All @@ -25,6 +33,82 @@ class NativeAppStartEventProcessor implements EventProcessor {
event.measurements[measurement.name] = measurement;
_native.didAddAppStartMeasurement = true;
}

if (appStartInfo != null) {
await _attachAppStartSpans(appStartInfo, event.tracer);
}

return event;
}

Future<void> _attachAppStartSpans(
AppStartInfo appStartInfo, SentryTracer transaction) async {
final transactionTraceId = transaction.context.traceId;

final appStartSpan = await _createAndFinishSpan(
tracer: transaction,
operation: appStartInfo.appStartTypeOperation,
description: appStartInfo.appStartTypeDescription,
parentSpanId: transaction.context.spanId,
traceId: transactionTraceId,
startTimestamp: appStartInfo.start,
endTimestamp: appStartInfo.end);

final pluginRegistrationSpan = await _createAndFinishSpan(
tracer: transaction,
operation: appStartInfo.appStartTypeOperation,
description: appStartInfo.pluginRegistrationDescription,
parentSpanId: appStartSpan.context.spanId,
traceId: transactionTraceId,
startTimestamp: appStartInfo.start,
endTimestamp: appStartInfo.pluginRegistration);

final mainIsolateSetupSpan = await _createAndFinishSpan(
tracer: transaction,
operation: appStartInfo.appStartTypeOperation,
description: appStartInfo.mainIsolateSetupDescription,
parentSpanId: appStartSpan.context.spanId,
traceId: transactionTraceId,
startTimestamp: appStartInfo.pluginRegistration,
endTimestamp: appStartInfo.mainIsolateStart);

final firstFrameRenderSpan = await _createAndFinishSpan(
tracer: transaction,
operation: appStartInfo.appStartTypeOperation,
description: appStartInfo.firstFrameRenderDescription,
parentSpanId: appStartSpan.context.spanId,
traceId: transactionTraceId,
startTimestamp: SentryFlutter.mainIsolateStartTime,
endTimestamp: appStartInfo.end);
buenaflor marked this conversation as resolved.
Show resolved Hide resolved

transaction.children.addAll([
appStartSpan,
pluginRegistrationSpan,
mainIsolateSetupSpan,
firstFrameRenderSpan
]);
}

Future<SentrySpan> _createAndFinishSpan({
required SentryTracer tracer,
required String operation,
required String description,
required SpanId? parentSpanId,
required SentryId? traceId,
required DateTime startTimestamp,
buenaflor marked this conversation as resolved.
Show resolved Hide resolved
required DateTime endTimestamp,
}) async {
final span = SentrySpan(
tracer,
SentrySpanContext(
operation: operation,
description: description,
parentSpanId: parentSpanId,
traceId: traceId,
),
_hub,
startTimestamp: startTimestamp);
await span.finish(endTimestamp: endTimestamp);
return span;
}
}
43 changes: 35 additions & 8 deletions flutter/lib/src/integrations/native_app_start_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
if (isIntegrationTest) {
final appStartInfo = AppStartInfo(AppStartType.cold,
start: DateTime.now(),
end: DateTime.now().add(const Duration(milliseconds: 100)));
end: DateTime.now().add(const Duration(milliseconds: 100)),
pluginRegistration:
DateTime.now().add(const Duration(milliseconds: 50)),
mainIsolateStart:
DateTime.now().add(const Duration(milliseconds: 60)));
setAppStartInfo(appStartInfo);
return;
}
Expand All @@ -67,16 +71,22 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
// We only assign the current time if it's not already set - this is useful in tests
// ignore: invalid_use_of_internal_member
_native.appStartEnd ??= options.clock();
final appStartEnd = _native.appStartEnd;
final appStartEndDateTime = _native.appStartEnd;
final nativeAppStart = await _native.fetchNativeAppStart();
final pluginRegistrationTime = nativeAppStart?.pluginRegistrationTime;
final mainIsolateStartDateTime = SentryFlutter.mainIsolateStartTime;

if (nativeAppStart == null || appStartEnd == null) {
if (nativeAppStart == null ||
appStartEndDateTime == null ||
pluginRegistrationTime == null) {
return;
}

final appStartDateTime = DateTime.fromMillisecondsSinceEpoch(
nativeAppStart.appStartTime.toInt());
final duration = appStartEnd.difference(appStartDateTime);
final duration = appStartEndDateTime.difference(appStartDateTime);
final pluginRegistrationDateTime =
DateTime.fromMillisecondsSinceEpoch(pluginRegistrationTime);

// We filter out app start more than 60s.
// This could be due to many different reasons.
Expand All @@ -93,9 +103,11 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {

final appStartInfo = AppStartInfo(
nativeAppStart.isColdStart ? AppStartType.cold : AppStartType.warm,
start: DateTime.fromMillisecondsSinceEpoch(
nativeAppStart.appStartTime.toInt()),
end: appStartEnd);
start: appStartDateTime,
end: appStartEndDateTime,
pluginRegistration: pluginRegistrationDateTime,
mainIsolateStart: mainIsolateStartDateTime);

setAppStartInfo(appStartInfo);
});
}
Expand All @@ -109,16 +121,31 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
enum AppStartType { cold, warm }

class AppStartInfo {
AppStartInfo(this.type, {required this.start, required this.end});
AppStartInfo(this.type,
{required this.start,
required this.end,
required this.pluginRegistration,
required this.mainIsolateStart});

final AppStartType type;
final DateTime start;
final DateTime end;
final DateTime pluginRegistration;
final DateTime mainIsolateStart;

Duration get duration => end.difference(start);

SentryMeasurement toMeasurement() {
return type == AppStartType.cold
? SentryMeasurement.coldAppStart(duration)
: SentryMeasurement.warmAppStart(duration);
}

String get appStartTypeOperation => 'app.start.${type.name}';

String get appStartTypeDescription =>
type == AppStartType.cold ? 'Cold start' : 'Warm start';
final pluginRegistrationDescription = 'App start to plugin registration';
final mainIsolateSetupDescription = 'Main isolate setup';
final firstFrameRenderDescription = 'First frame render';
}
11 changes: 8 additions & 3 deletions flutter/lib/src/native/sentry_native.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,20 @@ class SentryNative {
}

class NativeAppStart {
NativeAppStart(this.appStartTime, this.isColdStart);
NativeAppStart(
{required this.appStartTime,
required this.pluginRegistrationTime,
required this.isColdStart});

double appStartTime;
int pluginRegistrationTime;
bool isColdStart;

factory NativeAppStart.fromJson(Map<String, dynamic> json) {
return NativeAppStart(
json['appStartTime'] as double,
json['isColdStart'] as bool,
appStartTime: json['appStartTime'] as double,
pluginRegistrationTime: json['pluginRegistrationTime'] as int,
isColdStart: json['isColdStart'] as bool,
);
}
}
Expand Down
8 changes: 8 additions & 0 deletions flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ typedef FlutterOptionsConfiguration = FutureOr<void> Function(
mixin SentryFlutter {
static const _channel = MethodChannel('sentry_flutter');

/// Represents the time when the main isolate is set up and ready to execute.
@internal
// ignore: invalid_use_of_internal_member
static DateTime mainIsolateStartTime = getUtcDateTime();

static Future<void> init(
FlutterOptionsConfiguration optionsConfiguration, {
AppRunner? appRunner,
Expand All @@ -43,6 +48,9 @@ mixin SentryFlutter {
}) async {
final flutterOptions = SentryFlutterOptions();

// ignore: invalid_use_of_internal_member
mainIsolateStartTime = flutterOptions.clock();
buenaflor marked this conversation as resolved.
Show resolved Hide resolved

if (platformChecker != null) {
flutterOptions.platformChecker = platformChecker;
}
Expand Down
Loading
Loading