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