react-native-nitro-tracing 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/NitroTracing.podspec +32 -0
- package/README.md +75 -0
- package/android/CMakeLists.txt +49 -0
- package/android/build.gradle +133 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/proguard-rules.pro +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +47 -0
- package/android/src/main/java/com/margelo/nitro/nitrotracing/HermesSamplingGuard.kt +43 -0
- package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingFrameSource.kt +36 -0
- package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingPackage.kt +31 -0
- package/client.ts +15 -0
- package/cpp/HermesSampling.cpp +26 -0
- package/cpp/HermesSampling.hpp +10 -0
- package/cpp/HybridRecording.cpp +66 -0
- package/cpp/HybridRecording.hpp +30 -0
- package/cpp/HybridTraceSpan.cpp +10 -0
- package/cpp/HybridTraceSpan.hpp +17 -0
- package/cpp/HybridTracing.cpp +10 -0
- package/cpp/HybridTracing.hpp +10 -0
- package/cpp/NativeConversions.cpp +77 -0
- package/cpp/NativeConversions.hpp +15 -0
- package/cpp/core/NativeSampler.cpp +152 -0
- package/cpp/core/NativeSampler.hpp +56 -0
- package/cpp/core/Recorder.cpp +218 -0
- package/cpp/core/Recorder.hpp +60 -0
- package/cpp/core/Retention.cpp +26 -0
- package/cpp/core/Retention.hpp +7 -0
- package/cpp/core/TraceJson.cpp +214 -0
- package/cpp/core/TraceJson.hpp +7 -0
- package/cpp/core/TraceTypes.hpp +62 -0
- package/expo.ts +2 -0
- package/ios/NitroTracingFrameSource.mm +35 -0
- package/lib/commonjs/client.cjs +767 -0
- package/lib/commonjs/client.cjs.map +7 -0
- package/lib/commonjs/expo.cjs +88 -0
- package/lib/commonjs/expo.cjs.map +7 -0
- package/lib/commonjs/index.cjs +28 -0
- package/lib/commonjs/index.cjs.map +7 -0
- package/lib/commonjs/performance.cjs +112 -0
- package/lib/commonjs/performance.cjs.map +7 -0
- package/lib/commonjs/plugins.cjs +477 -0
- package/lib/commonjs/plugins.cjs.map +7 -0
- package/lib/commonjs/react.cjs +3833 -0
- package/lib/commonjs/react.cjs.map +7 -0
- package/lib/commonjs/release-profiler.cjs +142 -0
- package/lib/commonjs/release-profiler.cjs.map +7 -0
- package/lib/commonjs/sentry.cjs +188 -0
- package/lib/commonjs/sentry.cjs.map +7 -0
- package/lib/module/client.mjs +768 -0
- package/lib/module/client.mjs.map +7 -0
- package/lib/module/expo.mjs +62 -0
- package/lib/module/expo.mjs.map +7 -0
- package/lib/module/index.mjs +7 -0
- package/lib/module/index.mjs.map +7 -0
- package/lib/module/performance.mjs +89 -0
- package/lib/module/performance.mjs.map +7 -0
- package/lib/module/plugins.mjs +461 -0
- package/lib/module/plugins.mjs.map +7 -0
- package/lib/module/react.mjs +3851 -0
- package/lib/module/react.mjs.map +7 -0
- package/lib/module/release-profiler.mjs +137 -0
- package/lib/module/release-profiler.mjs.map +7 -0
- package/lib/module/sentry.mjs +165 -0
- package/lib/module/sentry.mjs.map +7 -0
- package/lib/typescript/client.d.ts +4 -0
- package/lib/typescript/expo.d.ts +2 -0
- package/lib/typescript/performance.d.ts +2 -0
- package/lib/typescript/plugins.d.ts +12 -0
- package/lib/typescript/react.d.ts +5 -0
- package/lib/typescript/release-profiler.d.ts +2 -0
- package/lib/typescript/sentry.d.ts +2 -0
- package/lib/typescript/src/client/TracingSink.d.ts +10 -0
- package/lib/typescript/src/client/createTraceClient.d.ts +82 -0
- package/lib/typescript/src/client/playground.d.ts +12 -0
- package/lib/typescript/src/expo/ExpoAdapter.d.ts +15 -0
- package/lib/typescript/src/expo/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/plugins/TracePlugin.d.ts +22 -0
- package/lib/typescript/src/plugins/errors.d.ts +15 -0
- package/lib/typescript/src/plugins/nativeMetrics.d.ts +13 -0
- package/lib/typescript/src/plugins/navigation.d.ts +29 -0
- package/lib/typescript/src/plugins/network.d.ts +23 -0
- package/lib/typescript/src/plugins/orderSpans.d.ts +3 -0
- package/lib/typescript/src/plugins/performance.d.ts +13 -0
- package/lib/typescript/src/plugins/releaseProfiler.d.ts +25 -0
- package/lib/typescript/src/plugins/runtimeMetrics.d.ts +17 -0
- package/lib/typescript/src/plugins/sentry.d.ts +15 -0
- package/lib/typescript/src/plugins/startPlugins.d.ts +4 -0
- package/lib/typescript/src/react/DetailView.d.ts +11 -0
- package/lib/typescript/src/react/ExplorerFilters.d.ts +9 -0
- package/lib/typescript/src/react/ExplorerView.d.ts +9 -0
- package/lib/typescript/src/react/InspectorControls.d.ts +229 -0
- package/lib/typescript/src/react/InspectorHeader.d.ts +8 -0
- package/lib/typescript/src/react/InspectorSheet.d.ts +16 -0
- package/lib/typescript/src/react/MetricsView.d.ts +7 -0
- package/lib/typescript/src/react/SummaryView.d.ts +11 -0
- package/lib/typescript/src/react/TimelineView.d.ts +8 -0
- package/lib/typescript/src/react/TraceInspector.d.ts +11 -0
- package/lib/typescript/src/react/TraceOverlay.d.ts +17 -0
- package/lib/typescript/src/react/explorerModel.d.ts +38 -0
- package/lib/typescript/src/react/labels.d.ts +203 -0
- package/lib/typescript/src/react/native.d.ts +42 -0
- package/lib/typescript/src/react/readSnapshot.d.ts +4 -0
- package/lib/typescript/src/react/store.d.ts +9 -0
- package/lib/typescript/src/react/topics.d.ts +80 -0
- package/lib/typescript/src/react/useAppReadyMetric.d.ts +3 -0
- package/lib/typescript/src/react/useLiveRecording.d.ts +4 -0
- package/lib/typescript/src/react/useTraceViewer.d.ts +140 -0
- package/lib/typescript/src/react/viewerModel.d.ts +59 -0
- package/lib/typescript/src/specs/Recording.nitro.d.ts +31 -0
- package/lib/typescript/src/specs/TraceSpan.nitro.d.ts +14 -0
- package/lib/typescript/src/specs/Tracing.nitro.d.ts +17 -0
- package/lib/typescript/src/types/CompletedSpanOptions.d.ts +15 -0
- package/lib/typescript/src/types/EventContext.d.ts +8 -0
- package/lib/typescript/src/types/MarkEvent.d.ts +4 -0
- package/lib/typescript/src/types/MarkOptions.d.ts +6 -0
- package/lib/typescript/src/types/MetricEvent.d.ts +8 -0
- package/lib/typescript/src/types/MetricOptions.d.ts +10 -0
- package/lib/typescript/src/types/NativeSamplingOptions.d.ts +7 -0
- package/lib/typescript/src/types/ReadOptions.d.ts +7 -0
- package/lib/typescript/src/types/RecordingOptions.d.ts +11 -0
- package/lib/typescript/src/types/RecordingStats.d.ts +25 -0
- package/lib/typescript/src/types/SpanEvent.d.ts +13 -0
- package/lib/typescript/src/types/SpanOptions.d.ts +6 -0
- package/lib/typescript/src/types/SpanOutcome.d.ts +2 -0
- package/lib/typescript/src/types/TraceAttribute.d.ts +7 -0
- package/lib/typescript/src/types/TraceAttributes.d.ts +3 -0
- package/lib/typescript/src/types/TraceContext.d.ts +10 -0
- package/lib/typescript/src/types/TracePage.d.ts +18 -0
- package/lib/typescript/src/types.d.ts +17 -0
- package/nitro.json +20 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroTracing+autolinking.cmake +83 -0
- package/nitrogen/generated/android/NitroTracing+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroTracingOnLoad.cpp +49 -0
- package/nitrogen/generated/android/NitroTracingOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotracing/NitroTracingOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroTracing+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/NitroTracingAutolinking.mm +35 -0
- package/nitrogen/generated/ios/NitroTracingAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/CompletedSpanOptions.hpp +122 -0
- package/nitrogen/generated/shared/c++/HybridRecordingSpec.cpp +31 -0
- package/nitrogen/generated/shared/c++/HybridRecordingSpec.hpp +100 -0
- package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/HybridTracingSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridTracingSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/MarkEvent.hpp +102 -0
- package/nitrogen/generated/shared/c++/MarkOptions.hpp +99 -0
- package/nitrogen/generated/shared/c++/MetricEvent.hpp +110 -0
- package/nitrogen/generated/shared/c++/MetricOptions.hpp +107 -0
- package/nitrogen/generated/shared/c++/NativeSamplingOptions.hpp +87 -0
- package/nitrogen/generated/shared/c++/ReadOptions.hpp +87 -0
- package/nitrogen/generated/shared/c++/RecordingOptions.hpp +95 -0
- package/nitrogen/generated/shared/c++/RecordingStats.hpp +123 -0
- package/nitrogen/generated/shared/c++/SpanEvent.hpp +121 -0
- package/nitrogen/generated/shared/c++/SpanOptions.hpp +99 -0
- package/nitrogen/generated/shared/c++/SpanOutcome.hpp +84 -0
- package/nitrogen/generated/shared/c++/TraceAttribute.hpp +87 -0
- package/nitrogen/generated/shared/c++/TracePage.hpp +111 -0
- package/package.json +236 -0
- package/performance.ts +2 -0
- package/plugins.ts +24 -0
- package/react-native.config.js +16 -0
- package/react.ts +10 -0
- package/release-profiler.ts +6 -0
- package/sentry.ts +2 -0
- package/src/client/TracingSink.ts +25 -0
- package/src/client/createTraceClient.ts +439 -0
- package/src/client/playground.ts +143 -0
- package/src/expo/ExpoAdapter.ts +71 -0
- package/src/expo/index.ts +2 -0
- package/src/index.ts +8 -0
- package/src/plugins/TracePlugin.ts +22 -0
- package/src/plugins/errors.ts +69 -0
- package/src/plugins/nativeMetrics.ts +26 -0
- package/src/plugins/navigation.ts +111 -0
- package/src/plugins/network.ts +178 -0
- package/src/plugins/orderSpans.ts +22 -0
- package/src/plugins/performance.ts +120 -0
- package/src/plugins/releaseProfiler.ts +123 -0
- package/src/plugins/runtimeMetrics.ts +131 -0
- package/src/plugins/sentry.ts +176 -0
- package/src/plugins/startPlugins.ts +58 -0
- package/src/react/DetailView.tsx +514 -0
- package/src/react/ExplorerFilters.tsx +151 -0
- package/src/react/ExplorerView.tsx +243 -0
- package/src/react/InspectorControls.tsx +651 -0
- package/src/react/InspectorHeader.tsx +171 -0
- package/src/react/InspectorSheet.tsx +89 -0
- package/src/react/MetricsView.tsx +182 -0
- package/src/react/SummaryView.tsx +121 -0
- package/src/react/TimelineView.tsx +87 -0
- package/src/react/TraceInspector.tsx +196 -0
- package/src/react/TraceOverlay.tsx +585 -0
- package/src/react/explorerModel.ts +285 -0
- package/src/react/labels.ts +234 -0
- package/src/react/native.tsx +159 -0
- package/src/react/readSnapshot.ts +58 -0
- package/src/react/store.ts +31 -0
- package/src/react/topics.ts +427 -0
- package/src/react/useAppReadyMetric.ts +10 -0
- package/src/react/useLiveRecording.ts +43 -0
- package/src/react/useTraceViewer.ts +333 -0
- package/src/react/viewerModel.ts +172 -0
- package/src/specs/Recording.nitro.ts +40 -0
- package/src/specs/TraceSpan.nitro.ts +14 -0
- package/src/specs/Tracing.nitro.ts +14 -0
- package/src/types/CompletedSpanOptions.ts +15 -0
- package/src/types/EventContext.ts +8 -0
- package/src/types/MarkEvent.ts +3 -0
- package/src/types/MarkOptions.ts +6 -0
- package/src/types/MetricEvent.ts +8 -0
- package/src/types/MetricOptions.ts +10 -0
- package/src/types/NativeSamplingOptions.ts +7 -0
- package/src/types/ReadOptions.ts +7 -0
- package/src/types/RecordingOptions.ts +11 -0
- package/src/types/RecordingStats.ts +25 -0
- package/src/types/SpanEvent.ts +13 -0
- package/src/types/SpanOptions.ts +6 -0
- package/src/types/SpanOutcome.ts +2 -0
- package/src/types/TraceAttribute.ts +7 -0
- package/src/types/TraceAttributes.ts +4 -0
- package/src/types/TraceContext.ts +10 -0
- package/src/types/TracePage.ts +18 -0
- package/src/types.ts +17 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { TracePlugin } from './TracePlugin'
|
|
2
|
+
export interface RuntimeMetricsOptions {
|
|
3
|
+
/** Reporting interval in ms, minimum 250; default 1000. */
|
|
4
|
+
intervalMs?: number
|
|
5
|
+
/** Observe JS requestAnimationFrame callbacks (not native UI frames). Default true. */
|
|
6
|
+
frames?: boolean
|
|
7
|
+
}
|
|
8
|
+
/** Injectable clock/lifecycle surface for deterministic tests and alternate hosts. */
|
|
9
|
+
export interface RuntimeMetricsHost {
|
|
10
|
+
now(): number
|
|
11
|
+
isActive(): boolean
|
|
12
|
+
subscribe(listener: (active: boolean) => void): () => void
|
|
13
|
+
requestFrame(callback: () => void): number
|
|
14
|
+
cancelFrame(id: number): void
|
|
15
|
+
}
|
|
16
|
+
const nativeHost = (): RuntimeMetricsHost => {
|
|
17
|
+
const { AppState } = require('react-native') as typeof import('react-native')
|
|
18
|
+
return {
|
|
19
|
+
now: () => globalThis.performance.now(),
|
|
20
|
+
isActive: () => AppState.currentState === 'active',
|
|
21
|
+
subscribe: (listener) => {
|
|
22
|
+
const subscription = AppState.addEventListener('change', (state) =>
|
|
23
|
+
listener(state === 'active')
|
|
24
|
+
)
|
|
25
|
+
return () => subscription.remove()
|
|
26
|
+
},
|
|
27
|
+
requestFrame: (callback) => requestAnimationFrame(callback),
|
|
28
|
+
cancelFrame: (id) => cancelAnimationFrame(id),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Low-volume foreground JS health samples. No per-frame native calls or UI FPS claims. */
|
|
32
|
+
export function createRuntimeMetricsPlugin(
|
|
33
|
+
options: RuntimeMetricsOptions = {},
|
|
34
|
+
host?: RuntimeMetricsHost
|
|
35
|
+
): TracePlugin {
|
|
36
|
+
const interval = options.intervalMs ?? 1000
|
|
37
|
+
if (!Number.isFinite(interval) || interval < 250)
|
|
38
|
+
throw new Error('Runtime metrics interval must be at least 250ms')
|
|
39
|
+
return {
|
|
40
|
+
id: 'runtime-metrics',
|
|
41
|
+
start({ recording, reportError }) {
|
|
42
|
+
const api = host ?? nativeHost()
|
|
43
|
+
let stopped = false
|
|
44
|
+
let active = false
|
|
45
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
46
|
+
let frame: number | undefined
|
|
47
|
+
let started = 0
|
|
48
|
+
let previousFrame: number | undefined
|
|
49
|
+
let frames = 0
|
|
50
|
+
let maxGap = 0
|
|
51
|
+
let longGaps = 0
|
|
52
|
+
const reset = () => {
|
|
53
|
+
started = api.now()
|
|
54
|
+
frames = 0
|
|
55
|
+
maxGap = 0
|
|
56
|
+
longGaps = 0
|
|
57
|
+
}
|
|
58
|
+
const emit = (name: string, value: number, unit: string) => {
|
|
59
|
+
try {
|
|
60
|
+
recording.recordMetric({
|
|
61
|
+
name,
|
|
62
|
+
value,
|
|
63
|
+
unit,
|
|
64
|
+
correlationId: '',
|
|
65
|
+
attributes: [
|
|
66
|
+
{ key: 'source', value: 'js-runtime' },
|
|
67
|
+
{ key: 'scope', value: 'foreground' },
|
|
68
|
+
{ key: 'windowMs', value: String(api.now() - started) },
|
|
69
|
+
],
|
|
70
|
+
})
|
|
71
|
+
} catch (error) {
|
|
72
|
+
reportError(error)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const onFrame = () => {
|
|
76
|
+
if (stopped || !active) return
|
|
77
|
+
const now = api.now()
|
|
78
|
+
if (previousFrame !== undefined) {
|
|
79
|
+
const gap = Math.max(0, now - previousFrame)
|
|
80
|
+
maxGap = Math.max(maxGap, gap)
|
|
81
|
+
if (gap > 50) longGaps++
|
|
82
|
+
}
|
|
83
|
+
previousFrame = now
|
|
84
|
+
frames++
|
|
85
|
+
frame = api.requestFrame(onFrame)
|
|
86
|
+
}
|
|
87
|
+
const tick = () => {
|
|
88
|
+
if (stopped || !active) return
|
|
89
|
+
const elapsed = api.now() - started
|
|
90
|
+
emit('js.event_loop.delay', Math.max(0, elapsed - interval), 'ms')
|
|
91
|
+
if (options.frames !== false && elapsed > 0) {
|
|
92
|
+
emit(
|
|
93
|
+
'js.frame_callback.rate',
|
|
94
|
+
(frames * 1000) / elapsed,
|
|
95
|
+
'callbacks/s'
|
|
96
|
+
)
|
|
97
|
+
if (frames > 1) emit('js.frame_gap.max', maxGap, 'ms')
|
|
98
|
+
emit('js.frame_gap.over_50ms', longGaps, 'count')
|
|
99
|
+
}
|
|
100
|
+
reset()
|
|
101
|
+
timer = setTimeout(tick, interval)
|
|
102
|
+
}
|
|
103
|
+
const suspend = () => {
|
|
104
|
+
clearTimeout(timer)
|
|
105
|
+
if (frame !== undefined) api.cancelFrame(frame)
|
|
106
|
+
frame = undefined
|
|
107
|
+
}
|
|
108
|
+
const change = (next: boolean) => {
|
|
109
|
+
if (stopped || next === active) return
|
|
110
|
+
active = next
|
|
111
|
+
previousFrame = undefined
|
|
112
|
+
suspend()
|
|
113
|
+
reset()
|
|
114
|
+
if (active) {
|
|
115
|
+
timer = setTimeout(tick, interval)
|
|
116
|
+
if (options.frames !== false) frame = api.requestFrame(onFrame)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const unsubscribe = api.subscribe(change)
|
|
120
|
+
change(api.isActive())
|
|
121
|
+
return {
|
|
122
|
+
stop() {
|
|
123
|
+
stopped = true
|
|
124
|
+
active = false
|
|
125
|
+
suspend()
|
|
126
|
+
unsubscribe()
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type * as Sentry from '@sentry/react-native'
|
|
2
|
+
import type { SpanEvent } from '../types'
|
|
3
|
+
import type { TracePlugin } from './TracePlugin'
|
|
4
|
+
import { orderSpans } from './orderSpans'
|
|
5
|
+
/** Optional Sentry capabilities; SDK initialization and consent remain app-owned. */
|
|
6
|
+
export interface SentryPluginOptions {
|
|
7
|
+
/** Inject an installed SDK. This module has no runtime SDK dependency. */
|
|
8
|
+
sentry: Pick<typeof Sentry, 'getClient' | 'spanToJSON' | 'startInactiveSpan'>
|
|
9
|
+
/** Capture observable SDK spans locally. Defaults to false; sampling applies. */
|
|
10
|
+
captureSpans?: boolean
|
|
11
|
+
/** Explicitly opt into sending retained span names/timing/status to Sentry on stop. Defaults to false. */
|
|
12
|
+
exportSpans?: boolean
|
|
13
|
+
/** Only these sanitized custom attribute keys may leave the device. Defaults to none. */
|
|
14
|
+
exportedAttributeKeys?: readonly string[]
|
|
15
|
+
}
|
|
16
|
+
/** Optional SDK measurements and opt-in export; neither direction is enabled implicitly. */
|
|
17
|
+
export function createSentryPlugin(options: SentryPluginOptions): TracePlugin {
|
|
18
|
+
return {
|
|
19
|
+
id: 'sentry',
|
|
20
|
+
start({ recording, reportError }) {
|
|
21
|
+
const { sentry } = options
|
|
22
|
+
const stats = recording.getStats()
|
|
23
|
+
const anchor = stats.startedAtUnixMs
|
|
24
|
+
const client = sentry.getClient()
|
|
25
|
+
if (options.captureSpans && !client)
|
|
26
|
+
throw new Error('Initialize Sentry before enabling local capture')
|
|
27
|
+
/** Sentry as the measurement layer: its frame and app-start data use the native metric names. */
|
|
28
|
+
const recordMeasurements = (
|
|
29
|
+
data: ReturnType<typeof sentry.spanToJSON>,
|
|
30
|
+
start: number,
|
|
31
|
+
anchorMs: number
|
|
32
|
+
) => {
|
|
33
|
+
const end = (data.timestamp ?? data.start_timestamp) * 1000 - anchorMs
|
|
34
|
+
const metric = (name: string, value: unknown, unit: string) => {
|
|
35
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return
|
|
36
|
+
recording.recordMetric({
|
|
37
|
+
name,
|
|
38
|
+
value,
|
|
39
|
+
unit,
|
|
40
|
+
timestampMs: Math.max(0, end),
|
|
41
|
+
correlationId: data.trace_id,
|
|
42
|
+
attributes: [
|
|
43
|
+
{ key: 'source', value: 'sentry' },
|
|
44
|
+
{
|
|
45
|
+
key: 'span',
|
|
46
|
+
value: (data.description || data.op || '').slice(0, 256),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
// Set by Sentry's nativeFrames integration on spans it measured.
|
|
52
|
+
metric('ui.frames.total', data.data?.['frames.total'], 'count')
|
|
53
|
+
metric('ui.frames.slow', data.data?.['frames.slow'], 'count')
|
|
54
|
+
metric('ui.frames.frozen', data.data?.['frames.frozen'], 'count')
|
|
55
|
+
// appStart integration spans: app.start.cold / app.start.warm.
|
|
56
|
+
if (data.op === 'app.start.cold' || data.op === 'app.start.warm')
|
|
57
|
+
metric(data.op, end - start, 'ms')
|
|
58
|
+
}
|
|
59
|
+
const unsubscribe = options.captureSpans
|
|
60
|
+
? client!.on('spanEnd', (span) => {
|
|
61
|
+
try {
|
|
62
|
+
const data = sentry.spanToJSON(span)
|
|
63
|
+
if (data.data?.['nitro.exported'] || data.timestamp === undefined)
|
|
64
|
+
return
|
|
65
|
+
const start = data.start_timestamp * 1000 - anchor
|
|
66
|
+
if (start < 0) return
|
|
67
|
+
recording.recordSpan({
|
|
68
|
+
name: (data.description || data.op || 'sentry.span').slice(
|
|
69
|
+
0,
|
|
70
|
+
128
|
|
71
|
+
),
|
|
72
|
+
correlationId: data.trace_id,
|
|
73
|
+
timestampMs: start,
|
|
74
|
+
sourceSpanId: `sentry:${data.trace_id}:${data.span_id}`,
|
|
75
|
+
sourceParentSpanId: data.parent_span_id
|
|
76
|
+
? `sentry:${data.trace_id}:${data.parent_span_id}`
|
|
77
|
+
: undefined,
|
|
78
|
+
durationMs: Math.max(
|
|
79
|
+
0,
|
|
80
|
+
(data.timestamp - data.start_timestamp) * 1000
|
|
81
|
+
),
|
|
82
|
+
outcome:
|
|
83
|
+
data.status && data.status !== 'ok' ? 'error' : 'success',
|
|
84
|
+
attributes: [
|
|
85
|
+
{ key: 'source', value: 'sentry' },
|
|
86
|
+
{ key: 'sentry.span_id', value: data.span_id },
|
|
87
|
+
{
|
|
88
|
+
key: 'sentry.parent_span_id',
|
|
89
|
+
value: data.parent_span_id || '',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
})
|
|
93
|
+
recordMeasurements(data, start, anchor)
|
|
94
|
+
} catch (error) {
|
|
95
|
+
reportError(error)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
: undefined
|
|
99
|
+
return {
|
|
100
|
+
stop() {
|
|
101
|
+
unsubscribe?.()
|
|
102
|
+
},
|
|
103
|
+
async flush() {
|
|
104
|
+
if (options.exportSpans !== true) return
|
|
105
|
+
if (!sentry.getClient())
|
|
106
|
+
throw new Error('Initialize Sentry before exporting traces')
|
|
107
|
+
const spans: SpanEvent[] = []
|
|
108
|
+
let cursor = 0
|
|
109
|
+
while (true) {
|
|
110
|
+
const page = await recording.readEvents({
|
|
111
|
+
afterSequence: cursor,
|
|
112
|
+
limit: 1000,
|
|
113
|
+
})
|
|
114
|
+
spans.push(
|
|
115
|
+
...page.spans.filter(
|
|
116
|
+
(s) =>
|
|
117
|
+
!s.attributes.some(
|
|
118
|
+
(a) => a.key === 'source' && a.value === 'sentry'
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
if (page.nextSequence === cursor) break
|
|
123
|
+
cursor = page.nextSequence
|
|
124
|
+
}
|
|
125
|
+
const ordered = orderSpans(spans)
|
|
126
|
+
const handles = new Map<
|
|
127
|
+
string,
|
|
128
|
+
ReturnType<typeof sentry.startInactiveSpan>
|
|
129
|
+
>()
|
|
130
|
+
const keys = new Set(options.exportedAttributeKeys ?? [])
|
|
131
|
+
for (let index = 0; index < ordered.length; index++) {
|
|
132
|
+
if (index > 0 && index % 250 === 0)
|
|
133
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0))
|
|
134
|
+
const span = ordered[index]!
|
|
135
|
+
const id = span.spanId
|
|
136
|
+
const parentSpan = handles.get(span.parentSpanId)
|
|
137
|
+
const exported = sentry.startInactiveSpan({
|
|
138
|
+
name: span.name,
|
|
139
|
+
op: 'nitro.trace',
|
|
140
|
+
startTime: (anchor + span.timestampMs) / 1000,
|
|
141
|
+
parentSpan: parentSpan ?? null,
|
|
142
|
+
forceTransaction: !parentSpan,
|
|
143
|
+
attributes: {
|
|
144
|
+
...Object.fromEntries(
|
|
145
|
+
span.attributes
|
|
146
|
+
.filter((a) => keys.has(a.key))
|
|
147
|
+
.map((a) => [a.key, a.value])
|
|
148
|
+
),
|
|
149
|
+
'nitro.exported': true,
|
|
150
|
+
'nitro.span_id': id,
|
|
151
|
+
'nitro.parent_missing': Boolean(
|
|
152
|
+
span.parentSpanId && !parentSpan
|
|
153
|
+
),
|
|
154
|
+
'nitro.outcome': span.outcome,
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
exported.setStatus(
|
|
158
|
+
span.outcome === 'success'
|
|
159
|
+
? { code: 1 }
|
|
160
|
+
: { code: 2, message: span.outcome }
|
|
161
|
+
)
|
|
162
|
+
handles.set(id, exported)
|
|
163
|
+
}
|
|
164
|
+
for (let index = ordered.length - 1; index >= 0; index--) {
|
|
165
|
+
const span = ordered[index]!
|
|
166
|
+
handles
|
|
167
|
+
.get(span.spanId)!
|
|
168
|
+
.end((anchor + span.timestampMs + span.durationMs) / 1000)
|
|
169
|
+
if (index > 0 && index % 250 === 0)
|
|
170
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0))
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Recording } from '../specs/Recording.nitro'
|
|
2
|
+
import type { PluginHandle, TracePlugin } from './TracePlugin'
|
|
3
|
+
/** Start optional integrations. Failures are isolated; stop waits for all cleanup. */
|
|
4
|
+
export function startPlugins(
|
|
5
|
+
recording: Recording,
|
|
6
|
+
plugins: readonly TracePlugin[],
|
|
7
|
+
onError: (error: unknown) => void
|
|
8
|
+
): PluginHandle {
|
|
9
|
+
const handles: PluginHandle[] = []
|
|
10
|
+
const ids = new Set<string>()
|
|
11
|
+
for (const plugin of plugins) {
|
|
12
|
+
if (ids.has(plugin.id))
|
|
13
|
+
throw new Error(`Duplicate tracing plugin: ${plugin.id}`)
|
|
14
|
+
ids.add(plugin.id)
|
|
15
|
+
}
|
|
16
|
+
const reportError = (error: unknown) => {
|
|
17
|
+
try {
|
|
18
|
+
onError(error)
|
|
19
|
+
} catch {
|
|
20
|
+
/* Error reporters cannot break capture. */
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const plugin of plugins) {
|
|
24
|
+
try {
|
|
25
|
+
handles.push(plugin.start({ recording, reportError }))
|
|
26
|
+
} catch (error) {
|
|
27
|
+
reportError(error)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
let stopping: Promise<void> | undefined
|
|
31
|
+
return {
|
|
32
|
+
stop() {
|
|
33
|
+
if (!stopping)
|
|
34
|
+
stopping = (async () => {
|
|
35
|
+
for (const handle of [...handles].reverse()) {
|
|
36
|
+
try {
|
|
37
|
+
await handle.stop()
|
|
38
|
+
} catch (error) {
|
|
39
|
+
reportError(error)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
recording.stop()
|
|
44
|
+
} catch (error) {
|
|
45
|
+
reportError(error)
|
|
46
|
+
}
|
|
47
|
+
for (const handle of handles) {
|
|
48
|
+
try {
|
|
49
|
+
await handle.flush?.()
|
|
50
|
+
} catch (error) {
|
|
51
|
+
reportError(error)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})()
|
|
55
|
+
return stopping
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|