From 5a8bea7968efb87261a8bf71609fa4cc16bb71be Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 30 May 2024 23:33:55 +0200 Subject: [PATCH 1/7] feat(internal): Add serialized app start measurements getter with spans for Hybrid SDKs --- .../android/core/InternalSentrySdk.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index 9bdbe86a77..ecbf550b1d 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -10,12 +10,14 @@ import io.sentry.IScope; import io.sentry.ISerializer; import io.sentry.ObjectWriter; +import io.sentry.SentryDate; import io.sentry.SentryEnvelope; import io.sentry.SentryEnvelopeItem; import io.sentry.SentryEvent; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.Session; +import io.sentry.android.core.performance.ActivityLifecycleTimeSpan; import io.sentry.android.core.performance.AppStartMetrics; import io.sentry.android.core.performance.TimeSpan; import io.sentry.protocol.App; @@ -193,6 +195,58 @@ public static SentryId captureEnvelope(final @NotNull byte[] envelopeData) { return null; } + public static Map getAppStartMeasurement() { + final @NotNull AppStartMetrics metrics = AppStartMetrics.getInstance(); + final @NotNull List> spans = new ArrayList<>(); + + final @NotNull Map processInitSpan = new HashMap<>(); + processInitSpan.put("description", "Process Initialization"); + processInitSpan.put("startTimestampMs", metrics.getAppStartTimeSpan().getStartTimestampMs()); + processInitSpan.put("endTimestampMs", metrics.getClassLoadedUptimeMs()); + spans.add(processInitSpan); + + final @NotNull Map applicationOnCreateSpan = new HashMap<>(); + applicationOnCreateSpan.put("description", "Process Initialization"); + applicationOnCreateSpan.put( + "startTimestampMs", metrics.getAppStartTimeSpan().getStartTimestampMs()); + applicationOnCreateSpan.put( + "endTimestampMs", metrics.getAppStartTimeSpan().getProjectedStopTimestampMs()); + spans.add(applicationOnCreateSpan); + + for (final TimeSpan span : metrics.getContentProviderOnCreateTimeSpans()) { + final @NotNull Map serializedSpan = new HashMap<>(); + serializedSpan.put("description", span.getDescription()); + serializedSpan.put("startTimestampMs", span.getStartTimestampMs()); + serializedSpan.put("endTimestampMs", span.getProjectedStopTimestampMs()); + spans.add(serializedSpan); + } + + for (final ActivityLifecycleTimeSpan span : metrics.getActivityLifecycleTimeSpans()) { + final @NotNull Map serializedOnCreateSpan = new HashMap<>(); + serializedOnCreateSpan.put("description", span.getOnCreate().getDescription()); + serializedOnCreateSpan.put("startTimestampMs", span.getOnCreate().getStartTimestampMs()); + serializedOnCreateSpan.put( + "endTimestampMs", span.getOnCreate().getProjectedStopTimestampMs()); + spans.add(serializedOnCreateSpan); + + final @NotNull Map serializedOnStartSpan = new HashMap<>(); + serializedOnStartSpan.put("description", span.getOnStart().getDescription()); + serializedOnStartSpan.put("startTimestampMs", span.getOnStart().getStartTimestampMs()); + serializedOnStartSpan.put("endTimestampMs", span.getOnStart().getProjectedStopTimestampMs()); + spans.add(serializedOnStartSpan); + } + + final @NotNull Map result = new HashMap<>(); + result.put("spans", spans); + result.put("type", metrics.getAppStartType().toString().toLowerCase()); + final @Nullable SentryDate appStartTime = metrics.getAppStartTimeSpan().getStartTimestamp(); + if (appStartTime != null) { + result.put("app_start_timestamp_ms", DateUtils.nanosToMillis(appStartTime.nanoTimestamp())); + } + + return result; + } + @Nullable private static Session updateSession( final @NotNull IHub hub, From 4c8ff7f34bc373bfc49eb829d8e40ac1f9085eb1 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 30 May 2024 23:35:16 +0200 Subject: [PATCH 2/7] use lower case snake case --- .../android/core/InternalSentrySdk.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index ecbf550b1d..17f928a4d7 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -201,38 +201,38 @@ public static Map getAppStartMeasurement() { final @NotNull Map processInitSpan = new HashMap<>(); processInitSpan.put("description", "Process Initialization"); - processInitSpan.put("startTimestampMs", metrics.getAppStartTimeSpan().getStartTimestampMs()); - processInitSpan.put("endTimestampMs", metrics.getClassLoadedUptimeMs()); + processInitSpan.put("start_timestamp_ms", metrics.getAppStartTimeSpan().getstart_timestamp_ms()); + processInitSpan.put("end_timestamp_ms", metrics.getClassLoadedUptimeMs()); spans.add(processInitSpan); final @NotNull Map applicationOnCreateSpan = new HashMap<>(); applicationOnCreateSpan.put("description", "Process Initialization"); applicationOnCreateSpan.put( - "startTimestampMs", metrics.getAppStartTimeSpan().getStartTimestampMs()); + "start_timestamp_ms", metrics.getAppStartTimeSpan().getstart_timestamp_ms()); applicationOnCreateSpan.put( - "endTimestampMs", metrics.getAppStartTimeSpan().getProjectedStopTimestampMs()); + "end_timestamp_ms", metrics.getAppStartTimeSpan().getProjectedStopTimestampMs()); spans.add(applicationOnCreateSpan); for (final TimeSpan span : metrics.getContentProviderOnCreateTimeSpans()) { final @NotNull Map serializedSpan = new HashMap<>(); serializedSpan.put("description", span.getDescription()); - serializedSpan.put("startTimestampMs", span.getStartTimestampMs()); - serializedSpan.put("endTimestampMs", span.getProjectedStopTimestampMs()); + serializedSpan.put("start_timestamp_ms", span.getstart_timestamp_ms()); + serializedSpan.put("end_timestamp_ms", span.getProjectedStopTimestampMs()); spans.add(serializedSpan); } for (final ActivityLifecycleTimeSpan span : metrics.getActivityLifecycleTimeSpans()) { final @NotNull Map serializedOnCreateSpan = new HashMap<>(); serializedOnCreateSpan.put("description", span.getOnCreate().getDescription()); - serializedOnCreateSpan.put("startTimestampMs", span.getOnCreate().getStartTimestampMs()); + serializedOnCreateSpan.put("start_timestamp_ms", span.getOnCreate().getstart_timestamp_ms()); serializedOnCreateSpan.put( - "endTimestampMs", span.getOnCreate().getProjectedStopTimestampMs()); + "end_timestamp_ms", span.getOnCreate().getProjectedStopTimestampMs()); spans.add(serializedOnCreateSpan); final @NotNull Map serializedOnStartSpan = new HashMap<>(); serializedOnStartSpan.put("description", span.getOnStart().getDescription()); - serializedOnStartSpan.put("startTimestampMs", span.getOnStart().getStartTimestampMs()); - serializedOnStartSpan.put("endTimestampMs", span.getOnStart().getProjectedStopTimestampMs()); + serializedOnStartSpan.put("start_timestamp_ms", span.getOnStart().getstart_timestamp_ms()); + serializedOnStartSpan.put("end_timestamp_ms", span.getOnStart().getProjectedStopTimestampMs()); spans.add(serializedOnStartSpan); } From 995d170219e06a385ce5188f5bdf5c5c8975d75b Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 3 Jun 2024 09:39:30 +0200 Subject: [PATCH 3/7] fix span start timestamp getter method name --- .../java/io/sentry/android/core/InternalSentrySdk.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index 17f928a4d7..ed3f0e962c 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -201,14 +201,14 @@ public static Map getAppStartMeasurement() { final @NotNull Map processInitSpan = new HashMap<>(); processInitSpan.put("description", "Process Initialization"); - processInitSpan.put("start_timestamp_ms", metrics.getAppStartTimeSpan().getstart_timestamp_ms()); + processInitSpan.put("start_timestamp_ms", metrics.getAppStartTimeSpan().getStartTimestampMs()); processInitSpan.put("end_timestamp_ms", metrics.getClassLoadedUptimeMs()); spans.add(processInitSpan); final @NotNull Map applicationOnCreateSpan = new HashMap<>(); applicationOnCreateSpan.put("description", "Process Initialization"); applicationOnCreateSpan.put( - "start_timestamp_ms", metrics.getAppStartTimeSpan().getstart_timestamp_ms()); + "start_timestamp_ms", metrics.getAppStartTimeSpan().getStartTimestampMs()); applicationOnCreateSpan.put( "end_timestamp_ms", metrics.getAppStartTimeSpan().getProjectedStopTimestampMs()); spans.add(applicationOnCreateSpan); @@ -216,7 +216,7 @@ public static Map getAppStartMeasurement() { for (final TimeSpan span : metrics.getContentProviderOnCreateTimeSpans()) { final @NotNull Map serializedSpan = new HashMap<>(); serializedSpan.put("description", span.getDescription()); - serializedSpan.put("start_timestamp_ms", span.getstart_timestamp_ms()); + serializedSpan.put("start_timestamp_ms", span.getStartTimestampMs()); serializedSpan.put("end_timestamp_ms", span.getProjectedStopTimestampMs()); spans.add(serializedSpan); } @@ -224,14 +224,14 @@ public static Map getAppStartMeasurement() { for (final ActivityLifecycleTimeSpan span : metrics.getActivityLifecycleTimeSpans()) { final @NotNull Map serializedOnCreateSpan = new HashMap<>(); serializedOnCreateSpan.put("description", span.getOnCreate().getDescription()); - serializedOnCreateSpan.put("start_timestamp_ms", span.getOnCreate().getstart_timestamp_ms()); + serializedOnCreateSpan.put("start_timestamp_ms", span.getOnCreate().getStartTimestampMs()); serializedOnCreateSpan.put( "end_timestamp_ms", span.getOnCreate().getProjectedStopTimestampMs()); spans.add(serializedOnCreateSpan); final @NotNull Map serializedOnStartSpan = new HashMap<>(); serializedOnStartSpan.put("description", span.getOnStart().getDescription()); - serializedOnStartSpan.put("start_timestamp_ms", span.getOnStart().getstart_timestamp_ms()); + serializedOnStartSpan.put("start_timestamp_ms", span.getOnStart().getStartTimestampMs()); serializedOnStartSpan.put("end_timestamp_ms", span.getOnStart().getProjectedStopTimestampMs()); spans.add(serializedOnStartSpan); } From 25b78b4a74e039a4c879b15c0ad06e78fa5346d2 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Mon, 3 Jun 2024 07:43:57 +0000 Subject: [PATCH 4/7] Format code --- .../main/java/io/sentry/android/core/InternalSentrySdk.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index ed3f0e962c..f8f44919fa 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -195,7 +195,7 @@ public static SentryId captureEnvelope(final @NotNull byte[] envelopeData) { return null; } - public static Map getAppStartMeasurement() { + public static Map getAppStartMeasurement() { final @NotNull AppStartMetrics metrics = AppStartMetrics.getInstance(); final @NotNull List> spans = new ArrayList<>(); @@ -232,7 +232,8 @@ public static Map getAppStartMeasurement() { final @NotNull Map serializedOnStartSpan = new HashMap<>(); serializedOnStartSpan.put("description", span.getOnStart().getDescription()); serializedOnStartSpan.put("start_timestamp_ms", span.getOnStart().getStartTimestampMs()); - serializedOnStartSpan.put("end_timestamp_ms", span.getOnStart().getProjectedStopTimestampMs()); + serializedOnStartSpan.put( + "end_timestamp_ms", span.getOnStart().getProjectedStopTimestampMs()); spans.add(serializedOnStartSpan); } From e4ffc6ef3a81ff1515f2fe372a2e2f34dda4d6d6 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 3 Jun 2024 16:38:09 +0200 Subject: [PATCH 5/7] add tests --- .../android/core/InternalSentrySdk.java | 22 ++-- .../android/core/InternalSentrySdkTest.kt | 118 ++++++++++++++++++ 2 files changed, 133 insertions(+), 7 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index ed3f0e962c..a70fd25678 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -195,22 +195,29 @@ public static SentryId captureEnvelope(final @NotNull byte[] envelopeData) { return null; } - public static Map getAppStartMeasurement() { + public static Map getAppStartMeasurement() { final @NotNull AppStartMetrics metrics = AppStartMetrics.getInstance(); final @NotNull List> spans = new ArrayList<>(); + final @NotNull TimeSpan processInitNativeSpan = new TimeSpan(); + processInitNativeSpan.setStartedAt(metrics.getAppStartTimeSpan().getStartUptimeMs()); + processInitNativeSpan.setStartUnixTimeMs( + metrics.getAppStartTimeSpan().getStartTimestampMs()); // This has to go after setStartedAt + processInitNativeSpan.setStoppedAt(metrics.getClassLoadedUptimeMs()); + final @NotNull Map processInitSpan = new HashMap<>(); processInitSpan.put("description", "Process Initialization"); - processInitSpan.put("start_timestamp_ms", metrics.getAppStartTimeSpan().getStartTimestampMs()); - processInitSpan.put("end_timestamp_ms", metrics.getClassLoadedUptimeMs()); + processInitSpan.put("start_timestamp_ms", processInitNativeSpan.getStartTimestampMs()); + processInitSpan.put("end_timestamp_ms", processInitNativeSpan.getProjectedStopTimestampMs()); spans.add(processInitSpan); final @NotNull Map applicationOnCreateSpan = new HashMap<>(); - applicationOnCreateSpan.put("description", "Process Initialization"); applicationOnCreateSpan.put( - "start_timestamp_ms", metrics.getAppStartTimeSpan().getStartTimestampMs()); + "description", metrics.getApplicationOnCreateTimeSpan().getDescription()); + applicationOnCreateSpan.put( + "start_timestamp_ms", metrics.getApplicationOnCreateTimeSpan().getStartTimestampMs()); applicationOnCreateSpan.put( - "end_timestamp_ms", metrics.getAppStartTimeSpan().getProjectedStopTimestampMs()); + "end_timestamp_ms", metrics.getApplicationOnCreateTimeSpan().getProjectedStopTimestampMs()); spans.add(applicationOnCreateSpan); for (final TimeSpan span : metrics.getContentProviderOnCreateTimeSpans()) { @@ -232,7 +239,8 @@ public static Map getAppStartMeasurement() { final @NotNull Map serializedOnStartSpan = new HashMap<>(); serializedOnStartSpan.put("description", span.getOnStart().getDescription()); serializedOnStartSpan.put("start_timestamp_ms", span.getOnStart().getStartTimestampMs()); - serializedOnStartSpan.put("end_timestamp_ms", span.getOnStart().getProjectedStopTimestampMs()); + serializedOnStartSpan.put( + "end_timestamp_ms", span.getOnStart().getProjectedStopTimestampMs()); spans.add(serializedOnStartSpan); } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt index a10d8add7b..b62799e150 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt @@ -1,5 +1,7 @@ package io.sentry.android.core +import android.app.Application +import android.content.ContentProvider import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -17,6 +19,8 @@ import io.sentry.SentryExceptionFactory import io.sentry.SentryItemType import io.sentry.SentryOptions import io.sentry.Session +import io.sentry.android.core.performance.ActivityLifecycleTimeSpan +import io.sentry.android.core.performance.AppStartMetrics import io.sentry.exception.ExceptionMechanismException import io.sentry.protocol.App import io.sentry.protocol.Contexts @@ -101,6 +105,61 @@ class InternalSentrySdkTest { InternalSentrySdk.captureEnvelope(data) } + + fun mockFinishedAppStart() { + val metrics = AppStartMetrics.getInstance() + + metrics.appStartType = AppStartMetrics.AppStartType.WARM + + metrics.appStartTimeSpan.setStartedAt(20) // Can't be 0, as that's the default value if not set + metrics.appStartTimeSpan.setStartUnixTimeMs(20) // The order matters, unix time must be set after started at in tests to avoid overwrite + metrics.appStartTimeSpan.setStoppedAt(200) + metrics.classLoadedUptimeMs = 100 + + AppStartMetrics.onApplicationCreate(mock()) + metrics.applicationOnCreateTimeSpan.description = "Application created" + metrics.applicationOnCreateTimeSpan.setStartedAt(30) // Can't be 0, as that's the default value if not set + metrics.applicationOnCreateTimeSpan.setStartUnixTimeMs(30) // The order matters, unix time must be set after started at in tests to avoid overwrite + metrics.applicationOnCreateTimeSpan.setStoppedAt(40) + + val activityLifecycleSpan = ActivityLifecycleTimeSpan() + activityLifecycleSpan.onCreate.description = "Test Activity Lifecycle onCreate" + activityLifecycleSpan.onCreate.setStartedAt(50) // Can't be 0, as that's the default value if not set + activityLifecycleSpan.onCreate.setStartUnixTimeMs(50) // The order matters, unix time must be set after started at in tests to avoid overwrite + activityLifecycleSpan.onCreate.setStoppedAt(60) + + activityLifecycleSpan.onStart.description = "Test Activity Lifecycle onStart" + activityLifecycleSpan.onStart.setStartedAt(70) // Can't be 0, as that's the default value if not set + activityLifecycleSpan.onStart.setStartUnixTimeMs(70) // The order matters, unix time must be set after started at in tests to avoid overwrite + activityLifecycleSpan.onStart.setStoppedAt(80) + metrics.addActivityLifecycleTimeSpans(activityLifecycleSpan) + + AppStartMetrics.onContentProviderCreate(mock()) + metrics.contentProviderOnCreateTimeSpans[0].description = "Test Content Provider created" + metrics.contentProviderOnCreateTimeSpans[0].setStartedAt(90) + metrics.contentProviderOnCreateTimeSpans[0].setStartUnixTimeMs(90) + metrics.contentProviderOnCreateTimeSpans[0].setStoppedAt(100) + + metrics.appStartProfiler = mock() + metrics.appStartSamplingDecision = mock() + } + + fun mockMinimumFinishedAppStart() { + val metrics = AppStartMetrics.getInstance() + + metrics.appStartType = AppStartMetrics.AppStartType.WARM + + metrics.appStartTimeSpan.setStartedAt(20) // Can't be 0, as that's the default value if not set + metrics.appStartTimeSpan.setStartUnixTimeMs(20) // The order matters, unix time must be set after started at in tests to avoid overwrite + metrics.appStartTimeSpan.setStoppedAt(200) + metrics.classLoadedUptimeMs = 100 + + AppStartMetrics.onApplicationCreate(mock()) + metrics.applicationOnCreateTimeSpan.description = "Application created" + metrics.applicationOnCreateTimeSpan.setStartedAt(30) // Can't be 0, as that's the default value if not set + metrics.applicationOnCreateTimeSpan.setStartUnixTimeMs(30) // The order matters, unix time must be set after started at in tests to avoid overwrite + metrics.applicationOnCreateTimeSpan.setStoppedAt(40) + } } @BeforeTest @@ -302,4 +361,63 @@ class InternalSentrySdkTest { } assertEquals(Session.State.Crashed, scopeRef.get().session!!.status) } + + @Test + fun `getAppStartMeasurement returns correct serialized data from the app start instance`() { + Fixture().mockFinishedAppStart() + + val serializedAppStart = InternalSentrySdk.getAppStartMeasurement() + + assertEquals("warm", serializedAppStart["type"]) + assertEquals(20.0, serializedAppStart["app_start_timestamp_ms"]) + + val actualSpans = serializedAppStart["spans"] as List<*> + + val actualProcessSpan = actualSpans[0] as Map<*, *> + assertEquals("Process Initialization", actualProcessSpan["description"]) + assertEquals(20.toLong(), actualProcessSpan["start_timestamp_ms"]) + assertEquals(100.toLong(), actualProcessSpan["end_timestamp_ms"]) + + val actualAppSpan = actualSpans[1] as Map<*, *> + assertEquals("Application created", actualAppSpan["description"]) + assertEquals(30.toLong(), actualAppSpan["start_timestamp_ms"]) + assertEquals(40.toLong(), actualAppSpan["end_timestamp_ms"]) + + val actualContentProviderSpan = actualSpans[2] as Map<*, *> + assertEquals("Test Content Provider created", actualContentProviderSpan["description"]) + assertEquals(90.toLong(), actualContentProviderSpan["start_timestamp_ms"]) + assertEquals(100.toLong(), actualContentProviderSpan["end_timestamp_ms"]) + + val actualActivityOnCreateSpan = actualSpans[3] as Map<*, *> + assertEquals("Test Activity Lifecycle onCreate", actualActivityOnCreateSpan["description"]) + assertEquals(50.toLong(), actualActivityOnCreateSpan["start_timestamp_ms"]) + assertEquals(60.toLong(), actualActivityOnCreateSpan["end_timestamp_ms"]) + + val actualActivityOnStartSpan = actualSpans[4] as Map<*, *> + assertEquals("Test Activity Lifecycle onStart", actualActivityOnStartSpan["description"]) + assertEquals(70.toLong(), actualActivityOnStartSpan["start_timestamp_ms"]) + assertEquals(80.toLong(), actualActivityOnStartSpan["end_timestamp_ms"]) + } + + @Test + fun `getAppStartMeasurement returns correct serialized data from the minimum app start instance`() { + Fixture().mockMinimumFinishedAppStart() + + val serializedAppStart = InternalSentrySdk.getAppStartMeasurement() + + assertEquals("warm", serializedAppStart["type"]) + assertEquals(20.0, serializedAppStart["app_start_timestamp_ms"]) + + val actualSpans = serializedAppStart["spans"] as List<*> + + val actualProcessSpan = actualSpans[0] as Map<*, *> + assertEquals("Process Initialization", actualProcessSpan["description"]) + assertEquals(20.toLong(), actualProcessSpan["start_timestamp_ms"]) + assertEquals(100.toLong(), actualProcessSpan["end_timestamp_ms"]) + + val actualAppSpan = actualSpans[1] as Map<*, *> + assertEquals("Application created", actualAppSpan["description"]) + assertEquals(30.toLong(), actualAppSpan["start_timestamp_ms"]) + assertEquals(40.toLong(), actualAppSpan["end_timestamp_ms"]) + } } From 9ca881cccc0b6516bad23b0180f0b5d512efa819 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 4 Jun 2024 11:41:12 +0200 Subject: [PATCH 6/7] refactor, add test for unfinished spans --- .../api/sentry-android-core.api | 1 + .../android/core/InternalSentrySdk.java | 68 +++++++++---------- .../android/core/InternalSentrySdkTest.kt | 44 +++++++++++- 3 files changed, 75 insertions(+), 38 deletions(-) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 8eb017346d..40cae8a59d 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -204,6 +204,7 @@ public abstract interface class io/sentry/android/core/IDebugImagesLoader { public final class io/sentry/android/core/InternalSentrySdk { public fun ()V public static fun captureEnvelope ([B)Lio/sentry/protocol/SentryId; + public static fun getAppStartMeasurement ()Ljava/util/Map; public static fun getCurrentScope ()Lio/sentry/IScope; public static fun serializeScope (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/IScope;)Ljava/util/Map; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index a70fd25678..d5f58bc93b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -10,7 +10,6 @@ import io.sentry.IScope; import io.sentry.ISerializer; import io.sentry.ObjectWriter; -import io.sentry.SentryDate; import io.sentry.SentryEnvelope; import io.sentry.SentryEnvelopeItem; import io.sentry.SentryEvent; @@ -204,57 +203,54 @@ public static Map getAppStartMeasurement() { processInitNativeSpan.setStartUnixTimeMs( metrics.getAppStartTimeSpan().getStartTimestampMs()); // This has to go after setStartedAt processInitNativeSpan.setStoppedAt(metrics.getClassLoadedUptimeMs()); + processInitNativeSpan.setDescription("Process Initialization"); - final @NotNull Map processInitSpan = new HashMap<>(); - processInitSpan.put("description", "Process Initialization"); - processInitSpan.put("start_timestamp_ms", processInitNativeSpan.getStartTimestampMs()); - processInitSpan.put("end_timestamp_ms", processInitNativeSpan.getProjectedStopTimestampMs()); - spans.add(processInitSpan); - - final @NotNull Map applicationOnCreateSpan = new HashMap<>(); - applicationOnCreateSpan.put( - "description", metrics.getApplicationOnCreateTimeSpan().getDescription()); - applicationOnCreateSpan.put( - "start_timestamp_ms", metrics.getApplicationOnCreateTimeSpan().getStartTimestampMs()); - applicationOnCreateSpan.put( - "end_timestamp_ms", metrics.getApplicationOnCreateTimeSpan().getProjectedStopTimestampMs()); - spans.add(applicationOnCreateSpan); + addTimeSpanToSerializedSpans(processInitNativeSpan, spans); + addTimeSpanToSerializedSpans(metrics.getApplicationOnCreateTimeSpan(), spans); for (final TimeSpan span : metrics.getContentProviderOnCreateTimeSpans()) { - final @NotNull Map serializedSpan = new HashMap<>(); - serializedSpan.put("description", span.getDescription()); - serializedSpan.put("start_timestamp_ms", span.getStartTimestampMs()); - serializedSpan.put("end_timestamp_ms", span.getProjectedStopTimestampMs()); - spans.add(serializedSpan); + addTimeSpanToSerializedSpans(span, spans); } for (final ActivityLifecycleTimeSpan span : metrics.getActivityLifecycleTimeSpans()) { - final @NotNull Map serializedOnCreateSpan = new HashMap<>(); - serializedOnCreateSpan.put("description", span.getOnCreate().getDescription()); - serializedOnCreateSpan.put("start_timestamp_ms", span.getOnCreate().getStartTimestampMs()); - serializedOnCreateSpan.put( - "end_timestamp_ms", span.getOnCreate().getProjectedStopTimestampMs()); - spans.add(serializedOnCreateSpan); - - final @NotNull Map serializedOnStartSpan = new HashMap<>(); - serializedOnStartSpan.put("description", span.getOnStart().getDescription()); - serializedOnStartSpan.put("start_timestamp_ms", span.getOnStart().getStartTimestampMs()); - serializedOnStartSpan.put( - "end_timestamp_ms", span.getOnStart().getProjectedStopTimestampMs()); - spans.add(serializedOnStartSpan); + addTimeSpanToSerializedSpans(span.getOnCreate(), spans); + addTimeSpanToSerializedSpans(span.getOnStart(), spans); } final @NotNull Map result = new HashMap<>(); result.put("spans", spans); result.put("type", metrics.getAppStartType().toString().toLowerCase()); - final @Nullable SentryDate appStartTime = metrics.getAppStartTimeSpan().getStartTimestamp(); - if (appStartTime != null) { - result.put("app_start_timestamp_ms", DateUtils.nanosToMillis(appStartTime.nanoTimestamp())); + if (metrics.getAppStartTimeSpan().hasStarted()) { + result.put("app_start_timestamp_ms", metrics.getAppStartTimeSpan().getStartTimestampMs()); } return result; } + private static void addTimeSpanToSerializedSpans(TimeSpan span, List> spans) { + if (span.hasNotStarted()) { + HubAdapter.getInstance() + .getOptions() + .getLogger() + .log(SentryLevel.WARNING, "Can not convert not-started TimeSpan to Map for Hybrid SDKs."); + return; + } + + if (span.hasNotStopped()) { + HubAdapter.getInstance() + .getOptions() + .getLogger() + .log(SentryLevel.WARNING, "Can not convert not-stopped TimeSpan to Map for Hybrid SDKs."); + return; + } + + final @NotNull Map spanMap = new HashMap<>(); + spanMap.put("description", span.getDescription()); + spanMap.put("start_timestamp_ms", span.getStartTimestampMs()); + spanMap.put("end_timestamp_ms", span.getProjectedStopTimestampMs()); + spans.add(spanMap); + } + @Nullable private static Session updateSession( final @NotNull IHub hub, diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt index b62799e150..65ecdb3b8c 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt @@ -160,6 +160,26 @@ class InternalSentrySdkTest { metrics.applicationOnCreateTimeSpan.setStartUnixTimeMs(30) // The order matters, unix time must be set after started at in tests to avoid overwrite metrics.applicationOnCreateTimeSpan.setStoppedAt(40) } + + fun mockUnfinishedAppStart() { + val metrics = AppStartMetrics.getInstance() + + metrics.appStartType = AppStartMetrics.AppStartType.WARM + + metrics.appStartTimeSpan.setStartedAt(20) // Can't be 0, as that's the default value if not set + metrics.appStartTimeSpan.setStartUnixTimeMs(20) // The order matters, unix time must be set after started at in tests to avoid overwrite + metrics.appStartTimeSpan.setStoppedAt(200) + metrics.classLoadedUptimeMs = 100 + + AppStartMetrics.onApplicationCreate(mock()) + metrics.applicationOnCreateTimeSpan.description = "Application created" + metrics.applicationOnCreateTimeSpan.setStartedAt(30) // Can't be 0, as that's the default value if not set + + val activityLifecycleSpan = ActivityLifecycleTimeSpan() // Expect the created spans are not started nor stopped + activityLifecycleSpan.onCreate.description = "Test Activity Lifecycle onCreate" + activityLifecycleSpan.onStart.description = "Test Activity Lifecycle onStart" + metrics.addActivityLifecycleTimeSpans(activityLifecycleSpan) + } } @BeforeTest @@ -369,9 +389,10 @@ class InternalSentrySdkTest { val serializedAppStart = InternalSentrySdk.getAppStartMeasurement() assertEquals("warm", serializedAppStart["type"]) - assertEquals(20.0, serializedAppStart["app_start_timestamp_ms"]) + assertEquals(20.toLong(), serializedAppStart["app_start_timestamp_ms"]) val actualSpans = serializedAppStart["spans"] as List<*> + assertEquals(5, actualSpans.size) val actualProcessSpan = actualSpans[0] as Map<*, *> assertEquals("Process Initialization", actualProcessSpan["description"]) @@ -406,9 +427,10 @@ class InternalSentrySdkTest { val serializedAppStart = InternalSentrySdk.getAppStartMeasurement() assertEquals("warm", serializedAppStart["type"]) - assertEquals(20.0, serializedAppStart["app_start_timestamp_ms"]) + assertEquals(20.toLong(), serializedAppStart["app_start_timestamp_ms"]) val actualSpans = serializedAppStart["spans"] as List<*> + assertEquals(2, actualSpans.size) val actualProcessSpan = actualSpans[0] as Map<*, *> assertEquals("Process Initialization", actualProcessSpan["description"]) @@ -420,4 +442,22 @@ class InternalSentrySdkTest { assertEquals(30.toLong(), actualAppSpan["start_timestamp_ms"]) assertEquals(40.toLong(), actualAppSpan["end_timestamp_ms"]) } + + @Test + fun `getAppStartMeasurement returns only stopped spans in serialized data`() { + Fixture().mockUnfinishedAppStart() + + val serializedAppStart = InternalSentrySdk.getAppStartMeasurement() + + assertEquals("warm", serializedAppStart["type"]) + assertEquals(20.toLong(), serializedAppStart["app_start_timestamp_ms"]) + + val actualSpans = serializedAppStart["spans"] as List<*> + assertEquals(1, actualSpans.size) + + val actualProcessSpan = actualSpans[0] as Map<*, *> + assertEquals("Process Initialization", actualProcessSpan["description"]) + assertEquals(20.toLong(), actualProcessSpan["start_timestamp_ms"]) + assertEquals(100.toLong(), actualProcessSpan["end_timestamp_ms"]) + } } From f0c530a5dbf8b14ae191cde0bd0feacfd254597a Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 4 Jun 2024 15:00:58 +0200 Subject: [PATCH 7/7] fix locale --- .../main/java/io/sentry/android/core/InternalSentrySdk.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index d5f58bc93b..a4d1db09df 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.jetbrains.annotations.ApiStatus; @@ -219,7 +220,7 @@ public static Map getAppStartMeasurement() { final @NotNull Map result = new HashMap<>(); result.put("spans", spans); - result.put("type", metrics.getAppStartType().toString().toLowerCase()); + result.put("type", metrics.getAppStartType().toString().toLowerCase(Locale.ROOT)); if (metrics.getAppStartTimeSpan().hasStarted()) { result.put("app_start_timestamp_ms", metrics.getAppStartTimeSpan().getStartTimestampMs()); }