Skip to content

Commit 0156268

Browse files
authored
Add start_type to app context (#3379)
* Add start_type to app context * Create app contexst in case it doesn't exist * Add changelog * Fix test fixtures * Fix tests
1 parent 61981dc commit 0156268

File tree

14 files changed

+138
-30
lines changed

14 files changed

+138
-30
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Add start_type to app context ([#3379](https://github.com/getsentry/sentry-java/pull/3379))
8+
59
### Fixes
610

711
- Fix timing metric value different from span duration ([#3368](https://github.com/getsentry/sentry-java/pull/3368))

sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.sentry.android.core.performance.ActivityLifecycleTimeSpan;
1717
import io.sentry.android.core.performance.AppStartMetrics;
1818
import io.sentry.android.core.performance.TimeSpan;
19+
import io.sentry.protocol.App;
1920
import io.sentry.protocol.MeasurementValue;
2021
import io.sentry.protocol.SentryId;
2122
import io.sentry.protocol.SentrySpan;
@@ -79,27 +80,40 @@ public SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) {
7980

8081
// the app start measurement is only sent once and only if the transaction has
8182
// the app.start span, which is automatically created by the SDK.
82-
if (!sentStartMeasurement && hasAppStartSpan(transaction)) {
83-
final @NotNull TimeSpan appStartTimeSpan =
84-
AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options);
85-
final long appStartUpDurationMs = appStartTimeSpan.getDurationMs();
86-
87-
// if appStartUpDurationMs is 0, metrics are not ready to be sent
88-
if (appStartUpDurationMs != 0) {
89-
final MeasurementValue value =
90-
new MeasurementValue(
91-
(float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName());
92-
93-
final String appStartKey =
94-
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
95-
? MeasurementValue.KEY_APP_START_COLD
96-
: MeasurementValue.KEY_APP_START_WARM;
97-
98-
transaction.getMeasurements().put(appStartKey, value);
99-
100-
attachColdAppStartSpans(AppStartMetrics.getInstance(), transaction);
101-
sentStartMeasurement = true;
83+
if (hasAppStartSpan(transaction)) {
84+
if (!sentStartMeasurement) {
85+
final @NotNull TimeSpan appStartTimeSpan =
86+
AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options);
87+
final long appStartUpDurationMs = appStartTimeSpan.getDurationMs();
88+
89+
// if appStartUpDurationMs is 0, metrics are not ready to be sent
90+
if (appStartUpDurationMs != 0) {
91+
final MeasurementValue value =
92+
new MeasurementValue(
93+
(float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName());
94+
95+
final String appStartKey =
96+
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
97+
? MeasurementValue.KEY_APP_START_COLD
98+
: MeasurementValue.KEY_APP_START_WARM;
99+
100+
transaction.getMeasurements().put(appStartKey, value);
101+
102+
attachColdAppStartSpans(AppStartMetrics.getInstance(), transaction);
103+
sentStartMeasurement = true;
104+
}
105+
}
106+
107+
@Nullable App appContext = transaction.getContexts().getApp();
108+
if (appContext == null) {
109+
appContext = new App();
110+
transaction.getContexts().setApp(appContext);
102111
}
112+
final String appStartType =
113+
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
114+
? "cold"
115+
: "warm";
116+
appContext.setStartType(appStartType);
103117
}
104118

105119
final SentryId eventId = transaction.getEventId();

sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import kotlin.test.BeforeTest
2727
import kotlin.test.Test
2828
import kotlin.test.assertEquals
2929
import kotlin.test.assertFalse
30+
import kotlin.test.assertNull
3031
import kotlin.test.assertTrue
3132

3233
@RunWith(AndroidJUnit4::class)
@@ -464,6 +465,60 @@ class PerformanceAndroidEventProcessorTest {
464465
}
465466
}
466467

468+
@Test
469+
fun `does not set start_type field for txns without app start span`() {
470+
// given some ui.load txn
471+
setAppStart(fixture.options, coldStart = true)
472+
473+
val sut = fixture.getSut(enablePerformanceV2 = true)
474+
val context = TransactionContext("Activity", UI_LOAD_OP)
475+
val tracer = SentryTracer(context, fixture.hub)
476+
var tr = SentryTransaction(tracer)
477+
478+
// when it contains no app start span and is processed
479+
tr = sut.process(tr, Hint())
480+
481+
// start_type should not be set
482+
assertNull(tr.contexts.app?.startType)
483+
}
484+
485+
@Test
486+
fun `sets start_type field for app context`() {
487+
// given some cold app start
488+
setAppStart(fixture.options, coldStart = true)
489+
490+
val sut = fixture.getSut(enablePerformanceV2 = true)
491+
val context = TransactionContext("Activity", UI_LOAD_OP)
492+
val tracer = SentryTracer(context, fixture.hub)
493+
var tr = SentryTransaction(tracer)
494+
495+
val appStartSpan = SentrySpan(
496+
0.0,
497+
1.0,
498+
tr.contexts.trace!!.traceId,
499+
SpanId(),
500+
null,
501+
APP_START_COLD,
502+
"App Start",
503+
SpanStatus.OK,
504+
null,
505+
emptyMap(),
506+
emptyMap(),
507+
null,
508+
null
509+
)
510+
tr.spans.add(appStartSpan)
511+
512+
// when the processor attaches the app start spans
513+
tr = sut.process(tr, Hint())
514+
515+
// start_type should be set as well
516+
assertEquals(
517+
"cold",
518+
tr.contexts.app!!.startType
519+
)
520+
}
521+
467522
private fun setAppStart(options: SentryAndroidOptions, coldStart: Boolean = true) {
468523
AppStartMetrics.getInstance().apply {
469524
appStartType = when (coldStart) {

sentry/api/sentry.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3643,6 +3643,7 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr
36433643
public fun getDeviceAppHash ()Ljava/lang/String;
36443644
public fun getInForeground ()Ljava/lang/Boolean;
36453645
public fun getPermissions ()Ljava/util/Map;
3646+
public fun getStartType ()Ljava/lang/String;
36463647
public fun getUnknown ()Ljava/util/Map;
36473648
public fun getViewNames ()Ljava/util/List;
36483649
public fun hashCode ()I
@@ -3656,6 +3657,7 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr
36563657
public fun setDeviceAppHash (Ljava/lang/String;)V
36573658
public fun setInForeground (Ljava/lang/Boolean;)V
36583659
public fun setPermissions (Ljava/util/Map;)V
3660+
public fun setStartType (Ljava/lang/String;)V
36593661
public fun setUnknown (Ljava/util/Map;)V
36603662
public fun setViewNames (Ljava/util/List;)V
36613663
}
@@ -3676,6 +3678,7 @@ public final class io/sentry/protocol/App$JsonKeys {
36763678
public static final field BUILD_TYPE Ljava/lang/String;
36773679
public static final field DEVICE_APP_HASH Ljava/lang/String;
36783680
public static final field IN_FOREGROUND Ljava/lang/String;
3681+
public static final field START_TYPE Ljava/lang/String;
36793682
public static final field VIEW_NAMES Ljava/lang/String;
36803683
public fun <init> ()V
36813684
}

sentry/src/main/java/io/sentry/protocol/App.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ public final class App implements JsonUnknown, JsonSerializable {
4040
private @Nullable String appBuild;
4141
/** Application permissions in the form of "permission_name" : "granted|not_granted" */
4242
private @Nullable Map<String, String> permissions;
43-
/** The list of the visibile UI screens * */
43+
/** The list of the visible UI screens * */
4444
private @Nullable List<String> viewNames;
45+
/** the app start type */
46+
private @Nullable String startType;
4547
/**
4648
* A flag indicating whether the app is in foreground or not. An app is in foreground when it's
4749
* visible to the user.
@@ -61,6 +63,7 @@ public App() {}
6163
this.permissions = CollectionUtils.newConcurrentHashMap(app.permissions);
6264
this.inForeground = app.inForeground;
6365
this.viewNames = CollectionUtils.newArrayList(app.viewNames);
66+
this.startType = app.startType;
6467
this.unknown = CollectionUtils.newConcurrentHashMap(app.unknown);
6568
}
6669

@@ -151,6 +154,15 @@ public void setViewNames(final @Nullable List<String> viewNames) {
151154
this.viewNames = viewNames;
152155
}
153156

157+
@Nullable
158+
public String getStartType() {
159+
return startType;
160+
}
161+
162+
public void setStartType(final @Nullable String startType) {
163+
this.startType = startType;
164+
}
165+
154166
@Override
155167
public boolean equals(Object o) {
156168
if (this == o) return true;
@@ -165,7 +177,8 @@ public boolean equals(Object o) {
165177
&& Objects.equals(appBuild, app.appBuild)
166178
&& Objects.equals(permissions, app.permissions)
167179
&& Objects.equals(inForeground, app.inForeground)
168-
&& Objects.equals(viewNames, app.viewNames);
180+
&& Objects.equals(viewNames, app.viewNames)
181+
&& Objects.equals(startType, app.startType);
169182
}
170183

171184
@Override
@@ -180,7 +193,8 @@ public int hashCode() {
180193
appBuild,
181194
permissions,
182195
inForeground,
183-
viewNames);
196+
viewNames,
197+
startType);
184198
}
185199

186200
// region json
@@ -207,6 +221,7 @@ public static final class JsonKeys {
207221
public static final String APP_PERMISSIONS = "permissions";
208222
public static final String IN_FOREGROUND = "in_foreground";
209223
public static final String VIEW_NAMES = "view_names";
224+
public static final String START_TYPE = "start_type";
210225
}
211226

212227
@Override
@@ -243,6 +258,9 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger
243258
if (viewNames != null) {
244259
writer.name(JsonKeys.VIEW_NAMES).value(logger, viewNames);
245260
}
261+
if (startType != null) {
262+
writer.name(JsonKeys.START_TYPE).value(startType);
263+
}
246264
if (unknown != null) {
247265
for (String key : unknown.keySet()) {
248266
Object value = unknown.get(key);
@@ -298,6 +316,9 @@ public static final class Deserializer implements JsonDeserializer<App> {
298316
app.setViewNames(viewNames);
299317
}
300318
break;
319+
case JsonKeys.START_TYPE:
320+
app.startType = reader.nextStringOrNull();
321+
break;
301322
default:
302323
if (unknown == null) {
303324
unknown = new ConcurrentHashMap<>();

sentry/src/test/java/io/sentry/protocol/AppSerializationTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class AppSerializationTest {
3131
)
3232
inForeground = true
3333
viewNames = listOf("MainActivity", "SidebarActivity")
34+
startType = "cold"
3435
}
3536
}
3637
private val fixture = Fixture()

sentry/src/test/java/io/sentry/protocol/AppTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class AppTest {
2121
app.permissions = mapOf(Pair("internet", "granted"))
2222
app.viewNames = listOf("MainActivity")
2323
app.inForeground = true
24+
app.startType = "cold"
2425
val unknown = mapOf(Pair("unknown", "unknown"))
2526
app.unknown = unknown
2627

@@ -49,6 +50,7 @@ class AppTest {
4950
app.permissions = mapOf(Pair("internet", "granted"))
5051
app.viewNames = listOf("MainActivity")
5152
app.inForeground = true
53+
app.startType = "cold"
5254
val unknown = mapOf(Pair("unknown", "unknown"))
5355
app.unknown = unknown
5456

@@ -67,6 +69,7 @@ class AppTest {
6769
assertEquals(listOf("MainActivity"), clone.viewNames)
6870

6971
assertEquals(true, clone.inForeground)
72+
assertEquals("cold", clone.startType)
7073
assertNotNull(clone.unknown) {
7174
assertEquals("unknown", it["unknown"])
7275
}

sentry/src/test/resources/json/app.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
"CAMERA": "granted"
1313
},
1414
"in_foreground": true,
15-
"view_names": ["MainActivity", "SidebarActivity"]
15+
"view_names": ["MainActivity", "SidebarActivity"],
16+
"start_type": "cold"
1617
}

sentry/src/test/resources/json/contexts.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"CAMERA": "granted"
1515
},
1616
"in_foreground": true,
17-
"view_names": ["MainActivity", "SidebarActivity"]
17+
"view_names": ["MainActivity", "SidebarActivity"],
18+
"start_type": "cold"
1819
},
1920
"browser":
2021
{

sentry/src/test/resources/json/sentry_base_event.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"CAMERA": "granted"
1818
},
1919
"in_foreground": true,
20-
"view_names": ["MainActivity", "SidebarActivity"]
20+
"view_names": ["MainActivity", "SidebarActivity"],
21+
"start_type": "cold"
2122
},
2223
"browser":
2324
{

0 commit comments

Comments
 (0)