Skip to content

Commit 0f87b22

Browse files
committed
chore(compose): Update SentryTraced to emit a span only for initial composition
Commit updates SentryTraced to: - emit a span only for initial composition, - add the origin on ui.render spans, as we already do for composition spans. It also cleans up use of ImmutableHolder and renames it to MutableRef.
1 parent 5b2d372 commit 0f87b22

2 files changed

Lines changed: 63 additions & 36 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
### Improvements
1111

12+
- Emit a single `ui.compose` span per `SentryTraced` on initial composition instead of one on every recomposition, and set the `auto.ui.jetpack_compose` origin on `ui.render` spans ([#XXXX](https://github.com/getsentry/sentry-java/pull/XXXX))
1213
- Move ANR profiling out of experimental ([#6042](https://github.com/getsentry/sentry-java/pull/6042))
1314

1415
### Fixes

‎sentry-compose/src/androidMain/kotlin/io/sentry/compose/SentryComposeTracing.kt‎

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package io.sentry.compose
33
import androidx.compose.foundation.layout.Box
44
import androidx.compose.foundation.layout.BoxScope
55
import androidx.compose.runtime.Composable
6-
import androidx.compose.runtime.Immutable
76
import androidx.compose.runtime.SideEffect
87
import androidx.compose.runtime.compositionLocalOf
98
import androidx.compose.runtime.remember
@@ -25,45 +24,68 @@ private const val OP_RENDER = "ui.render"
2524
private const val OP_TRACE_ORIGIN = "auto.ui.jetpack_compose"
2625

2726
private val localSentryCompositionParentSpan = compositionLocalOf {
28-
ImmutableHolder(
29-
getRootSpan()
30-
?.startChild(
31-
OP_PARENT_COMPOSITION,
32-
"Jetpack Compose Initial Composition",
33-
SpanOptions().apply {
34-
isTrimStart = true
35-
isTrimEnd = true
36-
isIdle = true
37-
},
38-
)
39-
?.apply { spanContext.origin = OP_TRACE_ORIGIN }
40-
)
27+
getRootSpan()
28+
// We create a single parent span to be shared among *all* composition spans emitted by all
29+
// SentryTraced composables.
30+
?.startChild(
31+
OP_PARENT_COMPOSITION,
32+
"Jetpack Compose Initial Composition",
33+
SpanOptions().apply {
34+
isTrimStart = true
35+
isTrimEnd = true
36+
isIdle = true
37+
},
38+
)
39+
?.apply { spanContext.origin = OP_TRACE_ORIGIN }
4140
}
4241

4342
private val localSentryRenderingParentSpan = compositionLocalOf {
44-
ImmutableHolder(
45-
getRootSpan()
46-
?.startChild(
47-
OP_PARENT_RENDER,
48-
"Jetpack Compose Initial Render",
49-
SpanOptions().apply {
50-
isTrimStart = true
51-
isTrimEnd = true
52-
isIdle = true
53-
},
54-
)
55-
?.apply { spanContext.origin = OP_TRACE_ORIGIN }
56-
)
43+
getRootSpan()
44+
// We create a single parent span to be shared among *all* render spans emitted by all
45+
// SentryTraced composables.
46+
?.startChild(
47+
OP_PARENT_RENDER,
48+
"Jetpack Compose Initial Render",
49+
SpanOptions().apply {
50+
isTrimStart = true
51+
isTrimEnd = true
52+
isIdle = true
53+
},
54+
)
55+
?.apply { spanContext.origin = OP_TRACE_ORIGIN }
5756
}
5857

59-
@Immutable internal class ImmutableHolder<T>(var item: T)
58+
/**
59+
* A substitute for Compose's `MutableState` that doesn't register itself with the snapshot system,
60+
* so mutating [value] never triggers recomposition.
61+
*/
62+
private class MutableRef<T>(var value: T)
6063

6164
/**
62-
* Creates spans for tracking the time required to compose the wrapped [content], and a span for its
63-
* initial draw.
65+
* Creates a span for tracking the time required to compose the wrapped [content], and a span for
66+
* its initial draw.
6467
*
6568
* Spans are approximate and include work performed by any composables [content] invokes. Abandoned
6669
* recompositions are ignored.
70+
*
71+
* Generated spans live under a set of parents shared by all `SentryTraced` composables. Every
72+
* `SentryTraced` contributes one `ui.compose` child and one `ui.render` child:
73+
* ```
74+
* Root span
75+
* │
76+
* ├─ ui.compose.composition "Jetpack Compose Initial Composition"
77+
* │ ├─ ui.compose "product_info"
78+
* │ └─ ui.compose "add_to_cart_button"
79+
* │
80+
* └─ ui.compose.rendering "Jetpack Compose Initial Render"
81+
* ├─ ui.render "product_info"
82+
* └─ ui.render "add_to_cart_button"
83+
* ```
84+
*
85+
* Here `ui.compose.composition` and `ui.compose.rendering` are the shared parents. The
86+
* "product_info" spans are generated by a `SentryTraced` that wraps a product info composable, and
87+
* the "add_to_cart_button" spans are generated by a separate `SentryTraced` that wraps an
88+
* add-to-cart button.
6789
*/
6890
@ExperimentalComposeUiApi
6991
@Composable
@@ -73,11 +95,12 @@ public fun SentryTraced(
7395
enableUserInteractionTracing: Boolean = true,
7496
content: @Composable BoxScope.() -> Unit,
7597
) {
76-
val alreadyRendered = remember { ImmutableHolder(false) }
98+
val alreadyComposed = remember { MutableRef(false) }
99+
val alreadyRendered = remember { MutableRef(false) }
77100
val baseModifier = if (enableUserInteractionTracing) modifier.sentryTag(tag) else modifier
78101

79-
val parentCompositionSpan = localSentryCompositionParentSpan.current.item
80-
val parentRenderingSpan = localSentryRenderingParentSpan.current.item
102+
val parentCompositionSpan = localSentryCompositionParentSpan.current
103+
val parentRenderingSpan = localSentryRenderingParentSpan.current
81104
val dateProvider = Sentry.getCurrentScopes().options.dateProvider
82105

83106
// Only record spans if we have a parent for them.
@@ -86,14 +109,14 @@ public fun SentryTraced(
86109
Box(
87110
modifier =
88111
baseModifier.drawWithContent {
89-
if (alreadyRendered.item || parentRenderingSpan == null) {
112+
if (alreadyRendered.value || parentRenderingSpan == null) {
90113
drawContent()
91114
} else {
92115
val renderStart = dateProvider.now()
93116
drawContent()
94117
val renderEnd = dateProvider.now()
95118

96-
alreadyRendered.item = true
119+
alreadyRendered.value = true
97120
recordRenderSpan(parentRenderingSpan, tag, renderStart, renderEnd)
98121
}
99122
},
@@ -102,7 +125,7 @@ public fun SentryTraced(
102125
content()
103126
}
104127

105-
if (compositionStart != null) {
128+
if (!alreadyComposed.value && compositionStart != null) {
106129
val compositionEnd = dateProvider.now()
107130

108131
SideEffect {
@@ -112,6 +135,8 @@ public fun SentryTraced(
112135
startTimestamp = compositionStart,
113136
endTimestamp = compositionEnd,
114137
)
138+
139+
alreadyComposed.value = true
115140
}
116141
}
117142
}
@@ -141,6 +166,7 @@ private fun recordRenderSpan(
141166
endTimestamp: SentryDate,
142167
) {
143168
parentSpan?.startChild(OP_RENDER, tag, startTimestamp)?.apply {
169+
spanContext.origin = OP_TRACE_ORIGIN
144170
finish(null, endTimestamp)
145171
}
146172
}

0 commit comments

Comments
 (0)