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.
Files changed (233) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/NitroTracing.podspec +32 -0
  4. package/README.md +75 -0
  5. package/android/CMakeLists.txt +49 -0
  6. package/android/build.gradle +133 -0
  7. package/android/fix-prefab.gradle +51 -0
  8. package/android/gradle.properties +5 -0
  9. package/android/proguard-rules.pro +5 -0
  10. package/android/src/main/AndroidManifest.xml +2 -0
  11. package/android/src/main/cpp/cpp-adapter.cpp +47 -0
  12. package/android/src/main/java/com/margelo/nitro/nitrotracing/HermesSamplingGuard.kt +43 -0
  13. package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingFrameSource.kt +36 -0
  14. package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingPackage.kt +31 -0
  15. package/client.ts +15 -0
  16. package/cpp/HermesSampling.cpp +26 -0
  17. package/cpp/HermesSampling.hpp +10 -0
  18. package/cpp/HybridRecording.cpp +66 -0
  19. package/cpp/HybridRecording.hpp +30 -0
  20. package/cpp/HybridTraceSpan.cpp +10 -0
  21. package/cpp/HybridTraceSpan.hpp +17 -0
  22. package/cpp/HybridTracing.cpp +10 -0
  23. package/cpp/HybridTracing.hpp +10 -0
  24. package/cpp/NativeConversions.cpp +77 -0
  25. package/cpp/NativeConversions.hpp +15 -0
  26. package/cpp/core/NativeSampler.cpp +152 -0
  27. package/cpp/core/NativeSampler.hpp +56 -0
  28. package/cpp/core/Recorder.cpp +218 -0
  29. package/cpp/core/Recorder.hpp +60 -0
  30. package/cpp/core/Retention.cpp +26 -0
  31. package/cpp/core/Retention.hpp +7 -0
  32. package/cpp/core/TraceJson.cpp +214 -0
  33. package/cpp/core/TraceJson.hpp +7 -0
  34. package/cpp/core/TraceTypes.hpp +62 -0
  35. package/expo.ts +2 -0
  36. package/ios/NitroTracingFrameSource.mm +35 -0
  37. package/lib/commonjs/client.cjs +767 -0
  38. package/lib/commonjs/client.cjs.map +7 -0
  39. package/lib/commonjs/expo.cjs +88 -0
  40. package/lib/commonjs/expo.cjs.map +7 -0
  41. package/lib/commonjs/index.cjs +28 -0
  42. package/lib/commonjs/index.cjs.map +7 -0
  43. package/lib/commonjs/performance.cjs +112 -0
  44. package/lib/commonjs/performance.cjs.map +7 -0
  45. package/lib/commonjs/plugins.cjs +477 -0
  46. package/lib/commonjs/plugins.cjs.map +7 -0
  47. package/lib/commonjs/react.cjs +3833 -0
  48. package/lib/commonjs/react.cjs.map +7 -0
  49. package/lib/commonjs/release-profiler.cjs +142 -0
  50. package/lib/commonjs/release-profiler.cjs.map +7 -0
  51. package/lib/commonjs/sentry.cjs +188 -0
  52. package/lib/commonjs/sentry.cjs.map +7 -0
  53. package/lib/module/client.mjs +768 -0
  54. package/lib/module/client.mjs.map +7 -0
  55. package/lib/module/expo.mjs +62 -0
  56. package/lib/module/expo.mjs.map +7 -0
  57. package/lib/module/index.mjs +7 -0
  58. package/lib/module/index.mjs.map +7 -0
  59. package/lib/module/performance.mjs +89 -0
  60. package/lib/module/performance.mjs.map +7 -0
  61. package/lib/module/plugins.mjs +461 -0
  62. package/lib/module/plugins.mjs.map +7 -0
  63. package/lib/module/react.mjs +3851 -0
  64. package/lib/module/react.mjs.map +7 -0
  65. package/lib/module/release-profiler.mjs +137 -0
  66. package/lib/module/release-profiler.mjs.map +7 -0
  67. package/lib/module/sentry.mjs +165 -0
  68. package/lib/module/sentry.mjs.map +7 -0
  69. package/lib/typescript/client.d.ts +4 -0
  70. package/lib/typescript/expo.d.ts +2 -0
  71. package/lib/typescript/performance.d.ts +2 -0
  72. package/lib/typescript/plugins.d.ts +12 -0
  73. package/lib/typescript/react.d.ts +5 -0
  74. package/lib/typescript/release-profiler.d.ts +2 -0
  75. package/lib/typescript/sentry.d.ts +2 -0
  76. package/lib/typescript/src/client/TracingSink.d.ts +10 -0
  77. package/lib/typescript/src/client/createTraceClient.d.ts +82 -0
  78. package/lib/typescript/src/client/playground.d.ts +12 -0
  79. package/lib/typescript/src/expo/ExpoAdapter.d.ts +15 -0
  80. package/lib/typescript/src/expo/index.d.ts +2 -0
  81. package/lib/typescript/src/index.d.ts +6 -0
  82. package/lib/typescript/src/plugins/TracePlugin.d.ts +22 -0
  83. package/lib/typescript/src/plugins/errors.d.ts +15 -0
  84. package/lib/typescript/src/plugins/nativeMetrics.d.ts +13 -0
  85. package/lib/typescript/src/plugins/navigation.d.ts +29 -0
  86. package/lib/typescript/src/plugins/network.d.ts +23 -0
  87. package/lib/typescript/src/plugins/orderSpans.d.ts +3 -0
  88. package/lib/typescript/src/plugins/performance.d.ts +13 -0
  89. package/lib/typescript/src/plugins/releaseProfiler.d.ts +25 -0
  90. package/lib/typescript/src/plugins/runtimeMetrics.d.ts +17 -0
  91. package/lib/typescript/src/plugins/sentry.d.ts +15 -0
  92. package/lib/typescript/src/plugins/startPlugins.d.ts +4 -0
  93. package/lib/typescript/src/react/DetailView.d.ts +11 -0
  94. package/lib/typescript/src/react/ExplorerFilters.d.ts +9 -0
  95. package/lib/typescript/src/react/ExplorerView.d.ts +9 -0
  96. package/lib/typescript/src/react/InspectorControls.d.ts +229 -0
  97. package/lib/typescript/src/react/InspectorHeader.d.ts +8 -0
  98. package/lib/typescript/src/react/InspectorSheet.d.ts +16 -0
  99. package/lib/typescript/src/react/MetricsView.d.ts +7 -0
  100. package/lib/typescript/src/react/SummaryView.d.ts +11 -0
  101. package/lib/typescript/src/react/TimelineView.d.ts +8 -0
  102. package/lib/typescript/src/react/TraceInspector.d.ts +11 -0
  103. package/lib/typescript/src/react/TraceOverlay.d.ts +17 -0
  104. package/lib/typescript/src/react/explorerModel.d.ts +38 -0
  105. package/lib/typescript/src/react/labels.d.ts +203 -0
  106. package/lib/typescript/src/react/native.d.ts +42 -0
  107. package/lib/typescript/src/react/readSnapshot.d.ts +4 -0
  108. package/lib/typescript/src/react/store.d.ts +9 -0
  109. package/lib/typescript/src/react/topics.d.ts +80 -0
  110. package/lib/typescript/src/react/useAppReadyMetric.d.ts +3 -0
  111. package/lib/typescript/src/react/useLiveRecording.d.ts +4 -0
  112. package/lib/typescript/src/react/useTraceViewer.d.ts +140 -0
  113. package/lib/typescript/src/react/viewerModel.d.ts +59 -0
  114. package/lib/typescript/src/specs/Recording.nitro.d.ts +31 -0
  115. package/lib/typescript/src/specs/TraceSpan.nitro.d.ts +14 -0
  116. package/lib/typescript/src/specs/Tracing.nitro.d.ts +17 -0
  117. package/lib/typescript/src/types/CompletedSpanOptions.d.ts +15 -0
  118. package/lib/typescript/src/types/EventContext.d.ts +8 -0
  119. package/lib/typescript/src/types/MarkEvent.d.ts +4 -0
  120. package/lib/typescript/src/types/MarkOptions.d.ts +6 -0
  121. package/lib/typescript/src/types/MetricEvent.d.ts +8 -0
  122. package/lib/typescript/src/types/MetricOptions.d.ts +10 -0
  123. package/lib/typescript/src/types/NativeSamplingOptions.d.ts +7 -0
  124. package/lib/typescript/src/types/ReadOptions.d.ts +7 -0
  125. package/lib/typescript/src/types/RecordingOptions.d.ts +11 -0
  126. package/lib/typescript/src/types/RecordingStats.d.ts +25 -0
  127. package/lib/typescript/src/types/SpanEvent.d.ts +13 -0
  128. package/lib/typescript/src/types/SpanOptions.d.ts +6 -0
  129. package/lib/typescript/src/types/SpanOutcome.d.ts +2 -0
  130. package/lib/typescript/src/types/TraceAttribute.d.ts +7 -0
  131. package/lib/typescript/src/types/TraceAttributes.d.ts +3 -0
  132. package/lib/typescript/src/types/TraceContext.d.ts +10 -0
  133. package/lib/typescript/src/types/TracePage.d.ts +18 -0
  134. package/lib/typescript/src/types.d.ts +17 -0
  135. package/nitro.json +20 -0
  136. package/nitrogen/generated/.gitattributes +1 -0
  137. package/nitrogen/generated/android/NitroTracing+autolinking.cmake +83 -0
  138. package/nitrogen/generated/android/NitroTracing+autolinking.gradle +27 -0
  139. package/nitrogen/generated/android/NitroTracingOnLoad.cpp +49 -0
  140. package/nitrogen/generated/android/NitroTracingOnLoad.hpp +34 -0
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotracing/NitroTracingOnLoad.kt +35 -0
  142. package/nitrogen/generated/ios/NitroTracing+autolinking.rb +62 -0
  143. package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.cpp +17 -0
  144. package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.hpp +27 -0
  145. package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Umbrella.hpp +38 -0
  146. package/nitrogen/generated/ios/NitroTracingAutolinking.mm +35 -0
  147. package/nitrogen/generated/ios/NitroTracingAutolinking.swift +16 -0
  148. package/nitrogen/generated/shared/c++/CompletedSpanOptions.hpp +122 -0
  149. package/nitrogen/generated/shared/c++/HybridRecordingSpec.cpp +31 -0
  150. package/nitrogen/generated/shared/c++/HybridRecordingSpec.hpp +100 -0
  151. package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.cpp +23 -0
  152. package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.hpp +65 -0
  153. package/nitrogen/generated/shared/c++/HybridTracingSpec.cpp +22 -0
  154. package/nitrogen/generated/shared/c++/HybridTracingSpec.hpp +68 -0
  155. package/nitrogen/generated/shared/c++/MarkEvent.hpp +102 -0
  156. package/nitrogen/generated/shared/c++/MarkOptions.hpp +99 -0
  157. package/nitrogen/generated/shared/c++/MetricEvent.hpp +110 -0
  158. package/nitrogen/generated/shared/c++/MetricOptions.hpp +107 -0
  159. package/nitrogen/generated/shared/c++/NativeSamplingOptions.hpp +87 -0
  160. package/nitrogen/generated/shared/c++/ReadOptions.hpp +87 -0
  161. package/nitrogen/generated/shared/c++/RecordingOptions.hpp +95 -0
  162. package/nitrogen/generated/shared/c++/RecordingStats.hpp +123 -0
  163. package/nitrogen/generated/shared/c++/SpanEvent.hpp +121 -0
  164. package/nitrogen/generated/shared/c++/SpanOptions.hpp +99 -0
  165. package/nitrogen/generated/shared/c++/SpanOutcome.hpp +84 -0
  166. package/nitrogen/generated/shared/c++/TraceAttribute.hpp +87 -0
  167. package/nitrogen/generated/shared/c++/TracePage.hpp +111 -0
  168. package/package.json +236 -0
  169. package/performance.ts +2 -0
  170. package/plugins.ts +24 -0
  171. package/react-native.config.js +16 -0
  172. package/react.ts +10 -0
  173. package/release-profiler.ts +6 -0
  174. package/sentry.ts +2 -0
  175. package/src/client/TracingSink.ts +25 -0
  176. package/src/client/createTraceClient.ts +439 -0
  177. package/src/client/playground.ts +143 -0
  178. package/src/expo/ExpoAdapter.ts +71 -0
  179. package/src/expo/index.ts +2 -0
  180. package/src/index.ts +8 -0
  181. package/src/plugins/TracePlugin.ts +22 -0
  182. package/src/plugins/errors.ts +69 -0
  183. package/src/plugins/nativeMetrics.ts +26 -0
  184. package/src/plugins/navigation.ts +111 -0
  185. package/src/plugins/network.ts +178 -0
  186. package/src/plugins/orderSpans.ts +22 -0
  187. package/src/plugins/performance.ts +120 -0
  188. package/src/plugins/releaseProfiler.ts +123 -0
  189. package/src/plugins/runtimeMetrics.ts +131 -0
  190. package/src/plugins/sentry.ts +176 -0
  191. package/src/plugins/startPlugins.ts +58 -0
  192. package/src/react/DetailView.tsx +514 -0
  193. package/src/react/ExplorerFilters.tsx +151 -0
  194. package/src/react/ExplorerView.tsx +243 -0
  195. package/src/react/InspectorControls.tsx +651 -0
  196. package/src/react/InspectorHeader.tsx +171 -0
  197. package/src/react/InspectorSheet.tsx +89 -0
  198. package/src/react/MetricsView.tsx +182 -0
  199. package/src/react/SummaryView.tsx +121 -0
  200. package/src/react/TimelineView.tsx +87 -0
  201. package/src/react/TraceInspector.tsx +196 -0
  202. package/src/react/TraceOverlay.tsx +585 -0
  203. package/src/react/explorerModel.ts +285 -0
  204. package/src/react/labels.ts +234 -0
  205. package/src/react/native.tsx +159 -0
  206. package/src/react/readSnapshot.ts +58 -0
  207. package/src/react/store.ts +31 -0
  208. package/src/react/topics.ts +427 -0
  209. package/src/react/useAppReadyMetric.ts +10 -0
  210. package/src/react/useLiveRecording.ts +43 -0
  211. package/src/react/useTraceViewer.ts +333 -0
  212. package/src/react/viewerModel.ts +172 -0
  213. package/src/specs/Recording.nitro.ts +40 -0
  214. package/src/specs/TraceSpan.nitro.ts +14 -0
  215. package/src/specs/Tracing.nitro.ts +14 -0
  216. package/src/types/CompletedSpanOptions.ts +15 -0
  217. package/src/types/EventContext.ts +8 -0
  218. package/src/types/MarkEvent.ts +3 -0
  219. package/src/types/MarkOptions.ts +6 -0
  220. package/src/types/MetricEvent.ts +8 -0
  221. package/src/types/MetricOptions.ts +10 -0
  222. package/src/types/NativeSamplingOptions.ts +7 -0
  223. package/src/types/ReadOptions.ts +7 -0
  224. package/src/types/RecordingOptions.ts +11 -0
  225. package/src/types/RecordingStats.ts +25 -0
  226. package/src/types/SpanEvent.ts +13 -0
  227. package/src/types/SpanOptions.ts +6 -0
  228. package/src/types/SpanOutcome.ts +2 -0
  229. package/src/types/TraceAttribute.ts +7 -0
  230. package/src/types/TraceAttributes.ts +4 -0
  231. package/src/types/TraceContext.ts +10 -0
  232. package/src/types/TracePage.ts +18 -0
  233. package/src/types.ts +17 -0
@@ -0,0 +1,768 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
+ }) : x)(function(x) {
8
+ if (typeof require !== "undefined") return require.apply(this, arguments);
9
+ throw Error('Dynamic require of "' + x + '" is not supported');
10
+ });
11
+ var __esm = (fn, res, err) => function __init() {
12
+ if (err) throw err[0];
13
+ try {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ } catch (e) {
16
+ throw err = [e], e;
17
+ }
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, { get: all[name], enumerable: true });
22
+ };
23
+ var __copyProps = (to, from, except, desc) => {
24
+ if (from && typeof from === "object" || typeof from === "function") {
25
+ for (let key of __getOwnPropNames(from))
26
+ if (!__hasOwnProp.call(to, key) && key !== except)
27
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
28
+ }
29
+ return to;
30
+ };
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/index.ts
34
+ var src_exports = {};
35
+ __export(src_exports, {
36
+ Tracing: () => Tracing
37
+ });
38
+ import { NitroModules } from "react-native-nitro-modules";
39
+ var Tracing;
40
+ var init_src = __esm({
41
+ "src/index.ts"() {
42
+ "use strict";
43
+ Tracing = NitroModules.createHybridObject("Tracing");
44
+ }
45
+ });
46
+
47
+ // src/plugins/runtimeMetrics.ts
48
+ var nativeHost = () => {
49
+ const { AppState } = __require("react-native");
50
+ return {
51
+ now: () => globalThis.performance.now(),
52
+ isActive: () => AppState.currentState === "active",
53
+ subscribe: (listener) => {
54
+ const subscription = AppState.addEventListener(
55
+ "change",
56
+ (state) => listener(state === "active")
57
+ );
58
+ return () => subscription.remove();
59
+ },
60
+ requestFrame: (callback) => requestAnimationFrame(callback),
61
+ cancelFrame: (id) => cancelAnimationFrame(id)
62
+ };
63
+ };
64
+ function createRuntimeMetricsPlugin(options = {}, host) {
65
+ const interval = options.intervalMs ?? 1e3;
66
+ if (!Number.isFinite(interval) || interval < 250)
67
+ throw new Error("Runtime metrics interval must be at least 250ms");
68
+ return {
69
+ id: "runtime-metrics",
70
+ start({ recording, reportError }) {
71
+ const api = host ?? nativeHost();
72
+ let stopped = false;
73
+ let active = false;
74
+ let timer;
75
+ let frame;
76
+ let started = 0;
77
+ let previousFrame;
78
+ let frames = 0;
79
+ let maxGap = 0;
80
+ let longGaps = 0;
81
+ const reset = () => {
82
+ started = api.now();
83
+ frames = 0;
84
+ maxGap = 0;
85
+ longGaps = 0;
86
+ };
87
+ const emit = (name, value, unit) => {
88
+ try {
89
+ recording.recordMetric({
90
+ name,
91
+ value,
92
+ unit,
93
+ correlationId: "",
94
+ attributes: [
95
+ { key: "source", value: "js-runtime" },
96
+ { key: "scope", value: "foreground" },
97
+ { key: "windowMs", value: String(api.now() - started) }
98
+ ]
99
+ });
100
+ } catch (error) {
101
+ reportError(error);
102
+ }
103
+ };
104
+ const onFrame = () => {
105
+ if (stopped || !active) return;
106
+ const now = api.now();
107
+ if (previousFrame !== void 0) {
108
+ const gap = Math.max(0, now - previousFrame);
109
+ maxGap = Math.max(maxGap, gap);
110
+ if (gap > 50) longGaps++;
111
+ }
112
+ previousFrame = now;
113
+ frames++;
114
+ frame = api.requestFrame(onFrame);
115
+ };
116
+ const tick = () => {
117
+ if (stopped || !active) return;
118
+ const elapsed = api.now() - started;
119
+ emit("js.event_loop.delay", Math.max(0, elapsed - interval), "ms");
120
+ if (options.frames !== false && elapsed > 0) {
121
+ emit(
122
+ "js.frame_callback.rate",
123
+ frames * 1e3 / elapsed,
124
+ "callbacks/s"
125
+ );
126
+ if (frames > 1) emit("js.frame_gap.max", maxGap, "ms");
127
+ emit("js.frame_gap.over_50ms", longGaps, "count");
128
+ }
129
+ reset();
130
+ timer = setTimeout(tick, interval);
131
+ };
132
+ const suspend = () => {
133
+ clearTimeout(timer);
134
+ if (frame !== void 0) api.cancelFrame(frame);
135
+ frame = void 0;
136
+ };
137
+ const change = (next) => {
138
+ if (stopped || next === active) return;
139
+ active = next;
140
+ previousFrame = void 0;
141
+ suspend();
142
+ reset();
143
+ if (active) {
144
+ timer = setTimeout(tick, interval);
145
+ if (options.frames !== false) frame = api.requestFrame(onFrame);
146
+ }
147
+ };
148
+ const unsubscribe = api.subscribe(change);
149
+ change(api.isActive());
150
+ return {
151
+ stop() {
152
+ stopped = true;
153
+ active = false;
154
+ suspend();
155
+ unsubscribe();
156
+ }
157
+ };
158
+ }
159
+ };
160
+ }
161
+
162
+ // src/client/TracingSink.ts
163
+ function formatTracingEntry(entry) {
164
+ const attributes = entry.attributes;
165
+ const id = attributes?.id;
166
+ const suffix = typeof id === "string" ? ` [${id.slice(-6)}]` : "";
167
+ const details = Object.entries(attributes ?? {}).filter(([key, value]) => key !== "id" && value !== void 0).map(([key, value]) => `${key}=${value}`);
168
+ const detail = details.length ? ` {${details.join(" ")}}` : "";
169
+ const label = entry.entryType === "metric" ? `${entry.name}=${entry.value}` : entry.entryType === "measure" ? `${entry.name} ${entry.durationMs.toFixed(0)}ms` : entry.name;
170
+ return `${label}${suffix}${detail}`;
171
+ }
172
+
173
+ // src/plugins/performance.ts
174
+ function createPerformancePlugin(api, onEntry) {
175
+ return {
176
+ id: "performance",
177
+ start({ recording, reportError }) {
178
+ const offset = recording.getStats().nowMs - api.performance.now();
179
+ let stopped = false;
180
+ const ingest = (entries) => {
181
+ if (stopped) return;
182
+ for (const entry of entries) {
183
+ try {
184
+ const timestampMs = entry.startTime + offset;
185
+ if (timestampMs < 0) continue;
186
+ const data = entry;
187
+ onEntry?.({
188
+ name: entry.name,
189
+ entryType: entry.entryType,
190
+ durationMs: entry.duration,
191
+ value: typeof data.value === "string" || typeof data.value === "number" ? data.value : void 0,
192
+ attributes: data.detail
193
+ });
194
+ const context = {
195
+ name: entry.name.slice(0, 128),
196
+ correlationId: typeof data.detail?.id === "string" ? data.detail.id.slice(0, 128) : "",
197
+ attributes: [
198
+ { key: "source", value: "performance" },
199
+ { key: "source.startTimeMs", value: String(entry.startTime) }
200
+ ]
201
+ };
202
+ if (entry.entryType === "longtask")
203
+ recording.recordSpan({
204
+ ...context,
205
+ name: "js.longtask",
206
+ timestampMs,
207
+ durationMs: entry.duration,
208
+ outcome: "success"
209
+ });
210
+ else if (entry.entryType === "measure")
211
+ recording.recordSpan({
212
+ ...context,
213
+ timestampMs,
214
+ durationMs: entry.duration,
215
+ outcome: "success"
216
+ });
217
+ else if (entry.entryType === "mark")
218
+ recording.mark({ ...context, timestampMs });
219
+ else if (entry.entryType === "metric" && typeof data.value === "number" && Number.isFinite(data.value))
220
+ recording.recordMetric({
221
+ ...context,
222
+ timestampMs,
223
+ value: data.value,
224
+ unit: typeof data.detail?.unit === "string" ? data.detail.unit.slice(0, 32) : "unspecified"
225
+ });
226
+ } catch (error) {
227
+ reportError(error);
228
+ }
229
+ }
230
+ };
231
+ const observer = new api.PerformanceObserver(
232
+ (list) => ingest(list.getEntries())
233
+ );
234
+ const supported = api.PerformanceObserver.supportedEntryTypes;
235
+ const entryTypes = ["mark", "measure", "metric", "longtask"].filter(
236
+ (type) => supported ? supported.includes(type) : type !== "longtask"
237
+ );
238
+ try {
239
+ observer.observe({ entryTypes });
240
+ } catch (error) {
241
+ observer.disconnect();
242
+ throw error;
243
+ }
244
+ return {
245
+ stop() {
246
+ if (stopped) return;
247
+ try {
248
+ ingest(observer.takeRecords());
249
+ } finally {
250
+ stopped = true;
251
+ observer.disconnect();
252
+ }
253
+ }
254
+ };
255
+ }
256
+ };
257
+ }
258
+
259
+ // src/plugins/startPlugins.ts
260
+ function startPlugins(recording, plugins, onError) {
261
+ const handles = [];
262
+ const ids = /* @__PURE__ */ new Set();
263
+ for (const plugin of plugins) {
264
+ if (ids.has(plugin.id))
265
+ throw new Error(`Duplicate tracing plugin: ${plugin.id}`);
266
+ ids.add(plugin.id);
267
+ }
268
+ const reportError = (error) => {
269
+ try {
270
+ onError(error);
271
+ } catch {
272
+ }
273
+ };
274
+ for (const plugin of plugins) {
275
+ try {
276
+ handles.push(plugin.start({ recording, reportError }));
277
+ } catch (error) {
278
+ reportError(error);
279
+ }
280
+ }
281
+ let stopping;
282
+ return {
283
+ stop() {
284
+ if (!stopping)
285
+ stopping = (async () => {
286
+ for (const handle of [...handles].reverse()) {
287
+ try {
288
+ await handle.stop();
289
+ } catch (error) {
290
+ reportError(error);
291
+ }
292
+ }
293
+ try {
294
+ recording.stop();
295
+ } catch (error) {
296
+ reportError(error);
297
+ }
298
+ for (const handle of handles) {
299
+ try {
300
+ await handle.flush?.();
301
+ } catch (error) {
302
+ reportError(error);
303
+ }
304
+ }
305
+ })();
306
+ return stopping;
307
+ }
308
+ };
309
+ }
310
+
311
+ // src/client/playground.ts
312
+ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
313
+ var recordPlayground = async (current) => {
314
+ const identity = current.startSpan({
315
+ name: "playground.run",
316
+ correlationId: "",
317
+ attributes: []
318
+ });
319
+ const correlationId = `playground-${identity.spanId}`;
320
+ identity.end("success");
321
+ const root = current.startSpan({
322
+ name: "playground.pipeline",
323
+ correlationId,
324
+ attributes: [{ key: "source", value: "playground" }]
325
+ });
326
+ const start = (name) => current.startSpan({
327
+ name,
328
+ correlationId,
329
+ parentSpanId: root.spanId,
330
+ attributes: []
331
+ });
332
+ try {
333
+ const first = start("playground.concurrent");
334
+ const second = start("playground.concurrent");
335
+ await Promise.all([
336
+ delay(30).then(() => first.end("success")),
337
+ delay(60).then(() => second.end("success"))
338
+ ]);
339
+ const cancelled = start("playground.cancelled");
340
+ cancelled.end("cancelled");
341
+ const repeated = start("playground.idempotent");
342
+ repeated.end("success");
343
+ repeated.end("error");
344
+ const failed = start("playground.error");
345
+ failed.end("error");
346
+ for (const value of [20, 45, 80])
347
+ current.recordMetric({
348
+ name: "playground.throughput",
349
+ correlationId,
350
+ attributes: [],
351
+ value,
352
+ unit: "byte/second"
353
+ });
354
+ root.end("success");
355
+ const spans = [];
356
+ const metrics = [];
357
+ let afterSequence = 0;
358
+ for (let pageIndex = 0; pageIndex < 100; pageIndex++) {
359
+ const page = await current.readEvents({ afterSequence, limit: 1e3 });
360
+ spans.push(
361
+ ...page.spans.filter(
362
+ (event) => event.correlationId === correlationId || event.spanId === root.spanId
363
+ )
364
+ );
365
+ metrics.push(
366
+ ...page.metrics.filter((event) => event.correlationId === correlationId)
367
+ );
368
+ if (page.nextSequence <= afterSequence || page.spans.length + page.marks.length + page.metrics.length < 1e3)
369
+ break;
370
+ afterSequence = page.nextSequence;
371
+ }
372
+ const concurrent = spans.filter(
373
+ (span) => span.name === "playground.concurrent"
374
+ );
375
+ const repeatedEvents = spans.filter(
376
+ (span) => span.spanId === repeated.spanId
377
+ );
378
+ const checks = [
379
+ {
380
+ id: "nested",
381
+ passed: spans.length === 6 && spans.filter((span) => span.spanId !== root.spanId).every((span) => span.parentSpanId === root.spanId)
382
+ },
383
+ {
384
+ id: "concurrency",
385
+ passed: concurrent.length === 2 && concurrent[0].spanId !== concurrent[1].spanId && Math.max(...concurrent.map((span) => span.timestampMs)) <= Math.min(
386
+ ...concurrent.map((span) => span.timestampMs + span.durationMs)
387
+ )
388
+ },
389
+ {
390
+ id: "cancelled",
391
+ passed: spans.some(
392
+ (span) => span.spanId === cancelled.spanId && span.outcome === "cancelled"
393
+ )
394
+ },
395
+ {
396
+ id: "idempotent",
397
+ passed: repeatedEvents.length === 1 && repeatedEvents[0].outcome === "success"
398
+ },
399
+ {
400
+ id: "error",
401
+ passed: spans.some(
402
+ (span) => span.spanId === failed.spanId && span.outcome === "error"
403
+ )
404
+ },
405
+ {
406
+ id: "metrics",
407
+ passed: metrics.length === 3 && metrics.map((metric) => metric.value).join(",") === "20,45,80"
408
+ }
409
+ ];
410
+ return {
411
+ correlationId,
412
+ checks,
413
+ passed: checks.every((check) => check.passed)
414
+ };
415
+ } catch (error) {
416
+ try {
417
+ root.end("error");
418
+ } catch {
419
+ }
420
+ throw error;
421
+ }
422
+ };
423
+
424
+ // src/client/createTraceClient.ts
425
+ var defaultPerformanceApi = () => {
426
+ try {
427
+ const module = __require("react-native-performance");
428
+ return {
429
+ performance: module.default,
430
+ PerformanceObserver: module.PerformanceObserver
431
+ };
432
+ } catch {
433
+ const global = globalThis;
434
+ return global.performance && global.PerformanceObserver ? global : void 0;
435
+ }
436
+ };
437
+ function createTraceClient(options = {}) {
438
+ let recording;
439
+ let handle;
440
+ const readinessStartMs = globalThis.performance.now();
441
+ let readyReported = false;
442
+ let enabled = false;
443
+ let disposed = false;
444
+ let pending = 0;
445
+ let tail = Promise.resolve();
446
+ let snapshot = {
447
+ visible: false,
448
+ recording: false,
449
+ busy: false,
450
+ disposed: false,
451
+ profiling: false
452
+ };
453
+ const listeners = /* @__PURE__ */ new Set();
454
+ const marks = /* @__PURE__ */ new Map();
455
+ const publish = (next = {}) => {
456
+ snapshot = {
457
+ ...snapshot,
458
+ recording: enabled,
459
+ disposed,
460
+ busy: pending > 0,
461
+ profiling: options.profiler?.isProfiling() ?? false,
462
+ profilePath: options.profiler?.getProfilePath(),
463
+ ...next
464
+ };
465
+ for (const listener of listeners) {
466
+ try {
467
+ listener();
468
+ } catch {
469
+ }
470
+ }
471
+ };
472
+ const reportError = (error) => {
473
+ publish({ error: String(error) });
474
+ try {
475
+ options.onError?.(error);
476
+ } catch {
477
+ }
478
+ };
479
+ const safely = (work) => {
480
+ try {
481
+ return work();
482
+ } catch (error) {
483
+ reportError(error);
484
+ return void 0;
485
+ }
486
+ };
487
+ const emit = (entry) => {
488
+ if (options.sink)
489
+ safely(() => options.sink(formatTracingEntry(entry), entry));
490
+ };
491
+ const enqueue = (work) => {
492
+ if (disposed) return Promise.reject(new Error("Trace client is disposed"));
493
+ pending++;
494
+ publish();
495
+ const result = tail.then(async () => {
496
+ try {
497
+ if (disposed) throw new Error("Trace client is disposed");
498
+ await work();
499
+ } finally {
500
+ pending--;
501
+ publish();
502
+ }
503
+ });
504
+ tail = result.catch(reportError);
505
+ return result;
506
+ };
507
+ const stopCurrent = async () => {
508
+ enabled = false;
509
+ try {
510
+ await handle?.stop();
511
+ } finally {
512
+ handle = void 0;
513
+ recording?.stop();
514
+ marks.clear();
515
+ publish();
516
+ }
517
+ };
518
+ const startCurrent = async () => {
519
+ await stopCurrent();
520
+ if (disposed) throw new Error("Trace client is disposed");
521
+ recording?.dispose();
522
+ recording = void 0;
523
+ const { Tracing: Tracing2 } = (init_src(), __toCommonJS(src_exports));
524
+ recording = Tracing2.startRecording({
525
+ // ~1-2 hours of spans for UAT sessions; metric samples use at most half.
526
+ maxEvents: 1e4,
527
+ maxBytes: 6 * 1024 * 1024,
528
+ maxActiveSpans: 256,
529
+ spanTimeoutMs: 30 * 60 * 1e3,
530
+ ...options.recordingOptions
531
+ });
532
+ publish({ error: void 0, playground: void 0 });
533
+ try {
534
+ const plugins = [...options.plugins ?? []];
535
+ const api = options.performance === false ? void 0 : options.performance ?? defaultPerformanceApi();
536
+ if (api)
537
+ plugins.unshift(
538
+ createPerformancePlugin(api, options.sink ? emit : void 0)
539
+ );
540
+ if (options.runtimeMetrics)
541
+ plugins.push(
542
+ createRuntimeMetricsPlugin(
543
+ options.runtimeMetrics === true ? {} : options.runtimeMetrics
544
+ )
545
+ );
546
+ if (options.profiler) plugins.push(options.profiler);
547
+ handle = startPlugins(recording, plugins, reportError);
548
+ publish({ collectors: plugins.map((plugin) => plugin.id) });
549
+ } catch (error) {
550
+ try {
551
+ await stopCurrent();
552
+ } catch (cleanupError) {
553
+ reportError(cleanupError);
554
+ }
555
+ try {
556
+ recording?.dispose();
557
+ } finally {
558
+ recording = void 0;
559
+ }
560
+ throw error;
561
+ }
562
+ enabled = true;
563
+ if (options.autoProfile && options.profiler)
564
+ safely(() => options.profiler.startProfiling());
565
+ publish();
566
+ };
567
+ const context = (name, attributes) => ({
568
+ name,
569
+ correlationId: String(attributes?.id ?? ""),
570
+ attributes: Object.entries(attributes ?? {}).filter(([, value]) => value !== void 0).slice(0, 32).map(([key, value]) => ({
571
+ key: key.slice(0, 64),
572
+ value: String(value).slice(0, 512)
573
+ }))
574
+ });
575
+ const trace = {
576
+ mark(name, attributes) {
577
+ safely(() => {
578
+ if (!enabled || !recording) return;
579
+ recording.mark(context(name, attributes));
580
+ emit({ name, entryType: "mark", durationMs: 0, attributes });
581
+ if (typeof attributes?.id === "string") {
582
+ let bucket = marks.get(attributes.id);
583
+ if (!bucket) {
584
+ if (marks.size >= 512) marks.delete(marks.keys().next().value);
585
+ bucket = { marks: /* @__PURE__ */ new Map(), emitted: /* @__PURE__ */ new Set() };
586
+ marks.set(attributes.id, bucket);
587
+ }
588
+ if (bucket.marks.size >= 64 && !bucket.marks.has(name))
589
+ bucket.marks.delete(bucket.marks.keys().next().value);
590
+ bucket.marks.set(name, recording.getStats().nowMs);
591
+ }
592
+ });
593
+ },
594
+ metric(name, value, attributes) {
595
+ safely(() => {
596
+ if (!enabled || !recording) return;
597
+ const numeric = Number(value);
598
+ if (Number.isFinite(numeric))
599
+ recording.recordMetric({
600
+ ...context(name, attributes),
601
+ value: numeric,
602
+ unit: String(attributes?.unit ?? "count")
603
+ });
604
+ else recording.mark(context(name, { ...attributes, value }));
605
+ emit({ name, entryType: "metric", durationMs: 0, value, attributes });
606
+ });
607
+ },
608
+ async measure(name, fn, attributes) {
609
+ const started = enabled && options.sink ? globalThis.performance.now() : void 0;
610
+ const span = safely(
611
+ () => enabled ? recording?.startSpan(context(name, attributes)) : void 0
612
+ );
613
+ try {
614
+ const value = await fn();
615
+ safely(() => span?.end("success"));
616
+ return value;
617
+ } catch (error) {
618
+ safely(() => span?.end("error"));
619
+ throw error;
620
+ } finally {
621
+ if (started !== void 0)
622
+ emit({
623
+ name,
624
+ entryType: "measure",
625
+ durationMs: Math.max(0, globalThis.performance.now() - started),
626
+ attributes
627
+ });
628
+ }
629
+ },
630
+ measureSince(name, startName, id, attributes) {
631
+ safely(() => {
632
+ if (!enabled || !recording) return;
633
+ const bucket = marks.get(id);
634
+ const start = bucket?.marks.get(startName);
635
+ if (start === void 0 || bucket.emitted.has(name) || bucket.emitted.size >= 64)
636
+ return;
637
+ bucket.emitted.add(name);
638
+ const durationMs = Math.max(0, recording.getStats().nowMs - start);
639
+ recording.recordSpan({
640
+ ...context(name, { ...attributes, id, startName }),
641
+ timestampMs: start,
642
+ durationMs,
643
+ outcome: "success"
644
+ });
645
+ emit({
646
+ name,
647
+ entryType: "measure",
648
+ durationMs,
649
+ attributes: { ...attributes, id }
650
+ });
651
+ });
652
+ },
653
+ clear(id) {
654
+ marks.delete(id);
655
+ }
656
+ };
657
+ return {
658
+ /** Once per client lifetime: elapsed time from client creation to the app-defined ready signal. */
659
+ reportAppReady() {
660
+ if (readyReported || !enabled) return;
661
+ readyReported = true;
662
+ trace.metric(
663
+ "app.ready.after_tracer_init",
664
+ Math.max(0, globalThis.performance.now() - readinessStartMs),
665
+ {
666
+ unit: "ms",
667
+ source: "app-readiness",
668
+ definition: "Tracer initialization to app ready signal; excludes earlier JS and native startup"
669
+ }
670
+ );
671
+ },
672
+ trace,
673
+ start: () => enqueue(startCurrent),
674
+ stop: () => enqueue(stopCurrent),
675
+ clear: () => enqueue(async () => {
676
+ const wasEnabled = enabled;
677
+ await startCurrent();
678
+ if (!wasEnabled) await stopCurrent();
679
+ }),
680
+ getRecording: () => recording,
681
+ getError: () => snapshot.error,
682
+ getSnapshot: () => snapshot,
683
+ subscribe(listener) {
684
+ listeners.add(listener);
685
+ return () => {
686
+ listeners.delete(listener);
687
+ };
688
+ },
689
+ open() {
690
+ if (!disposed) publish({ visible: true });
691
+ },
692
+ close() {
693
+ publish({ visible: false });
694
+ },
695
+ reportError,
696
+ canShare: Boolean(options.share),
697
+ canProfile: Boolean(options.profiler),
698
+ canShareProfile: Boolean(options.shareProfile),
699
+ export: () => enqueue(async () => {
700
+ if (!recording || !options.share) return;
701
+ await stopCurrent();
702
+ await options.share(recording, "recording");
703
+ }),
704
+ /** Share a snapshot while recording continues, e.g. right after catching a problem. */
705
+ shareTrace: (format = "perfetto") => enqueue(async () => {
706
+ if (recording && options.share) await options.share(recording, format);
707
+ }),
708
+ /**
709
+ * Mark the moment a tester saw a problem. With an idle profiler this also captures
710
+ * a bounded Hermes profile, so the flag ships with what the JS thread was doing.
711
+ */
712
+ flag: (note) => {
713
+ trace.mark("flag", { source: "flag", note });
714
+ const profiler = options.profiler;
715
+ const windowMs = options.flagProfileMs ?? 1e4;
716
+ if (!profiler || windowMs <= 0) return;
717
+ enqueue(async () => {
718
+ if (!enabled || profiler.isProfiling()) return;
719
+ profiler.startProfiling();
720
+ setTimeout(() => {
721
+ enqueue(async () => {
722
+ if (profiler.isProfiling()) await profiler.stopProfiling();
723
+ }).catch(() => {
724
+ });
725
+ }, windowMs);
726
+ }).catch(() => {
727
+ });
728
+ },
729
+ toggleProfile: () => enqueue(async () => {
730
+ if (!enabled || !options.profiler) return;
731
+ if (options.profiler.isProfiling())
732
+ await options.profiler.stopProfiling();
733
+ else options.profiler.startProfiling();
734
+ }),
735
+ shareProfile: () => enqueue(async () => {
736
+ const path = options.profiler?.getProfilePath();
737
+ if (path) await options.shareProfile?.(path);
738
+ }),
739
+ playground: () => enqueue(async () => {
740
+ if (enabled && recording)
741
+ publish({ playground: await recordPlayground(recording) });
742
+ }),
743
+ dispose() {
744
+ if (disposed) return tail;
745
+ disposed = true;
746
+ enabled = false;
747
+ publish({ visible: false });
748
+ const result = tail.then(async () => {
749
+ try {
750
+ await stopCurrent();
751
+ } finally {
752
+ try {
753
+ recording?.dispose();
754
+ } finally {
755
+ recording = void 0;
756
+ listeners.clear();
757
+ }
758
+ }
759
+ });
760
+ tail = result.catch(reportError);
761
+ return result;
762
+ }
763
+ };
764
+ }
765
+ export {
766
+ createTraceClient
767
+ };
768
+ //# sourceMappingURL=client.mjs.map