Skip to content

Commit 7750213

Browse files
committed
Update
1 parent 2f1840d commit 7750213

File tree

6 files changed

+123
-122
lines changed

6 files changed

+123
-122
lines changed

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

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -342,20 +342,22 @@ class SentryFlutterPlugin :
342342
}
343343

344344
val appStartTimeMillis = DateUtils.nanosToMillis(appStartTime.nanoTimestamp().toDouble())
345-
val item = mutableMapOf<String, Any?>(
346-
"pluginRegistrationTime" to pluginRegistrationTime,
347-
"appStartTime" to appStartTimeMillis,
348-
"isColdStart" to isColdStart,
349-
)
345+
val item =
346+
mutableMapOf<String, Any?>(
347+
"pluginRegistrationTime" to pluginRegistrationTime,
348+
"appStartTime" to appStartTimeMillis,
349+
"isColdStart" to isColdStart,
350+
)
350351

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

353-
val processInitSpan = TimeSpan().apply {
354-
description = "Process Initialization"
355-
setStartUnixTimeMs(appStartTimeSpan.startTimestampMs)
356-
setStartedAt(appStartTimeSpan.startUptimeMs)
357-
setStoppedAt(appStartMetrics.classLoadedUptimeMs)
358-
}
354+
val processInitSpan =
355+
TimeSpan().apply {
356+
description = "Process Initialization"
357+
setStartUnixTimeMs(appStartTimeSpan.startTimestampMs)
358+
setStartedAt(appStartTimeSpan.startUptimeMs)
359+
setStoppedAt(appStartMetrics.classLoadedUptimeMs)
360+
}
359361
addTimeSpanToMap(processInitSpan, androidNativeSpans)
360362

361363
val applicationOnCreateSpan = appStartMetrics.applicationOnCreateTimeSpan
@@ -377,14 +379,18 @@ class SentryFlutterPlugin :
377379
return json.toByteArray(Charsets.UTF_8)
378380
}
379381

380-
private fun addTimeSpanToMap(span: TimeSpan, map: MutableMap<String, Any?>) {
382+
private fun addTimeSpanToMap(
383+
span: TimeSpan,
384+
map: MutableMap<String, Any?>,
385+
) {
381386
if (span.startTimestamp == null) return
382387

383388
span.description?.let { description ->
384-
map[description] = mapOf<String, Any?>(
385-
"startTimestampMsSinceEpoch" to span.startTimestampMs,
386-
"stopTimestampMsSinceEpoch" to span.projectedStopTimestampMs,
387-
)
389+
map[description] =
390+
mapOf<String, Any?>(
391+
"startTimestampMsSinceEpoch" to span.startTimestampMs,
392+
"stopTimestampMsSinceEpoch" to span.projectedStopTimestampMs,
393+
)
388394
}
389395
}
390396

packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift

Lines changed: 56 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
7676
case "closeNativeSdk":
7777
closeNativeSdk(call, result: result)
7878

79-
case "fetchNativeAppStart":
80-
fetchNativeAppStart(result: result)
81-
8279
case "setContexts":
8380
let arguments = call.arguments as? [String: Any?]
8481
let key = arguments?["key"] as? String
@@ -291,83 +288,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
291288
return !name.isEmpty
292289
}
293290

294-
struct TimeSpan {
295-
var startTimestampMsSinceEpoch: NSNumber
296-
var stopTimestampMsSinceEpoch: NSNumber
297-
var description: String
298-
299-
func addToMap(_ map: inout [String: Any]) {
300-
map[description] = [
301-
"startTimestampMsSinceEpoch": startTimestampMsSinceEpoch,
302-
"stopTimestampMsSinceEpoch": stopTimestampMsSinceEpoch
303-
]
304-
}
305-
}
306-
307-
private func fetchNativeAppStart(result: @escaping FlutterResult) {
308-
#if os(iOS) || os(tvOS)
309-
guard let appStartMeasurement = PrivateSentrySDKOnly.appStartMeasurement else {
310-
print("warning: appStartMeasurement is null")
311-
result(nil)
312-
return
313-
}
314-
315-
var nativeSpanTimes: [String: Any] = [:]
316-
317-
let appStartTimeMs = appStartMeasurement.appStartTimestamp.timeIntervalSince1970.toMilliseconds()
318-
let runtimeInitTimeMs = appStartMeasurement.runtimeInitTimestamp.timeIntervalSince1970.toMilliseconds()
319-
let moduleInitializationTimeMs =
320-
appStartMeasurement.moduleInitializationTimestamp.timeIntervalSince1970.toMilliseconds()
321-
let sdkStartTimeMs = appStartMeasurement.sdkStartTimestamp.timeIntervalSince1970.toMilliseconds()
322-
323-
if !appStartMeasurement.isPreWarmed {
324-
let preRuntimeInitDescription = "Pre Runtime Init"
325-
let preRuntimeInitSpan = TimeSpan(
326-
startTimestampMsSinceEpoch: NSNumber(value: appStartTimeMs),
327-
stopTimestampMsSinceEpoch: NSNumber(value: runtimeInitTimeMs),
328-
description: preRuntimeInitDescription
329-
)
330-
preRuntimeInitSpan.addToMap(&nativeSpanTimes)
331-
332-
let moduleInitializationDescription = "Runtime init to Pre Main initializers"
333-
let moduleInitializationSpan = TimeSpan(
334-
startTimestampMsSinceEpoch: NSNumber(value: runtimeInitTimeMs),
335-
stopTimestampMsSinceEpoch: NSNumber(value: moduleInitializationTimeMs),
336-
description: moduleInitializationDescription
337-
)
338-
moduleInitializationSpan.addToMap(&nativeSpanTimes)
339-
}
340-
341-
let uiKitInitDescription = "UIKit init"
342-
let uiKitInitSpan = TimeSpan(
343-
startTimestampMsSinceEpoch: NSNumber(value: moduleInitializationTimeMs),
344-
stopTimestampMsSinceEpoch: NSNumber(value: sdkStartTimeMs),
345-
description: uiKitInitDescription
346-
)
347-
uiKitInitSpan.addToMap(&nativeSpanTimes)
348-
349-
// Info: We don't have access to didFinishLaunchingTimestamp,
350-
// On HybridSDKs, the Cocoa SDK misses the didFinishLaunchNotification and the
351-
// didBecomeVisibleNotification. Therefore, we can't set the
352-
// didFinishLaunchingTimestamp
353-
354-
let appStartTime = appStartMeasurement.appStartTimestamp.timeIntervalSince1970 * 1000
355-
let isColdStart = appStartMeasurement.type == .cold
356-
357-
let item: [String: Any] = [
358-
"pluginRegistrationTime": SentryFlutterPlugin.pluginRegistrationTime,
359-
"appStartTime": appStartTime,
360-
"isColdStart": isColdStart,
361-
"nativeSpanTimes": nativeSpanTimes
362-
]
363-
364-
result(item)
365-
#else
366-
print("note: appStartMeasurement not available on this platform")
367-
result(nil)
368-
#endif
369-
}
370-
371291
private func setContexts(key: String?, value: Any?, result: @escaping FlutterResult) {
372292
guard let key = key else {
373293
result("")
@@ -565,6 +485,62 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
565485
}
566486
#endif
567487

488+
@objc public class func fetchNativeAppStartAsBytes() -> NSData? {
489+
#if os(iOS) || os(tvOS)
490+
guard let appStartMeasurement = PrivateSentrySDKOnly.appStartMeasurement else {
491+
return nil
492+
}
493+
494+
var nativeSpanTimes: [String: Any] = [:]
495+
496+
let appStartTimeMs = appStartMeasurement.appStartTimestamp.timeIntervalSince1970.toMilliseconds()
497+
let runtimeInitTimeMs = appStartMeasurement.runtimeInitTimestamp.timeIntervalSince1970.toMilliseconds()
498+
let moduleInitializationTimeMs =
499+
appStartMeasurement.moduleInitializationTimestamp.timeIntervalSince1970.toMilliseconds()
500+
let sdkStartTimeMs = appStartMeasurement.sdkStartTimestamp.timeIntervalSince1970.toMilliseconds()
501+
502+
if !appStartMeasurement.isPreWarmed {
503+
let preRuntimeInitDescription = "Pre Runtime Init"
504+
let preRuntimeInitSpan: [String: Any] = [
505+
"startTimestampMsSinceEpoch": NSNumber(value: appStartTimeMs),
506+
"stopTimestampMsSinceEpoch": NSNumber(value: runtimeInitTimeMs)
507+
]
508+
nativeSpanTimes[preRuntimeInitDescription] = preRuntimeInitSpan
509+
510+
let moduleInitializationDescription = "Runtime init to Pre Main initializers"
511+
let moduleInitializationSpan: [String: Any] = [
512+
"startTimestampMsSinceEpoch": NSNumber(value: runtimeInitTimeMs),
513+
"stopTimestampMsSinceEpoch": NSNumber(value: moduleInitializationTimeMs)
514+
]
515+
nativeSpanTimes[moduleInitializationDescription] = moduleInitializationSpan
516+
}
517+
518+
let uiKitInitDescription = "UIKit init"
519+
let uiKitInitSpan: [String: Any] = [
520+
"startTimestampMsSinceEpoch": NSNumber(value: moduleInitializationTimeMs),
521+
"stopTimestampMsSinceEpoch": NSNumber(value: sdkStartTimeMs)
522+
]
523+
nativeSpanTimes[uiKitInitDescription] = uiKitInitSpan
524+
525+
let appStartTime = appStartMeasurement.appStartTimestamp.timeIntervalSince1970 * 1000
526+
let isColdStart = appStartMeasurement.type == .cold
527+
528+
let item: [String: Any] = [
529+
"pluginRegistrationTime": pluginRegistrationTime,
530+
"appStartTime": appStartTime,
531+
"isColdStart": isColdStart,
532+
"nativeSpanTimes": nativeSpanTimes
533+
]
534+
535+
if let data = try? JSONSerialization.data(withJSONObject: item, options: []) {
536+
return data as NSData
537+
}
538+
return nil
539+
#else
540+
return nil
541+
#endif
542+
}
543+
568544
@objc(loadDebugImagesAsBytes:)
569545
public class func loadDebugImagesAsBytes(instructionAddresses: Set<String>) -> NSData? {
570546
var debugImages: [DebugMeta] = []

packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#else
66
@interface SentryFlutterPlugin : NSObject
77
+ (nullable NSNumber *)getDisplayRefreshRate;
8+
+ (nullable NSData *)fetchNativeAppStartAsBytes;
89
+ (nullable NSData *)loadContextsAsBytes;
910
+ (nullable NSData *)loadDebugImagesAsBytes:(NSSet<NSString *> *)instructionAddresses;
1011
@end

packages/flutter/lib/src/native/cocoa/binding.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,8 @@ class SentryId$1 extends objc.NSObject {
11231123
late final _class_SentryFlutterPlugin = objc.getClass("SentryFlutterPlugin");
11241124
late final _sel_getDisplayRefreshRate =
11251125
objc.registerName("getDisplayRefreshRate");
1126+
late final _sel_fetchNativeAppStartAsBytes =
1127+
objc.registerName("fetchNativeAppStartAsBytes");
11261128
late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes");
11271129
late final _sel_loadDebugImagesAsBytes_ =
11281130
objc.registerName("loadDebugImagesAsBytes:");
@@ -1157,6 +1159,15 @@ class SentryFlutterPlugin extends objc.NSObject {
11571159
: objc.NSNumber.castFromPointer(_ret, retain: true, release: true);
11581160
}
11591161

1162+
/// fetchNativeAppStartAsBytes
1163+
static objc.NSData? fetchNativeAppStartAsBytes() {
1164+
final _ret = _objc_msgSend_151sglz(
1165+
_class_SentryFlutterPlugin, _sel_fetchNativeAppStartAsBytes);
1166+
return _ret.address == 0
1167+
? null
1168+
: objc.NSData.castFromPointer(_ret, retain: true, release: true);
1169+
}
1170+
11601171
/// loadContextsAsBytes
11611172
static objc.NSData? loadContextsAsBytes() {
11621173
final _ret = _objc_msgSend_151sglz(

packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:objective_c/objective_c.dart';
55

66
import '../../../sentry_flutter.dart';
77
import '../../replay/replay_config.dart';
8+
import '../native_app_start.dart';
89
import '../sentry_native_channel.dart';
910
import '../utils/utf8_json.dart';
1011
import 'binding.dart' as cocoa;
@@ -164,4 +165,17 @@ class SentryNativeCocoa extends SentryNativeChannel {
164165
return refreshRate.intValue;
165166
},
166167
);
168+
169+
@override
170+
NativeAppStart? fetchNativeAppStart() => tryCatchSync(
171+
'fetchNativeAppStart',
172+
() {
173+
final appStartUtf8JsonBytes =
174+
cocoa.SentryFlutterPlugin.fetchNativeAppStartAsBytes();
175+
if (appStartUtf8JsonBytes == null) return null;
176+
177+
final json = decodeUtf8JsonMap(appStartUtf8JsonBytes.toList());
178+
return NativeAppStart.fromJson(json);
179+
},
180+
);
167181
}

packages/flutter/test/sentry_native_channel_test.dart

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,34 +38,15 @@ void main() {
3838
// TODO move other methods here, e.g. init_native_sdk_test.dart
3939

4040
test('fetchNativeAppStart', () async {
41-
when(channel.invokeMethod('fetchNativeAppStart'))
42-
.thenAnswer((_) async => {
43-
'pluginRegistrationTime': 1,
44-
'appStartTime': 0.1,
45-
'isColdStart': true,
46-
// ignore: inference_failure_on_collection_literal
47-
'nativeSpanTimes': {},
48-
});
49-
50-
final actual = await sut.fetchNativeAppStart();
51-
52-
expect(actual?.appStartTime, 0.1);
53-
expect(actual?.isColdStart, true);
54-
});
55-
56-
test('invalid fetchNativeAppStart returns null', () async {
57-
when(channel.invokeMethod('fetchNativeAppStart'))
58-
.thenAnswer((_) async => {
59-
'pluginRegistrationTime': 'invalid',
60-
'appStartTime': 'invalid',
61-
'isColdStart': 'invalid',
62-
// ignore: inference_failure_on_collection_literal
63-
'nativeSpanTimes': 'invalid',
64-
});
41+
final matcher = _nativeUnavailableMatcher(
42+
mockPlatform,
43+
includeLookupSymbol: true,
44+
includeFailedToLoadClassException: true,
45+
);
6546

66-
final actual = await sut.fetchNativeAppStart();
47+
expect(() => sut.fetchNativeAppStart(), matcher);
6748

68-
expect(actual, isNull);
49+
verifyZeroInteractions(channel);
6950
});
7051

7152
test('setUser', () async {
@@ -269,6 +250,18 @@ void main() {
269250
verifyZeroInteractions(channel);
270251
});
271252

253+
test('displayRefreshRate', () async {
254+
final matcher = _nativeUnavailableMatcher(
255+
mockPlatform,
256+
includeLookupSymbol: true,
257+
includeFailedToLoadClassException: true,
258+
);
259+
260+
expect(() => sut.displayRefreshRate(), matcher);
261+
262+
verifyZeroInteractions(channel);
263+
});
264+
272265
test('pauseAppHangTracking', () async {
273266
when(channel.invokeMethod('pauseAppHangTracking'))
274267
.thenAnswer((_) => Future.value());

0 commit comments

Comments
 (0)