@@ -10,10 +10,17 @@ import io.sentry.SentryNanotimeDate
1010import io.sentry.SentryStackTraceFactory
1111import io.sentry.SpanDataConvention
1212import io.sentry.SpanStatus
13+ import java.util.Date
1314
1415private const val SQLITE_TRACE_ORIGIN = " auto.db.sqlite"
1516
16- /* * Shared span instrumentation for SQLite. */
17+ /* *
18+ * Sentinel for extracting a [SentryNanotimeDate]'s underlying [System.nanoTime] value via
19+ * [SentryDate.diff].
20+ */
21+ private val EMPTY_NANO_TIME = SentryNanotimeDate (Date (0 ), 0L )
22+
23+ /* * Span instrumentation for [SentrySQLiteDriver]. */
1724internal class SQLiteSpanInstrumentation (
1825 private val scopes : IScopes ,
1926 private val dbMetadata : DbMetadata ,
@@ -22,53 +29,32 @@ internal class SQLiteSpanInstrumentation(
2229 private val stackTraceFactory = SentryStackTraceFactory (scopes.options)
2330
2431 /* *
25- * Returns a start timestamp for a `db.sql.query` span.
32+ * Returns a timestamp in nanoseconds for use with [recordSpan]. Timestamp is ns-precise if the
33+ * active parent span uses a [SentryNanotimeDate] (the ordinary case); otherwise it's ms-precise.
2634 *
27- * Exposed so callers can capture a wall-clock start before accumulating database time.
28- * Internalizing the start time in [recordSpan] would shift spans to end-of-work on the trace
29- * timeline, which is less desirable .
35+ * Note: Internalizing the start time in [recordSpan] would shift spans to end-of-work on the
36+ * trace timeline, which is less desirable; callers capture the start before doing database work
37+ * and pass it back to [recordSpan] .
3038 */
31- fun startTimestamp (): SentryDate = scopes.options.dateProvider.now()
39+ fun startTimestamp (): Long =
40+ // Try to retain nanosecond precision + avoid SentryDate allocation...
41+ scopes.span?.childStartTimestampOrNull()
42+ // ...otherwise fall back to millisecond precision + allocate.
43+ ? : scopes.options.dateProvider.now().nanoTimestamp()
3244
33- /* * Records a `db.sql.query` span from [startTimestamp] to [startTimestamp] + [durationNanos] . */
45+ /* * Records a `db.sql.query` span. */
3446 fun recordSpan (
3547 sql : String ,
36- startTimestamp : SentryDate ,
48+ startTimestampNanos : Long ,
3749 durationNanos : Long ,
3850 status : SpanStatus ,
3951 throwable : Throwable ? = null,
4052 ) {
4153 val parent = scopes.span ? : return
42- val nanoPrecisionStart = startTimestamp.repairPrecision(anchor = parent.startDate)
43- val endTimestamp = SentryLongDate (nanoPrecisionStart.nanoTimestamp() + durationNanos)
44- parent.recordChild(sql, nanoPrecisionStart, endTimestamp, status, throwable)
45- }
46-
47- /* *
48- * Records a `db.sql.query` span from [startTimestamp] to the moment of invocation.
49- *
50- * "Coarse" in that it doesn't try to restore nanosecond precision for the start timestamp. Spans
51- * that start within the same wall clock millisecond will share the same start time and may be
52- * arbitrarily re-ordered by the Sentry UI.
53- */
54- fun recordCoarseSpan (
55- sql : String ,
56- startTimestamp : SentryDate ,
57- status : SpanStatus ,
58- throwable : Throwable ? = null,
59- ) {
60- val parent = scopes.span ? : return
61- parent.recordChild(sql, startTimestamp, endTimestamp = null , status, throwable)
62- }
54+ val startTimestamp = SentryLongDate (startTimestampNanos)
55+ val endTimestamp = SentryLongDate (startTimestampNanos + durationNanos)
6356
64- private fun ISpan.recordChild (
65- sql : String ,
66- startTimestamp : SentryDate ,
67- endTimestamp : SentryDate ? ,
68- status : SpanStatus ,
69- throwable : Throwable ? ,
70- ) {
71- startChild(" db.sql.query" , sql, startTimestamp, Instrumenter .SENTRY ).apply {
57+ parent.startChild(" db.sql.query" , sql, startTimestamp, Instrumenter .SENTRY ).apply {
7258 spanContext.origin = SQLITE_TRACE_ORIGIN
7359 throwable?.let { this .throwable = it }
7460
@@ -96,26 +82,15 @@ internal class SQLiteSpanInstrumentation(
9682 scopes : IScopes = ScopesAdapter .getInstance(),
9783 ): SQLiteSpanInstrumentation =
9884 SQLiteSpanInstrumentation (scopes, dbMetadataFromFileName(fileName))
99-
100- /* *
101- * Returns [SQLiteSpanInstrumentation] based on
102- * [SupportSQLiteOpenHelper.databaseName][androidx.sqlite.db.SupportSQLiteOpenHelper.databaseName].
103- */
104- fun fromDatabaseName (
105- databaseName : String? ,
106- scopes : IScopes = ScopesAdapter .getInstance(),
107- ): SQLiteSpanInstrumentation =
108- SQLiteSpanInstrumentation (scopes, dbMetadataFromDatabaseName(databaseName))
10985 }
11086}
11187
11288/* *
113- * Repairs the receiver's [nanoTimestamp][SentryDate.nanoTimestamp] if needed so that it actually
114- * has nanosecond precision.
89+ * Computes a start timestamp with nanosecond precision for the child of the receiver span. Returns
90+ * null if nanosecond precision isn't possible .
11591 *
116- * Designed for use with spans whose start timestamps are [SentryNanotimeDate]s. Without repair,
117- * those timestamps will be aligned to the same millisecond at transport, and the Sentry UI will
118- * arbitrarily reorder them:
92+ * Lets us improve the display of spans in the Sentry UI. If timestamps are only ms-precise, the
93+ * Sentry UI will left-align and arbitrarily reorder spans that share the same wall clock ms:
11994 * ```
12095 * (Relative start times out of order)
12196 * ↓
@@ -128,19 +103,22 @@ internal class SQLiteSpanInstrumentation(
128103 * even though their execution was staggered)
129104 * ```
130105 *
131- * Repair ensures proper ordering and lets the spans stagger:
106+ * Nanosecond precision ensures proper ordering and lets the spans stagger:
132107 * ```
133108 * Parent span ├█████████████┤
134109 * BEGIN IMMEDIATE TRANSACTION ├████┤ 0.02 ms
135110 * INSERT INTO `my_db` … ├██┤ 0.30 ms
136111 * END TRANSACTION ├███┤ 0.33 ms
137112 * ```
138113 */
139- internal fun SentryDate.repairPrecision (anchor : SentryDate ? ): SentryDate =
140- if (anchor is SentryNanotimeDate ) {
141- // Compute a new timestamp with nanosecond precision by using the anchor as the epoch instant
142- // and adding to it the diff of this.nanos - anchor.nanos.
143- SentryLongDate (anchor.laterDateNanosTimestampByDiff(this ))
144- } else {
145- this
114+ internal fun ISpan.childStartTimestampOrNull (): Long? {
115+ if (startDate !is SentryNanotimeDate ) {
116+ return null
146117 }
118+
119+ val parentWallClockNanos = startDate.nanoTimestamp()
120+ val parentMonotonicNanos = startDate.diff(EMPTY_NANO_TIME )
121+ val elapsedSinceParentStart = System .nanoTime() - parentMonotonicNanos
122+ // Return the child's absolute start time.
123+ return parentWallClockNanos + elapsedSinceParentStart
124+ }
0 commit comments