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

Kw/app start span for hybrid SDKs #3454

Merged
merged 8 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -193,6 +195,66 @@ public static SentryId captureEnvelope(final @NotNull byte[] envelopeData) {
return null;
}

public static Map<String, Object> getAppStartMeasurement() {
final @NotNull AppStartMetrics metrics = AppStartMetrics.getInstance();
final @NotNull List<Map<String, Object>> 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<String, Object> processInitSpan = new HashMap<>();
processInitSpan.put("description", "Process Initialization");
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
processInitSpan.put("start_timestamp_ms", processInitNativeSpan.getStartTimestampMs());
processInitSpan.put("end_timestamp_ms", processInitNativeSpan.getProjectedStopTimestampMs());
spans.add(processInitSpan);

final @NotNull Map<String, Object> applicationOnCreateSpan = new HashMap<>();
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
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);

for (final TimeSpan span : metrics.getContentProviderOnCreateTimeSpans()) {
final @NotNull Map<String, Object> 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);
}

for (final ActivityLifecycleTimeSpan span : metrics.getActivityLifecycleTimeSpans()) {
final @NotNull Map<String, Object> 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<String, Object> 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);
}

final @NotNull Map<String, Object> 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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
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<Application>())
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<ContentProvider>())
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<Application>())
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
Expand Down Expand Up @@ -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"])
}
}
Loading