11package io.sentry.sqlite
22
33import io.sentry.IScopes
4+ import io.sentry.ISpan
45import io.sentry.Instrumenter
56import io.sentry.ScopesAdapter
67import io.sentry.SentryDate
78import io.sentry.SentryLongDate
9+ import io.sentry.SentryNanotimeDate
810import io.sentry.SentryStackTraceFactory
911import io.sentry.SpanDataConvention
1012import io.sentry.SpanStatus
@@ -28,36 +30,44 @@ internal class SQLiteSpanInstrumentation(
2830 */
2931 fun startTimestamp (): SentryDate = scopes.options.dateProvider.now()
3032
31- /* * Records a `db.sql.query` span from [startTimestamp] to the moment of invocation . */
33+ /* * Records a `db.sql.query` span from [startTimestamp] to [startTimestamp] + [durationNanos] . */
3234 fun recordSpan (
3335 sql : String ,
3436 startTimestamp : SentryDate ,
37+ durationNanos : Long ,
3538 status : SpanStatus ,
3639 throwable : Throwable ? = null,
3740 ) {
38- recordSpan(sql, startTimestamp, endTimestamp = null , status, throwable)
41+ val parent = scopes.span ? : return
42+ val nanoPrecisionStart = startTimestamp.repairPrecision(baseline = parent.startDate)
43+ val endTimestamp = SentryLongDate (nanoPrecisionStart.nanoTimestamp() + durationNanos)
44+ parent.recordChild(sql, nanoPrecisionStart, endTimestamp, status, throwable)
3945 }
4046
41- /* * Records a `db.sql.query` span from [startTimestamp] to [startTimestamp] + [durationNanos]. */
42- fun recordSpan (
47+ /* *
48+ * Records a `db.sql.query` span from [startTimestamp] to the moment of invocation.
49+ *
50+ * "Coarse" in that it doesn't ensure nanosecond precision for [SentryNanotimeDate]
51+ * [startTimestamp]s.
52+ */
53+ fun recordCoarseSpan (
4354 sql : String ,
4455 startTimestamp : SentryDate ,
45- durationNanos : Long ,
4656 status : SpanStatus ,
4757 throwable : Throwable ? = null,
4858 ) {
49- val endTimestamp = SentryLongDate (startTimestamp.nanoTimestamp() + durationNanos)
50- recordSpan (sql, startTimestamp, endTimestamp, status, throwable)
59+ val parent = scopes.span ? : return
60+ parent.recordChild (sql, startTimestamp, endTimestamp = null , status, throwable)
5161 }
5262
53- private fun recordSpan (
63+ private fun ISpan. recordChild (
5464 sql : String ,
5565 startTimestamp : SentryDate ,
5666 endTimestamp : SentryDate ? ,
5767 status : SpanStatus ,
5868 throwable : Throwable ? ,
5969 ) {
60- scopes.span?. startChild(" db.sql.query" , sql, startTimestamp, Instrumenter .SENTRY )? .apply {
70+ startChild(" db.sql.query" , sql, startTimestamp, Instrumenter .SENTRY ).apply {
6171 spanContext.origin = SQLITE_TRACE_ORIGIN
6272 throwable?.let { this .throwable = it }
6373
@@ -74,6 +84,38 @@ internal class SQLiteSpanInstrumentation(
7484 }
7585 }
7686
87+ /* *
88+ * Repairs the receiver's [nanoTimestamp][SentryDate.nanoTimestamp] if needed so that it actually
89+ * has nanosecond precision.
90+ *
91+ * Designed for use with spans whose start timestamps are [SentryNanotimeDate]s. Without repair,
92+ * those timestamps will be aligned to the same millisecond at transport, and the Sentry UI will
93+ * arbitrarily reorder them:
94+ * ```
95+ * Parent span ├█████████████┤
96+ * END TRANSACTION ├███┤ 0.18 ms ← (Wrong order)
97+ * BEGIN IMMEDIATE TRANSACTION ├████┤ 0.25 ms
98+ * INSERT INTO `my_db` … ├██┤ 0.10 ms
99+ * ↑
100+ * (All spans share the same ms baseline
101+ * even though their execution was staggered)
102+ * ```
103+ *
104+ * Repair ensures proper ordering and lets the spans stagger:
105+ * ```
106+ * Parent span ├█████████████┤
107+ * BEGIN IMMEDIATE TRANSACTION ├████┤ 0.25 ms
108+ * INSERT INTO `my_db` … ├██┤ 0.10 ms
109+ * END TRANSACTION ├███┤ 0.18 ms
110+ * ```
111+ */
112+ private fun SentryDate.repairPrecision (baseline : SentryDate ? ): SentryDate =
113+ if (baseline is SentryNanotimeDate ) {
114+ SentryLongDate (baseline.laterDateNanosTimestampByDiff(this ))
115+ } else {
116+ this
117+ }
118+
77119 companion object {
78120
79121 fun fromDatabaseName (databaseName : String? , scopes : IScopes = ScopesAdapter .getInstance()) =
0 commit comments