@sentry/react-native 5.31.1 → 6.0.0-alpha.1
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 +102 -0
- package/dist/js/client.d.ts +5 -9
- package/dist/js/client.d.ts.map +1 -1
- package/dist/js/client.js +11 -42
- package/dist/js/client.js.map +1 -1
- package/dist/js/index.d.ts +5 -10
- package/dist/js/index.d.ts.map +1 -1
- package/dist/js/index.js +4 -21
- package/dist/js/index.js.map +1 -1
- package/dist/js/integrations/debugsymbolicator.d.ts +2 -8
- package/dist/js/integrations/debugsymbolicator.d.ts.map +1 -1
- package/dist/js/integrations/debugsymbolicator.js +1 -9
- package/dist/js/integrations/debugsymbolicator.js.map +1 -1
- package/dist/js/integrations/default.d.ts.map +1 -1
- package/dist/js/integrations/default.js +16 -4
- package/dist/js/integrations/default.js.map +1 -1
- package/dist/js/integrations/devicecontext.d.ts +2 -8
- package/dist/js/integrations/devicecontext.d.ts.map +1 -1
- package/dist/js/integrations/devicecontext.js +0 -9
- package/dist/js/integrations/devicecontext.js.map +1 -1
- package/dist/js/integrations/eventorigin.d.ts +2 -8
- package/dist/js/integrations/eventorigin.d.ts.map +1 -1
- package/dist/js/integrations/eventorigin.js +0 -8
- package/dist/js/integrations/eventorigin.js.map +1 -1
- package/dist/js/integrations/expocontext.d.ts +2 -8
- package/dist/js/integrations/expocontext.d.ts.map +1 -1
- package/dist/js/integrations/expocontext.js +0 -8
- package/dist/js/integrations/expocontext.js.map +1 -1
- package/dist/js/integrations/exports.d.ts +5 -0
- package/dist/js/integrations/exports.d.ts.map +1 -1
- package/dist/js/integrations/exports.js +5 -0
- package/dist/js/integrations/exports.js.map +1 -1
- package/dist/js/integrations/modulesloader.d.ts +2 -8
- package/dist/js/integrations/modulesloader.d.ts.map +1 -1
- package/dist/js/integrations/modulesloader.js +0 -8
- package/dist/js/integrations/modulesloader.js.map +1 -1
- package/dist/js/integrations/nativelinkederrors.d.ts +2 -8
- package/dist/js/integrations/nativelinkederrors.d.ts.map +1 -1
- package/dist/js/integrations/nativelinkederrors.js +0 -8
- package/dist/js/integrations/nativelinkederrors.js.map +1 -1
- package/dist/js/integrations/reactnativeerrorhandlers.d.ts +2 -8
- package/dist/js/integrations/reactnativeerrorhandlers.d.ts.map +1 -1
- package/dist/js/integrations/reactnativeerrorhandlers.js +1 -8
- package/dist/js/integrations/reactnativeerrorhandlers.js.map +1 -1
- package/dist/js/integrations/reactnativeinfo.d.ts +2 -8
- package/dist/js/integrations/reactnativeinfo.d.ts.map +1 -1
- package/dist/js/integrations/reactnativeinfo.js +0 -8
- package/dist/js/integrations/reactnativeinfo.js.map +1 -1
- package/dist/js/integrations/release.d.ts +2 -8
- package/dist/js/integrations/release.d.ts.map +1 -1
- package/dist/js/integrations/release.js +0 -8
- package/dist/js/integrations/release.js.map +1 -1
- package/dist/js/integrations/rewriteframes.js +1 -1
- package/dist/js/integrations/rewriteframes.js.map +1 -1
- package/dist/js/integrations/screenshot.d.ts +2 -8
- package/dist/js/integrations/screenshot.d.ts.map +1 -1
- package/dist/js/integrations/screenshot.js +2 -11
- package/dist/js/integrations/screenshot.js.map +1 -1
- package/dist/js/integrations/sdkinfo.d.ts +2 -8
- package/dist/js/integrations/sdkinfo.d.ts.map +1 -1
- package/dist/js/integrations/sdkinfo.js +0 -8
- package/dist/js/integrations/sdkinfo.js.map +1 -1
- package/dist/js/integrations/spotlight.d.ts +2 -10
- package/dist/js/integrations/spotlight.d.ts.map +1 -1
- package/dist/js/integrations/spotlight.js +1 -10
- package/dist/js/integrations/spotlight.js.map +1 -1
- package/dist/js/integrations/viewhierarchy.d.ts +2 -8
- package/dist/js/integrations/viewhierarchy.d.ts.map +1 -1
- package/dist/js/integrations/viewhierarchy.js +0 -8
- package/dist/js/integrations/viewhierarchy.js.map +1 -1
- package/dist/js/options.d.ts +34 -2
- package/dist/js/options.d.ts.map +1 -1
- package/dist/js/options.js.map +1 -1
- package/dist/js/profiling/cache.d.ts +1 -1
- package/dist/js/profiling/hermes.d.ts +1 -1
- package/dist/js/profiling/hermes.d.ts.map +1 -1
- package/dist/js/profiling/integration.d.ts +1 -7
- package/dist/js/profiling/integration.d.ts.map +1 -1
- package/dist/js/profiling/integration.js +39 -25
- package/dist/js/profiling/integration.js.map +1 -1
- package/dist/js/profiling/utils.js +2 -1
- package/dist/js/profiling/utils.js.map +1 -1
- package/dist/js/replay/mobilereplay.d.ts +2 -2
- package/dist/js/replay/mobilereplay.d.ts.map +1 -1
- package/dist/js/replay/mobilereplay.js.map +1 -1
- package/dist/js/replay/networkUtils.d.ts +0 -1
- package/dist/js/replay/networkUtils.d.ts.map +1 -1
- package/dist/js/scopeSync.d.ts +6 -0
- package/dist/js/scopeSync.d.ts.map +1 -0
- package/dist/js/scopeSync.js +60 -0
- package/dist/js/scopeSync.js.map +1 -0
- package/dist/js/sdk.d.ts +1 -20
- package/dist/js/sdk.d.ts.map +1 -1
- package/dist/js/sdk.js +20 -53
- package/dist/js/sdk.js.map +1 -1
- package/dist/js/tools/metroconfig.d.ts +1 -1
- package/dist/js/tools/metroconfig.d.ts.map +1 -1
- package/dist/js/tools/metroconfig.js.map +1 -1
- package/dist/js/tools/sentryMetroSerializer.js.map +1 -1
- package/dist/js/touchevents.d.ts +0 -1
- package/dist/js/touchevents.d.ts.map +1 -1
- package/dist/js/touchevents.js +5 -9
- package/dist/js/touchevents.js.map +1 -1
- package/dist/js/tracing/gesturetracing.d.ts +1 -6
- package/dist/js/tracing/gesturetracing.d.ts.map +1 -1
- package/dist/js/tracing/gesturetracing.js +8 -12
- package/dist/js/tracing/gesturetracing.js.map +1 -1
- package/dist/js/tracing/index.d.ts +6 -7
- package/dist/js/tracing/index.d.ts.map +1 -1
- package/dist/js/tracing/index.js +4 -7
- package/dist/js/tracing/index.js.map +1 -1
- package/dist/js/tracing/integrations/appStart.d.ts +39 -0
- package/dist/js/tracing/integrations/appStart.d.ts.map +1 -0
- package/dist/js/tracing/integrations/appStart.js +301 -0
- package/dist/js/tracing/integrations/appStart.js.map +1 -0
- package/dist/js/tracing/integrations/nativeFrames.d.ts +21 -0
- package/dist/js/tracing/integrations/nativeFrames.d.ts.map +1 -0
- package/dist/js/tracing/integrations/nativeFrames.js +195 -0
- package/dist/js/tracing/integrations/nativeFrames.js.map +1 -0
- package/dist/js/tracing/integrations/stalltracking.d.ts +31 -0
- package/dist/js/tracing/integrations/stalltracking.d.ts.map +1 -0
- package/dist/js/tracing/integrations/stalltracking.js +236 -0
- package/dist/js/tracing/integrations/stalltracking.js.map +1 -0
- package/dist/js/tracing/integrations/userInteraction.d.ts +11 -0
- package/dist/js/tracing/integrations/userInteraction.d.ts.map +1 -0
- package/dist/js/tracing/integrations/userInteraction.js +70 -0
- package/dist/js/tracing/integrations/userInteraction.js.map +1 -0
- package/dist/js/tracing/onSpanEndUtils.d.ts +17 -0
- package/dist/js/tracing/onSpanEndUtils.d.ts.map +1 -0
- package/dist/js/tracing/onSpanEndUtils.js +112 -0
- package/dist/js/tracing/onSpanEndUtils.js.map +1 -0
- package/dist/js/tracing/origin.d.ts +2 -0
- package/dist/js/tracing/origin.d.ts.map +1 -0
- package/dist/js/tracing/origin.js +2 -0
- package/dist/js/tracing/origin.js.map +1 -0
- package/dist/js/tracing/reactnativenavigation.d.ts +21 -39
- package/dist/js/tracing/reactnativenavigation.d.ts.map +1 -1
- package/dist/js/tracing/reactnativenavigation.js +98 -87
- package/dist/js/tracing/reactnativenavigation.js.map +1 -1
- package/dist/js/tracing/reactnativeprofiler.d.ts.map +1 -1
- package/dist/js/tracing/reactnativeprofiler.js +6 -13
- package/dist/js/tracing/reactnativeprofiler.js.map +1 -1
- package/dist/js/tracing/reactnativetracing.d.ts +44 -160
- package/dist/js/tracing/reactnativetracing.d.ts.map +1 -1
- package/dist/js/tracing/reactnativetracing.js +51 -481
- package/dist/js/tracing/reactnativetracing.js.map +1 -1
- package/dist/js/tracing/reactnavigation.d.ts +18 -63
- package/dist/js/tracing/reactnavigation.d.ts.map +1 -1
- package/dist/js/tracing/reactnavigation.js +200 -205
- package/dist/js/tracing/reactnavigation.js.map +1 -1
- package/dist/js/tracing/semanticAttributes.d.ts +12 -0
- package/dist/js/tracing/semanticAttributes.d.ts.map +1 -0
- package/dist/js/tracing/semanticAttributes.js +12 -0
- package/dist/js/tracing/semanticAttributes.js.map +1 -0
- package/dist/js/tracing/span.d.ts +52 -0
- package/dist/js/tracing/span.d.ts.map +1 -0
- package/dist/js/tracing/span.js +82 -0
- package/dist/js/tracing/span.js.map +1 -0
- package/dist/js/tracing/timetodisplay.d.ts.map +1 -1
- package/dist/js/tracing/timetodisplay.js +14 -29
- package/dist/js/tracing/timetodisplay.js.map +1 -1
- package/dist/js/tracing/timetodisplaynative.types.d.ts +0 -1
- package/dist/js/tracing/timetodisplaynative.types.d.ts.map +1 -1
- package/dist/js/tracing/types.d.ts +2 -9
- package/dist/js/tracing/types.d.ts.map +1 -1
- package/dist/js/tracing/types.js.map +1 -1
- package/dist/js/tracing/utils.d.ts +19 -14
- package/dist/js/tracing/utils.d.ts.map +1 -1
- package/dist/js/tracing/utils.js +38 -52
- package/dist/js/tracing/utils.js.map +1 -1
- package/dist/js/transports/encodePolyfill.d.ts +3 -0
- package/dist/js/transports/encodePolyfill.d.ts.map +1 -0
- package/dist/js/transports/encodePolyfill.js +13 -0
- package/dist/js/transports/encodePolyfill.js.map +1 -0
- package/dist/js/transports/native.d.ts +2 -2
- package/dist/js/transports/native.d.ts.map +1 -1
- package/dist/js/transports/native.js +2 -1
- package/dist/js/transports/native.js.map +1 -1
- package/dist/js/utils/AsyncExpiringMap.d.ts +56 -0
- package/dist/js/utils/AsyncExpiringMap.d.ts.map +1 -0
- package/dist/js/utils/AsyncExpiringMap.js +130 -0
- package/dist/js/utils/AsyncExpiringMap.js.map +1 -0
- package/dist/js/utils/fill.d.ts +7 -0
- package/dist/js/utils/fill.d.ts.map +1 -0
- package/dist/js/utils/fill.js +9 -0
- package/dist/js/utils/fill.js.map +1 -0
- package/dist/js/utils/span.d.ts +19 -0
- package/dist/js/utils/span.d.ts.map +1 -0
- package/dist/js/utils/span.js +29 -0
- package/dist/js/utils/span.js.map +1 -0
- package/dist/js/vendor/react-native/index.d.ts +1 -2
- package/dist/js/vendor/react-native/index.d.ts.map +1 -1
- package/dist/js/vendor/react-native/index.js.map +1 -1
- package/dist/js/version.d.ts +1 -1
- package/dist/js/version.d.ts.map +1 -1
- package/dist/js/version.js +1 -1
- package/dist/js/version.js.map +1 -1
- package/dist/js/wrapper.d.ts.map +1 -1
- package/dist/js/wrapper.js +1 -0
- package/dist/js/wrapper.js.map +1 -1
- package/package.json +11 -12
- package/ts3.8/dist/js/client.d.ts +5 -9
- package/ts3.8/dist/js/index.d.ts +5 -10
- package/ts3.8/dist/js/integrations/debugsymbolicator.d.ts +2 -8
- package/ts3.8/dist/js/integrations/devicecontext.d.ts +2 -8
- package/ts3.8/dist/js/integrations/eventorigin.d.ts +2 -8
- package/ts3.8/dist/js/integrations/expocontext.d.ts +2 -8
- package/ts3.8/dist/js/integrations/exports.d.ts +5 -0
- package/ts3.8/dist/js/integrations/modulesloader.d.ts +2 -8
- package/ts3.8/dist/js/integrations/nativelinkederrors.d.ts +2 -8
- package/ts3.8/dist/js/integrations/reactnativeerrorhandlers.d.ts +2 -8
- package/ts3.8/dist/js/integrations/reactnativeinfo.d.ts +2 -8
- package/ts3.8/dist/js/integrations/release.d.ts +2 -8
- package/ts3.8/dist/js/integrations/screenshot.d.ts +2 -8
- package/ts3.8/dist/js/integrations/sdkinfo.d.ts +2 -8
- package/ts3.8/dist/js/integrations/spotlight.d.ts +2 -10
- package/ts3.8/dist/js/integrations/viewhierarchy.d.ts +2 -8
- package/ts3.8/dist/js/options.d.ts +34 -2
- package/ts3.8/dist/js/profiling/cache.d.ts +1 -1
- package/ts3.8/dist/js/profiling/hermes.d.ts +1 -1
- package/ts3.8/dist/js/profiling/integration.d.ts +1 -7
- package/ts3.8/dist/js/replay/mobilereplay.d.ts +2 -2
- package/ts3.8/dist/js/replay/networkUtils.d.ts +0 -1
- package/ts3.8/dist/js/scopeSync.d.ts +6 -0
- package/ts3.8/dist/js/sdk.d.ts +1 -20
- package/ts3.8/dist/js/touchevents.d.ts +0 -1
- package/ts3.8/dist/js/tracing/gesturetracing.d.ts +1 -6
- package/ts3.8/dist/js/tracing/index.d.ts +6 -7
- package/ts3.8/dist/js/tracing/integrations/appStart.d.ts +39 -0
- package/ts3.8/dist/js/tracing/integrations/nativeFrames.d.ts +21 -0
- package/ts3.8/dist/js/tracing/integrations/stalltracking.d.ts +31 -0
- package/ts3.8/dist/js/tracing/integrations/userInteraction.d.ts +11 -0
- package/ts3.8/dist/js/tracing/onSpanEndUtils.d.ts +17 -0
- package/ts3.8/dist/js/tracing/origin.d.ts +2 -0
- package/ts3.8/dist/js/tracing/reactnativenavigation.d.ts +21 -39
- package/ts3.8/dist/js/tracing/reactnativetracing.d.ts +44 -160
- package/ts3.8/dist/js/tracing/reactnavigation.d.ts +18 -63
- package/ts3.8/dist/js/tracing/semanticAttributes.d.ts +12 -0
- package/ts3.8/dist/js/tracing/span.d.ts +52 -0
- package/ts3.8/dist/js/tracing/timetodisplaynative.types.d.ts +0 -1
- package/ts3.8/dist/js/tracing/types.d.ts +2 -9
- package/ts3.8/dist/js/tracing/utils.d.ts +19 -14
- package/ts3.8/dist/js/transports/encodePolyfill.d.ts +3 -0
- package/ts3.8/dist/js/transports/native.d.ts +2 -2
- package/ts3.8/dist/js/utils/AsyncExpiringMap.d.ts +56 -0
- package/ts3.8/dist/js/utils/fill.d.ts +7 -0
- package/ts3.8/dist/js/utils/span.d.ts +19 -0
- package/ts3.8/dist/js/vendor/react-native/index.d.ts +1 -2
- package/ts3.8/dist/js/version.d.ts +1 -1
- package/dist/js/integrations/index.d.ts +0 -16
- package/dist/js/integrations/index.d.ts.map +0 -1
- package/dist/js/integrations/index.js +0 -17
- package/dist/js/integrations/index.js.map +0 -1
- package/dist/js/scope.d.ts +0 -54
- package/dist/js/scope.d.ts.map +0 -1
- package/dist/js/scope.js +0 -89
- package/dist/js/scope.js.map +0 -1
- package/dist/js/tracing/addTracingExtensions.d.ts +0 -8
- package/dist/js/tracing/addTracingExtensions.d.ts.map +0 -1
- package/dist/js/tracing/addTracingExtensions.js +0 -66
- package/dist/js/tracing/addTracingExtensions.js.map +0 -1
- package/dist/js/tracing/nativeframes.d.ts +0 -60
- package/dist/js/tracing/nativeframes.d.ts.map +0 -1
- package/dist/js/tracing/nativeframes.js +0 -210
- package/dist/js/tracing/nativeframes.js.map +0 -1
- package/dist/js/tracing/reactnavigationv4.d.ts +0 -92
- package/dist/js/tracing/reactnavigationv4.d.ts.map +0 -1
- package/dist/js/tracing/reactnavigationv4.js +0 -229
- package/dist/js/tracing/reactnavigationv4.js.map +0 -1
- package/dist/js/tracing/routingInstrumentation.d.ts +0 -52
- package/dist/js/tracing/routingInstrumentation.d.ts.map +0 -1
- package/dist/js/tracing/routingInstrumentation.js +0 -36
- package/dist/js/tracing/routingInstrumentation.js.map +0 -1
- package/dist/js/tracing/stalltracking.d.ts +0 -99
- package/dist/js/tracing/stalltracking.d.ts.map +0 -1
- package/dist/js/tracing/stalltracking.js +0 -286
- package/dist/js/tracing/stalltracking.js.map +0 -1
- package/dist/js/tracing/transaction.d.ts +0 -11
- package/dist/js/tracing/transaction.d.ts.map +0 -1
- package/dist/js/tracing/transaction.js +0 -37
- package/dist/js/tracing/transaction.js.map +0 -1
- package/dist/js/transports/TextEncoder.d.ts +0 -3
- package/dist/js/transports/TextEncoder.d.ts.map +0 -1
- package/dist/js/transports/TextEncoder.js +0 -12
- package/dist/js/transports/TextEncoder.js.map +0 -1
- package/ts3.8/dist/js/integrations/index.d.ts +0 -16
- package/ts3.8/dist/js/scope.d.ts +0 -54
- package/ts3.8/dist/js/tracing/addTracingExtensions.d.ts +0 -8
- package/ts3.8/dist/js/tracing/nativeframes.d.ts +0 -60
- package/ts3.8/dist/js/tracing/reactnavigationv4.d.ts +0 -92
- package/ts3.8/dist/js/tracing/routingInstrumentation.d.ts +0 -52
- package/ts3.8/dist/js/tracing/stalltracking.d.ts +0 -99
- package/ts3.8/dist/js/tracing/transaction.d.ts +0 -11
- package/ts3.8/dist/js/transports/TextEncoder.d.ts +0 -3
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import { logger, timestampInSeconds } from '@sentry/utils';
|
|
3
|
+
import { AsyncExpiringMap } from '../../utils/AsyncExpiringMap';
|
|
4
|
+
import { isRootSpan } from '../../utils/span';
|
|
5
|
+
import { NATIVE } from '../../wrapper';
|
|
6
|
+
/**
|
|
7
|
+
* Timeout from the start of a span to fetching the associated native frames.
|
|
8
|
+
*/
|
|
9
|
+
const FETCH_FRAMES_TIMEOUT_MS = 2000;
|
|
10
|
+
/**
|
|
11
|
+
* This is the time end frames data from the native layer will be
|
|
12
|
+
* kept in memory and waiting for the event processing. This ensures that spans
|
|
13
|
+
* which are never processed are not leaking memory.
|
|
14
|
+
*/
|
|
15
|
+
const END_FRAMES_TIMEOUT_MS = 2000;
|
|
16
|
+
/**
|
|
17
|
+
* This is the time start frames data from the native layer will be
|
|
18
|
+
* kept in memory and waiting for span end. This ensures that spans
|
|
19
|
+
* which never end or are not processed are not leaking memory.
|
|
20
|
+
*/
|
|
21
|
+
const START_FRAMES_TIMEOUT_MS = 60000;
|
|
22
|
+
/**
|
|
23
|
+
* A margin of error of 50ms is allowed for the async native bridge call.
|
|
24
|
+
* Anything larger would reduce the accuracy of our frames measurements.
|
|
25
|
+
*/
|
|
26
|
+
const MARGIN_OF_ERROR_SECONDS = 0.05;
|
|
27
|
+
const INTEGRATION_NAME = 'NativeFrames';
|
|
28
|
+
export const createNativeFramesIntegrations = (enable) => {
|
|
29
|
+
if (!enable && NATIVE.enableNative) {
|
|
30
|
+
// On Android this will free up resource when JS reloaded (native modules stay) and thus JS side of the SDK reinitialized.
|
|
31
|
+
NATIVE.disableNativeFramesTracking();
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return nativeFramesIntegration();
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Instrumentation to add native slow/frozen frames measurements onto transactions.
|
|
38
|
+
*/
|
|
39
|
+
export const nativeFramesIntegration = () => {
|
|
40
|
+
/** The native frames at the finish time of the most recent span. */
|
|
41
|
+
let _lastChildSpanEndFrames = null;
|
|
42
|
+
const _spanToNativeFramesAtStartMap = new AsyncExpiringMap({
|
|
43
|
+
ttl: START_FRAMES_TIMEOUT_MS,
|
|
44
|
+
});
|
|
45
|
+
const _spanToNativeFramesAtEndMap = new AsyncExpiringMap({ ttl: END_FRAMES_TIMEOUT_MS });
|
|
46
|
+
/**
|
|
47
|
+
* Hooks into the client start and end span events.
|
|
48
|
+
*/
|
|
49
|
+
const setup = (client) => {
|
|
50
|
+
if (!NATIVE.enableNative) {
|
|
51
|
+
logger.warn(`[${INTEGRATION_NAME}] This is not available on the Web, Expo Go and other platforms without native modules.`);
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
NATIVE.enableNativeFramesTracking();
|
|
55
|
+
client.on('spanStart', fetchStartFramesForSpan);
|
|
56
|
+
client.on('spanEnd', fetchEndFramesForSpan);
|
|
57
|
+
};
|
|
58
|
+
const fetchStartFramesForSpan = (rootSpan) => {
|
|
59
|
+
if (!isRootSpan(rootSpan)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const spanId = rootSpan.spanContext().spanId;
|
|
63
|
+
logger.debug(`[${INTEGRATION_NAME}] Fetching frames for root span start (${spanId}).`);
|
|
64
|
+
_spanToNativeFramesAtStartMap.set(spanId, new Promise(resolve => {
|
|
65
|
+
fetchNativeFrames()
|
|
66
|
+
.then(frames => resolve(frames))
|
|
67
|
+
.then(undefined, error => {
|
|
68
|
+
logger.debug(`[${INTEGRATION_NAME}] Error while fetching native frames.`, error);
|
|
69
|
+
resolve(null);
|
|
70
|
+
});
|
|
71
|
+
}));
|
|
72
|
+
};
|
|
73
|
+
const fetchEndFramesForSpan = (span) => {
|
|
74
|
+
const timestamp = timestampInSeconds();
|
|
75
|
+
const spanId = span.spanContext().spanId;
|
|
76
|
+
if (isRootSpan(span)) {
|
|
77
|
+
const hasStartFrames = _spanToNativeFramesAtStartMap.has(spanId);
|
|
78
|
+
if (!hasStartFrames) {
|
|
79
|
+
// We don't have start frames, won't be able to calculate the difference.
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
logger.debug(`[${INTEGRATION_NAME}] Fetch frames for root span end (${spanId}).`);
|
|
83
|
+
_spanToNativeFramesAtEndMap.set(spanId, new Promise(resolve => {
|
|
84
|
+
fetchNativeFrames()
|
|
85
|
+
.then(frames => {
|
|
86
|
+
resolve({
|
|
87
|
+
timestamp,
|
|
88
|
+
nativeFrames: frames,
|
|
89
|
+
});
|
|
90
|
+
})
|
|
91
|
+
.then(undefined, error => {
|
|
92
|
+
logger.debug(`[${INTEGRATION_NAME}] Error while fetching native frames.`, error);
|
|
93
|
+
resolve(null);
|
|
94
|
+
});
|
|
95
|
+
}));
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
logger.debug(`[${INTEGRATION_NAME}] Fetch frames for child span end (${spanId}).`);
|
|
100
|
+
fetchNativeFrames()
|
|
101
|
+
.then(frames => {
|
|
102
|
+
_lastChildSpanEndFrames = {
|
|
103
|
+
timestamp,
|
|
104
|
+
nativeFrames: frames,
|
|
105
|
+
};
|
|
106
|
+
})
|
|
107
|
+
.catch(error => logger.debug(`[${INTEGRATION_NAME}] Error while fetching native frames.`, error));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const processEvent = (event) => __awaiter(void 0, void 0, void 0, function* () {
|
|
111
|
+
var _a;
|
|
112
|
+
if (event.type !== 'transaction' ||
|
|
113
|
+
!event.transaction ||
|
|
114
|
+
!event.contexts ||
|
|
115
|
+
!event.contexts.trace ||
|
|
116
|
+
!event.timestamp ||
|
|
117
|
+
!event.contexts.trace.span_id) {
|
|
118
|
+
return event;
|
|
119
|
+
}
|
|
120
|
+
const traceOp = event.contexts.trace.op;
|
|
121
|
+
const spanId = event.contexts.trace.span_id;
|
|
122
|
+
const startFrames = yield _spanToNativeFramesAtStartMap.pop(spanId);
|
|
123
|
+
if (!startFrames) {
|
|
124
|
+
logger.warn(`[${INTEGRATION_NAME}] Start frames of transaction ${event.transaction} (eventId, ${event.event_id}) are missing, but the transaction already ended.`);
|
|
125
|
+
return event;
|
|
126
|
+
}
|
|
127
|
+
const endFrames = yield _spanToNativeFramesAtEndMap.pop(spanId);
|
|
128
|
+
let finalEndFrames;
|
|
129
|
+
if (endFrames && isClose(endFrames.timestamp, event.timestamp)) {
|
|
130
|
+
// Must be in the margin of error of the actual transaction finish time (finalEndTimestamp)
|
|
131
|
+
logger.debug(`[${INTEGRATION_NAME}] Using frames from root span end (spanId, ${spanId}).`);
|
|
132
|
+
finalEndFrames = endFrames.nativeFrames;
|
|
133
|
+
}
|
|
134
|
+
else if (_lastChildSpanEndFrames && isClose(_lastChildSpanEndFrames.timestamp, event.timestamp)) {
|
|
135
|
+
// Fallback to the last span finish if it is within the margin of error of the actual finish timestamp.
|
|
136
|
+
// This should be the case for trimEnd.
|
|
137
|
+
logger.debug(`[${INTEGRATION_NAME}] Using native frames from last child span end (spanId, ${spanId}).`);
|
|
138
|
+
finalEndFrames = _lastChildSpanEndFrames.nativeFrames;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
logger.warn(`[${INTEGRATION_NAME}] Frames were collected within larger than margin of error delay for spanId (${spanId}). Dropping the inaccurate values.`);
|
|
142
|
+
return event;
|
|
143
|
+
}
|
|
144
|
+
const measurements = {
|
|
145
|
+
frames_total: {
|
|
146
|
+
value: finalEndFrames.totalFrames - startFrames.totalFrames,
|
|
147
|
+
unit: 'none',
|
|
148
|
+
},
|
|
149
|
+
frames_frozen: {
|
|
150
|
+
value: finalEndFrames.frozenFrames - startFrames.frozenFrames,
|
|
151
|
+
unit: 'none',
|
|
152
|
+
},
|
|
153
|
+
frames_slow: {
|
|
154
|
+
value: finalEndFrames.slowFrames - startFrames.slowFrames,
|
|
155
|
+
unit: 'none',
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
if (measurements.frames_frozen.value <= 0 &&
|
|
159
|
+
measurements.frames_slow.value <= 0 &&
|
|
160
|
+
measurements.frames_total.value <= 0) {
|
|
161
|
+
logger.warn(`[${INTEGRATION_NAME}] Detected zero slow or frozen frames. Not adding measurements to spanId (${spanId}).`);
|
|
162
|
+
return event;
|
|
163
|
+
}
|
|
164
|
+
logger.log(`[${INTEGRATION_NAME}] Adding measurements to ${traceOp} transaction ${event.transaction}: ${JSON.stringify(measurements, undefined, 2)}`);
|
|
165
|
+
event.measurements = Object.assign(Object.assign({}, ((_a = event.measurements) !== null && _a !== void 0 ? _a : {})), measurements);
|
|
166
|
+
return event;
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
name: INTEGRATION_NAME,
|
|
170
|
+
setup,
|
|
171
|
+
processEvent,
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
function fetchNativeFrames() {
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
NATIVE.fetchNativeFrames()
|
|
177
|
+
.then(value => {
|
|
178
|
+
if (!value) {
|
|
179
|
+
reject('Native frames response is null.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
resolve(value);
|
|
183
|
+
})
|
|
184
|
+
.then(undefined, error => {
|
|
185
|
+
reject(error);
|
|
186
|
+
});
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
reject('Fetching native frames took too long. Dropping frames.');
|
|
189
|
+
}, FETCH_FRAMES_TIMEOUT_MS);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function isClose(t1, t2) {
|
|
193
|
+
return Math.abs(t1 - t2) < MARGIN_OF_ERROR_SECONDS;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=nativeFrames.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeFrames.js","sourceRoot":"","sources":["../../../../src/js/tracing/integrations/nativeFrames.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Client, Event, Integration, Measurements, MeasurementUnit, Span } from '@sentry/types';\nimport { logger, timestampInSeconds } from '@sentry/utils';\n\nimport type { NativeFramesResponse } from '../../NativeRNSentry';\nimport { AsyncExpiringMap } from '../../utils/AsyncExpiringMap';\nimport { isRootSpan } from '../../utils/span';\nimport { NATIVE } from '../../wrapper';\n\n/**\n * Timeout from the start of a span to fetching the associated native frames.\n */\nconst FETCH_FRAMES_TIMEOUT_MS = 2_000;\n\n/**\n * This is the time end frames data from the native layer will be\n * kept in memory and waiting for the event processing. This ensures that spans\n * which are never processed are not leaking memory.\n */\nconst END_FRAMES_TIMEOUT_MS = 2_000;\n\n/**\n * This is the time start frames data from the native layer will be\n * kept in memory and waiting for span end. This ensures that spans\n * which never end or are not processed are not leaking memory.\n */\nconst START_FRAMES_TIMEOUT_MS = 60_000;\n\n/**\n * A margin of error of 50ms is allowed for the async native bridge call.\n * Anything larger would reduce the accuracy of our frames measurements.\n */\nconst MARGIN_OF_ERROR_SECONDS = 0.05;\n\nconst INTEGRATION_NAME = 'NativeFrames';\n\nexport interface FramesMeasurements extends Measurements {\n frames_total: { value: number; unit: MeasurementUnit };\n frames_slow: { value: number; unit: MeasurementUnit };\n frames_frozen: { value: number; unit: MeasurementUnit };\n}\n\ninterface NativeFramesResponseWithTimestamp {\n timestamp: number;\n nativeFrames: NativeFramesResponse;\n}\n\nexport const createNativeFramesIntegrations = (enable: boolean | undefined): Integration | undefined => {\n if (!enable && NATIVE.enableNative) {\n // On Android this will free up resource when JS reloaded (native modules stay) and thus JS side of the SDK reinitialized.\n NATIVE.disableNativeFramesTracking();\n return undefined;\n }\n\n return nativeFramesIntegration();\n};\n\n/**\n * Instrumentation to add native slow/frozen frames measurements onto transactions.\n */\nexport const nativeFramesIntegration = (): Integration => {\n /** The native frames at the finish time of the most recent span. */\n let _lastChildSpanEndFrames: NativeFramesResponseWithTimestamp | null = null;\n const _spanToNativeFramesAtStartMap: AsyncExpiringMap<string, NativeFramesResponse> = new AsyncExpiringMap({\n ttl: START_FRAMES_TIMEOUT_MS,\n });\n const _spanToNativeFramesAtEndMap: AsyncExpiringMap<string, NativeFramesResponseWithTimestamp | null> =\n new AsyncExpiringMap({ ttl: END_FRAMES_TIMEOUT_MS });\n\n /**\n * Hooks into the client start and end span events.\n */\n const setup = (client: Client): void => {\n if (!NATIVE.enableNative) {\n logger.warn(\n `[${INTEGRATION_NAME}] This is not available on the Web, Expo Go and other platforms without native modules.`,\n );\n return undefined;\n }\n\n NATIVE.enableNativeFramesTracking();\n client.on('spanStart', fetchStartFramesForSpan);\n client.on('spanEnd', fetchEndFramesForSpan);\n };\n\n const fetchStartFramesForSpan = (rootSpan: Span): void => {\n if (!isRootSpan(rootSpan)) {\n return;\n }\n\n const spanId = rootSpan.spanContext().spanId;\n logger.debug(`[${INTEGRATION_NAME}] Fetching frames for root span start (${spanId}).`);\n _spanToNativeFramesAtStartMap.set(\n spanId,\n new Promise<NativeFramesResponse | null>(resolve => {\n fetchNativeFrames()\n .then(frames => resolve(frames))\n .then(undefined, error => {\n logger.debug(`[${INTEGRATION_NAME}] Error while fetching native frames.`, error);\n resolve(null);\n });\n }),\n );\n };\n\n const fetchEndFramesForSpan = (span: Span): void => {\n const timestamp = timestampInSeconds();\n const spanId = span.spanContext().spanId;\n\n if (isRootSpan(span)) {\n const hasStartFrames = _spanToNativeFramesAtStartMap.has(spanId);\n if (!hasStartFrames) {\n // We don't have start frames, won't be able to calculate the difference.\n return;\n }\n\n logger.debug(`[${INTEGRATION_NAME}] Fetch frames for root span end (${spanId}).`);\n _spanToNativeFramesAtEndMap.set(\n spanId,\n new Promise<NativeFramesResponseWithTimestamp | null>(resolve => {\n fetchNativeFrames()\n .then(frames => {\n resolve({\n timestamp,\n nativeFrames: frames,\n });\n })\n .then(undefined, error => {\n logger.debug(`[${INTEGRATION_NAME}] Error while fetching native frames.`, error);\n resolve(null);\n });\n }),\n );\n return undefined;\n } else {\n logger.debug(`[${INTEGRATION_NAME}] Fetch frames for child span end (${spanId}).`);\n fetchNativeFrames()\n .then(frames => {\n _lastChildSpanEndFrames = {\n timestamp,\n nativeFrames: frames,\n };\n })\n .catch(error => logger.debug(`[${INTEGRATION_NAME}] Error while fetching native frames.`, error));\n }\n };\n\n const processEvent = async (event: Event): Promise<Event> => {\n if (\n event.type !== 'transaction' ||\n !event.transaction ||\n !event.contexts ||\n !event.contexts.trace ||\n !event.timestamp ||\n !event.contexts.trace.span_id\n ) {\n return event;\n }\n\n const traceOp = event.contexts.trace.op;\n const spanId = event.contexts.trace.span_id;\n const startFrames = await _spanToNativeFramesAtStartMap.pop(spanId);\n if (!startFrames) {\n logger.warn(\n `[${INTEGRATION_NAME}] Start frames of transaction ${event.transaction} (eventId, ${event.event_id}) are missing, but the transaction already ended.`,\n );\n return event;\n }\n\n const endFrames = await _spanToNativeFramesAtEndMap.pop(spanId);\n let finalEndFrames: NativeFramesResponse | undefined;\n\n if (endFrames && isClose(endFrames.timestamp, event.timestamp)) {\n // Must be in the margin of error of the actual transaction finish time (finalEndTimestamp)\n logger.debug(`[${INTEGRATION_NAME}] Using frames from root span end (spanId, ${spanId}).`);\n finalEndFrames = endFrames.nativeFrames;\n } else if (_lastChildSpanEndFrames && isClose(_lastChildSpanEndFrames.timestamp, event.timestamp)) {\n // Fallback to the last span finish if it is within the margin of error of the actual finish timestamp.\n // This should be the case for trimEnd.\n logger.debug(`[${INTEGRATION_NAME}] Using native frames from last child span end (spanId, ${spanId}).`);\n finalEndFrames = _lastChildSpanEndFrames.nativeFrames;\n } else {\n logger.warn(\n `[${INTEGRATION_NAME}] Frames were collected within larger than margin of error delay for spanId (${spanId}). Dropping the inaccurate values.`,\n );\n return event;\n }\n\n const measurements = {\n frames_total: {\n value: finalEndFrames.totalFrames - startFrames.totalFrames,\n unit: 'none',\n },\n frames_frozen: {\n value: finalEndFrames.frozenFrames - startFrames.frozenFrames,\n unit: 'none',\n },\n frames_slow: {\n value: finalEndFrames.slowFrames - startFrames.slowFrames,\n unit: 'none',\n },\n };\n\n if (\n measurements.frames_frozen.value <= 0 &&\n measurements.frames_slow.value <= 0 &&\n measurements.frames_total.value <= 0\n ) {\n logger.warn(\n `[${INTEGRATION_NAME}] Detected zero slow or frozen frames. Not adding measurements to spanId (${spanId}).`,\n );\n return event;\n }\n\n logger.log(\n `[${INTEGRATION_NAME}] Adding measurements to ${traceOp} transaction ${event.transaction}: ${JSON.stringify(\n measurements,\n undefined,\n 2,\n )}`,\n );\n event.measurements = {\n ...(event.measurements ?? {}),\n ...measurements,\n };\n return event;\n };\n\n return {\n name: INTEGRATION_NAME,\n setup,\n processEvent,\n };\n};\n\nfunction fetchNativeFrames(): Promise<NativeFramesResponse> {\n return new Promise<NativeFramesResponse>((resolve, reject) => {\n NATIVE.fetchNativeFrames()\n .then(value => {\n if (!value) {\n reject('Native frames response is null.');\n return;\n }\n resolve(value);\n })\n .then(undefined, error => {\n reject(error);\n });\n\n setTimeout(() => {\n reject('Fetching native frames took too long. Dropping frames.');\n }, FETCH_FRAMES_TIMEOUT_MS);\n });\n}\n\nfunction isClose(t1: number, t2: number): boolean {\n return Math.abs(t1 - t2) < MARGIN_OF_ERROR_SECONDS;\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Integration, Measurements, MeasurementUnit } from '@sentry/types';
|
|
2
|
+
import { STALL_COUNT, STALL_LONGEST_TIME, STALL_TOTAL_TIME } from '../../measurements';
|
|
3
|
+
export interface StallMeasurements extends Measurements {
|
|
4
|
+
[STALL_COUNT]: {
|
|
5
|
+
value: number;
|
|
6
|
+
unit: MeasurementUnit;
|
|
7
|
+
};
|
|
8
|
+
[STALL_TOTAL_TIME]: {
|
|
9
|
+
value: number;
|
|
10
|
+
unit: MeasurementUnit;
|
|
11
|
+
};
|
|
12
|
+
[STALL_LONGEST_TIME]: {
|
|
13
|
+
value: number;
|
|
14
|
+
unit: MeasurementUnit;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Stall measurement tracker inspired by the `JSEventLoopWatchdog` used internally in React Native:
|
|
19
|
+
* https://github.com/facebook/react-native/blob/006f5afe120c290a37cf6ff896748fbc062bf7ed/Libraries/Interaction/JSEventLoopWatchdog.js
|
|
20
|
+
*
|
|
21
|
+
* However, we modified the interval implementation to instead have a fixed loop timeout interval of `LOOP_TIMEOUT_INTERVAL_MS`.
|
|
22
|
+
* We then would consider that iteration a stall when the total time for that interval to run is greater than `LOOP_TIMEOUT_INTERVAL_MS + minimumStallThreshold`
|
|
23
|
+
*/
|
|
24
|
+
export declare const stallTrackingIntegration: ({ minimumStallThresholdMs, }?: {
|
|
25
|
+
/**
|
|
26
|
+
* How long in milliseconds an event loop iteration can be delayed for before being considered a "stall."
|
|
27
|
+
* @default 50
|
|
28
|
+
*/
|
|
29
|
+
minimumStallThresholdMs?: number;
|
|
30
|
+
}) => Integration;
|
|
31
|
+
//# sourceMappingURL=stalltracking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stalltracking.d.ts","sourceRoot":"","sources":["../../../../src/js/tracing/integrations/stalltracking.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,WAAW,EAAE,YAAY,EAAE,eAAe,EAAQ,MAAM,eAAe,CAAC;AAK9F,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAMvF,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,CAAC,WAAW,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,eAAe,CAAA;KAAE,CAAC;IACxD,CAAC,gBAAgB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,eAAe,CAAA;KAAE,CAAC;IAC7D,CAAC,kBAAkB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,eAAe,CAAA;KAAE,CAAC;CAChE;AASD;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB;IAGnC;;;OAGG;8BACuB,MAAM;MACzB,WA0TR,CAAC"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
import { getRootSpan, spanToJSON } from '@sentry/core';
|
|
3
|
+
import { logger, timestampInSeconds } from '@sentry/utils';
|
|
4
|
+
import { AppState } from 'react-native';
|
|
5
|
+
import { STALL_COUNT, STALL_LONGEST_TIME, STALL_TOTAL_TIME } from '../../measurements';
|
|
6
|
+
import { isRootSpan } from '../../utils/span';
|
|
7
|
+
import { getLatestChildSpanEndTimestamp, isNearToNow, setSpanMeasurement } from '../utils';
|
|
8
|
+
const INTEGRATION_NAME = 'StallTracking';
|
|
9
|
+
/** Margin of error of 20ms */
|
|
10
|
+
const MARGIN_OF_ERROR_SECONDS = 0.02;
|
|
11
|
+
/** How long between each iteration in the event loop tracker timeout */
|
|
12
|
+
const LOOP_TIMEOUT_INTERVAL_MS = 50;
|
|
13
|
+
/** Limit for how many transactions the stall tracker will track at a time to prevent leaks due to transactions not being finished */
|
|
14
|
+
const MAX_RUNNING_TRANSACTIONS = 10;
|
|
15
|
+
/**
|
|
16
|
+
* Stall measurement tracker inspired by the `JSEventLoopWatchdog` used internally in React Native:
|
|
17
|
+
* https://github.com/facebook/react-native/blob/006f5afe120c290a37cf6ff896748fbc062bf7ed/Libraries/Interaction/JSEventLoopWatchdog.js
|
|
18
|
+
*
|
|
19
|
+
* However, we modified the interval implementation to instead have a fixed loop timeout interval of `LOOP_TIMEOUT_INTERVAL_MS`.
|
|
20
|
+
* We then would consider that iteration a stall when the total time for that interval to run is greater than `LOOP_TIMEOUT_INTERVAL_MS + minimumStallThreshold`
|
|
21
|
+
*/
|
|
22
|
+
export const stallTrackingIntegration = ({ minimumStallThresholdMs = 50, } = {}) => {
|
|
23
|
+
const statsByRootSpan = new Map();
|
|
24
|
+
const state = {
|
|
25
|
+
isTracking: false,
|
|
26
|
+
timeout: null,
|
|
27
|
+
isBackground: false,
|
|
28
|
+
lastIntervalMs: 0,
|
|
29
|
+
totalStallTime: 0,
|
|
30
|
+
stallCount: 0,
|
|
31
|
+
backgroundEventListener: (appState) => {
|
|
32
|
+
if (appState === 'active') {
|
|
33
|
+
state.isBackground = false;
|
|
34
|
+
if (state.timeout != null) {
|
|
35
|
+
state.lastIntervalMs = timestampInSeconds() * 1000;
|
|
36
|
+
state.iteration();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
state.isBackground = true;
|
|
41
|
+
state.timeout !== null && clearTimeout(state.timeout);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
iteration: () => {
|
|
45
|
+
var _a;
|
|
46
|
+
const now = timestampInSeconds() * 1000;
|
|
47
|
+
const totalTimeTaken = now - state.lastIntervalMs;
|
|
48
|
+
if (totalTimeTaken >= LOOP_TIMEOUT_INTERVAL_MS + minimumStallThresholdMs) {
|
|
49
|
+
const stallTime = totalTimeTaken - LOOP_TIMEOUT_INTERVAL_MS;
|
|
50
|
+
state.stallCount += 1;
|
|
51
|
+
state.totalStallTime += stallTime;
|
|
52
|
+
for (const [transaction, value] of statsByRootSpan.entries()) {
|
|
53
|
+
const longestStallTime = Math.max((_a = value.longestStallTime) !== null && _a !== void 0 ? _a : 0, stallTime);
|
|
54
|
+
statsByRootSpan.set(transaction, Object.assign(Object.assign({}, value), { longestStallTime }));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
state.lastIntervalMs = now;
|
|
58
|
+
if (state.isTracking && !state.isBackground) {
|
|
59
|
+
state.timeout = setTimeout(state.iteration, LOOP_TIMEOUT_INTERVAL_MS);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const setup = (client) => {
|
|
64
|
+
client.on('spanStart', _onSpanStart);
|
|
65
|
+
client.on('spanEnd', _onSpanEnd);
|
|
66
|
+
};
|
|
67
|
+
const _onSpanStart = (rootSpan) => {
|
|
68
|
+
if (!isRootSpan(rootSpan)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (statsByRootSpan.has(rootSpan)) {
|
|
72
|
+
logger.error('[StallTracking] Tried to start stall tracking on a transaction already being tracked. Measurements might be lost.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
_startTracking();
|
|
76
|
+
statsByRootSpan.set(rootSpan, {
|
|
77
|
+
longestStallTime: 0,
|
|
78
|
+
atTimestamp: null,
|
|
79
|
+
atStart: _getCurrentStats(rootSpan),
|
|
80
|
+
});
|
|
81
|
+
_flushLeakedTransactions();
|
|
82
|
+
};
|
|
83
|
+
const _onSpanEnd = (rootSpan) => {
|
|
84
|
+
if (!isRootSpan(rootSpan)) {
|
|
85
|
+
return _onChildSpanEnd(rootSpan);
|
|
86
|
+
}
|
|
87
|
+
const transactionStats = statsByRootSpan.get(rootSpan);
|
|
88
|
+
if (!transactionStats) {
|
|
89
|
+
// Transaction has been flushed out somehow, we return null.
|
|
90
|
+
logger.log('[StallTracking] Stall measurements were not added to transaction due to exceeding the max count.');
|
|
91
|
+
statsByRootSpan.delete(rootSpan);
|
|
92
|
+
_shouldStopTracking();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// The endTimestamp is always set, but type-wise it's optional
|
|
96
|
+
// https://github.com/getsentry/sentry-javascript/blob/38bd57b0785c97c413f36f89ff931d927e469078/packages/core/src/tracing/sentrySpan.ts#L170
|
|
97
|
+
const endTimestamp = spanToJSON(rootSpan).timestamp;
|
|
98
|
+
let statsOnFinish;
|
|
99
|
+
if (isNearToNow(endTimestamp)) {
|
|
100
|
+
statsOnFinish = _getCurrentStats(rootSpan);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// The idleSpan in JS V8 is always trimmed to the last span's endTimestamp (timestamp).
|
|
104
|
+
// The unfinished child spans are removed from the root span after the `spanEnd` event.
|
|
105
|
+
const latestChildSpanEnd = getLatestChildSpanEndTimestamp(rootSpan);
|
|
106
|
+
if (latestChildSpanEnd !== endTimestamp) {
|
|
107
|
+
logger.log('[StallTracking] Stall measurements not added due to a custom `endTimestamp` (root end is not equal to the latest child span end).');
|
|
108
|
+
}
|
|
109
|
+
if (!transactionStats.atTimestamp) {
|
|
110
|
+
logger.log('[StallTracking] Stall measurements not added due to `endTimestamp` not being close to now. And no previous stats from child end were found.');
|
|
111
|
+
}
|
|
112
|
+
if (latestChildSpanEnd === endTimestamp && transactionStats.atTimestamp) {
|
|
113
|
+
statsOnFinish = transactionStats.atTimestamp.stats;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
statsByRootSpan.delete(rootSpan);
|
|
117
|
+
_shouldStopTracking();
|
|
118
|
+
if (!statsOnFinish) {
|
|
119
|
+
if (typeof endTimestamp !== 'undefined') {
|
|
120
|
+
logger.log('[StallTracking] Stall measurements not added due to `endTimestamp` not being close to now.', 'endTimestamp', endTimestamp, 'now', timestampInSeconds());
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
setSpanMeasurement(rootSpan, STALL_COUNT, statsOnFinish.stall_count.value - transactionStats.atStart.stall_count.value, transactionStats.atStart.stall_count.unit);
|
|
125
|
+
setSpanMeasurement(rootSpan, STALL_TOTAL_TIME, statsOnFinish.stall_total_time.value - transactionStats.atStart.stall_total_time.value, transactionStats.atStart.stall_total_time.unit);
|
|
126
|
+
setSpanMeasurement(rootSpan, STALL_LONGEST_TIME, statsOnFinish.stall_longest_time.value, statsOnFinish.stall_longest_time.unit);
|
|
127
|
+
};
|
|
128
|
+
const _onChildSpanEnd = (childSpan) => {
|
|
129
|
+
const rootSpan = getRootSpan(childSpan);
|
|
130
|
+
const finalEndTimestamp = spanToJSON(childSpan).timestamp;
|
|
131
|
+
if (finalEndTimestamp) {
|
|
132
|
+
_markSpanFinish(rootSpan, finalEndTimestamp);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Logs the finish time of the span for use in `trimEnd: true` transactions.
|
|
137
|
+
*/
|
|
138
|
+
const _markSpanFinish = (rootSpan, childSpanEndTime) => {
|
|
139
|
+
const previousStats = statsByRootSpan.get(rootSpan);
|
|
140
|
+
if (previousStats) {
|
|
141
|
+
if (Math.abs(timestampInSeconds() - childSpanEndTime) > MARGIN_OF_ERROR_SECONDS) {
|
|
142
|
+
logger.log('[StallTracking] Span end not logged due to end timestamp being outside the margin of error from now.');
|
|
143
|
+
if (previousStats.atTimestamp && previousStats.atTimestamp.timestamp < childSpanEndTime) {
|
|
144
|
+
// We also need to delete the stat for the last span, as the transaction would be trimmed to this span not the last one.
|
|
145
|
+
statsByRootSpan.set(rootSpan, Object.assign(Object.assign({}, previousStats), { atTimestamp: null }));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
statsByRootSpan.set(rootSpan, Object.assign(Object.assign({}, previousStats), { atTimestamp: {
|
|
150
|
+
timestamp: childSpanEndTime,
|
|
151
|
+
stats: _getCurrentStats(rootSpan),
|
|
152
|
+
} }));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Get the current stats for a transaction at a given time.
|
|
158
|
+
*/
|
|
159
|
+
const _getCurrentStats = (span) => {
|
|
160
|
+
var _a, _b;
|
|
161
|
+
return {
|
|
162
|
+
stall_count: { value: state.stallCount, unit: 'none' },
|
|
163
|
+
stall_total_time: { value: state.totalStallTime, unit: 'millisecond' },
|
|
164
|
+
stall_longest_time: {
|
|
165
|
+
value: (_b = (_a = statsByRootSpan.get(span)) === null || _a === void 0 ? void 0 : _a.longestStallTime) !== null && _b !== void 0 ? _b : 0,
|
|
166
|
+
unit: 'millisecond',
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Start tracking stalls
|
|
172
|
+
*/
|
|
173
|
+
const _startTracking = () => {
|
|
174
|
+
if (!state.isTracking) {
|
|
175
|
+
state.isTracking = true;
|
|
176
|
+
state.lastIntervalMs = Math.floor(timestampInSeconds() * 1000);
|
|
177
|
+
state.iteration();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Stops the stall tracking interval and calls reset().
|
|
182
|
+
*/
|
|
183
|
+
const _stopTracking = () => {
|
|
184
|
+
state.isTracking = false;
|
|
185
|
+
if (state.timeout !== null) {
|
|
186
|
+
clearTimeout(state.timeout);
|
|
187
|
+
state.timeout = null;
|
|
188
|
+
}
|
|
189
|
+
_reset();
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Will stop tracking if there are no more transactions.
|
|
193
|
+
*/
|
|
194
|
+
const _shouldStopTracking = () => {
|
|
195
|
+
if (statsByRootSpan.size === 0) {
|
|
196
|
+
_stopTracking();
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* Clears all the collected stats
|
|
201
|
+
*/
|
|
202
|
+
const _reset = () => {
|
|
203
|
+
state.stallCount = 0;
|
|
204
|
+
state.totalStallTime = 0;
|
|
205
|
+
state.lastIntervalMs = 0;
|
|
206
|
+
statsByRootSpan.clear();
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Deletes leaked transactions (Earliest transactions when we have more than MAX_RUNNING_TRANSACTIONS transactions.)
|
|
210
|
+
*/
|
|
211
|
+
const _flushLeakedTransactions = () => {
|
|
212
|
+
if (statsByRootSpan.size > MAX_RUNNING_TRANSACTIONS) {
|
|
213
|
+
let counter = 0;
|
|
214
|
+
const len = statsByRootSpan.size - MAX_RUNNING_TRANSACTIONS;
|
|
215
|
+
const transactions = statsByRootSpan.keys();
|
|
216
|
+
for (const t of transactions) {
|
|
217
|
+
if (counter >= len)
|
|
218
|
+
break;
|
|
219
|
+
counter += 1;
|
|
220
|
+
statsByRootSpan.delete(t);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
// Avoids throwing any error if using React Native on a environment that doesn't implement AppState.
|
|
225
|
+
if (AppState === null || AppState === void 0 ? void 0 : AppState.isAvailable) {
|
|
226
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
227
|
+
AppState.addEventListener('change', state.backgroundEventListener);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
name: INTEGRATION_NAME,
|
|
231
|
+
setup,
|
|
232
|
+
/** For testing only @private */
|
|
233
|
+
_internalState: state,
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
//# sourceMappingURL=stalltracking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stalltracking.js","sourceRoot":"","sources":["../../../../src/js/tracing/integrations/stalltracking.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable max-lines */\nimport { getRootSpan, spanToJSON } from '@sentry/core';\nimport type { Client, Integration, Measurements, MeasurementUnit, Span } from '@sentry/types';\nimport { logger, timestampInSeconds } from '@sentry/utils';\nimport type { AppStateStatus } from 'react-native';\nimport { AppState } from 'react-native';\n\nimport { STALL_COUNT, STALL_LONGEST_TIME, STALL_TOTAL_TIME } from '../../measurements';\nimport { isRootSpan } from '../../utils/span';\nimport { getLatestChildSpanEndTimestamp, isNearToNow, setSpanMeasurement } from '../utils';\n\nconst INTEGRATION_NAME = 'StallTracking';\n\nexport interface StallMeasurements extends Measurements {\n [STALL_COUNT]: { value: number; unit: MeasurementUnit };\n [STALL_TOTAL_TIME]: { value: number; unit: MeasurementUnit };\n [STALL_LONGEST_TIME]: { value: number; unit: MeasurementUnit };\n}\n\n/** Margin of error of 20ms */\nconst MARGIN_OF_ERROR_SECONDS = 0.02;\n/** How long between each iteration in the event loop tracker timeout */\nconst LOOP_TIMEOUT_INTERVAL_MS = 50;\n/** Limit for how many transactions the stall tracker will track at a time to prevent leaks due to transactions not being finished */\nconst MAX_RUNNING_TRANSACTIONS = 10;\n\n/**\n * Stall measurement tracker inspired by the `JSEventLoopWatchdog` used internally in React Native:\n * https://github.com/facebook/react-native/blob/006f5afe120c290a37cf6ff896748fbc062bf7ed/Libraries/Interaction/JSEventLoopWatchdog.js\n *\n * However, we modified the interval implementation to instead have a fixed loop timeout interval of `LOOP_TIMEOUT_INTERVAL_MS`.\n * We then would consider that iteration a stall when the total time for that interval to run is greater than `LOOP_TIMEOUT_INTERVAL_MS + minimumStallThreshold`\n */\nexport const stallTrackingIntegration = ({\n minimumStallThresholdMs = 50,\n}: {\n /**\n * How long in milliseconds an event loop iteration can be delayed for before being considered a \"stall.\"\n * @default 50\n */\n minimumStallThresholdMs?: number;\n} = {}): Integration => {\n const statsByRootSpan: Map<\n Span,\n {\n longestStallTime: number;\n atStart: StallMeasurements;\n atTimestamp: {\n timestamp: number;\n stats: StallMeasurements;\n } | null;\n }\n > = new Map();\n\n const state: {\n isTracking: boolean;\n timeout: ReturnType<typeof setTimeout> | null;\n isBackground: boolean;\n /** Switch that enables the iteration once app moves from background to foreground. */\n backgroundEventListener: (appState: AppStateStatus) => void;\n /** The last timestamp the iteration ran in milliseconds */\n lastIntervalMs: number;\n /** Total amount of time of all stalls that occurred during the current tracking session */\n totalStallTime: number;\n /** Total number of stalls that occurred during the current tracking session */\n stallCount: number;\n /**\n * Iteration of the stall tracking interval. Measures how long the timer strayed from its expected time of running, and how\n * long the stall is for.\n */\n iteration: () => void;\n } = {\n isTracking: false,\n timeout: null,\n isBackground: false,\n lastIntervalMs: 0,\n totalStallTime: 0,\n stallCount: 0,\n backgroundEventListener: (appState: AppStateStatus): void => {\n if (appState === ('active' as AppStateStatus)) {\n state.isBackground = false;\n if (state.timeout != null) {\n state.lastIntervalMs = timestampInSeconds() * 1000;\n state.iteration();\n }\n } else {\n state.isBackground = true;\n state.timeout !== null && clearTimeout(state.timeout);\n }\n },\n iteration: (): void => {\n const now = timestampInSeconds() * 1000;\n const totalTimeTaken = now - state.lastIntervalMs;\n\n if (totalTimeTaken >= LOOP_TIMEOUT_INTERVAL_MS + minimumStallThresholdMs) {\n const stallTime = totalTimeTaken - LOOP_TIMEOUT_INTERVAL_MS;\n state.stallCount += 1;\n state.totalStallTime += stallTime;\n\n for (const [transaction, value] of statsByRootSpan.entries()) {\n const longestStallTime = Math.max(value.longestStallTime ?? 0, stallTime);\n\n statsByRootSpan.set(transaction, {\n ...value,\n longestStallTime,\n });\n }\n }\n\n state.lastIntervalMs = now;\n\n if (state.isTracking && !state.isBackground) {\n state.timeout = setTimeout(state.iteration, LOOP_TIMEOUT_INTERVAL_MS);\n }\n },\n };\n\n const setup = (client: Client): void => {\n client.on('spanStart', _onSpanStart);\n client.on('spanEnd', _onSpanEnd);\n };\n\n const _onSpanStart = (rootSpan: Span): void => {\n if (!isRootSpan(rootSpan)) {\n return;\n }\n\n if (statsByRootSpan.has(rootSpan)) {\n logger.error(\n '[StallTracking] Tried to start stall tracking on a transaction already being tracked. Measurements might be lost.',\n );\n return;\n }\n\n _startTracking();\n statsByRootSpan.set(rootSpan, {\n longestStallTime: 0,\n atTimestamp: null,\n atStart: _getCurrentStats(rootSpan),\n });\n _flushLeakedTransactions();\n };\n\n const _onSpanEnd = (rootSpan: Span): void => {\n if (!isRootSpan(rootSpan)) {\n return _onChildSpanEnd(rootSpan);\n }\n\n const transactionStats = statsByRootSpan.get(rootSpan);\n\n if (!transactionStats) {\n // Transaction has been flushed out somehow, we return null.\n logger.log('[StallTracking] Stall measurements were not added to transaction due to exceeding the max count.');\n\n statsByRootSpan.delete(rootSpan);\n _shouldStopTracking();\n\n return;\n }\n\n // The endTimestamp is always set, but type-wise it's optional\n // https://github.com/getsentry/sentry-javascript/blob/38bd57b0785c97c413f36f89ff931d927e469078/packages/core/src/tracing/sentrySpan.ts#L170\n const endTimestamp = spanToJSON(rootSpan).timestamp;\n\n let statsOnFinish: StallMeasurements | undefined;\n if (isNearToNow(endTimestamp)) {\n statsOnFinish = _getCurrentStats(rootSpan);\n } else {\n // The idleSpan in JS V8 is always trimmed to the last span's endTimestamp (timestamp).\n // The unfinished child spans are removed from the root span after the `spanEnd` event.\n\n const latestChildSpanEnd = getLatestChildSpanEndTimestamp(rootSpan);\n if (latestChildSpanEnd !== endTimestamp) {\n logger.log(\n '[StallTracking] Stall measurements not added due to a custom `endTimestamp` (root end is not equal to the latest child span end).',\n );\n }\n\n if (!transactionStats.atTimestamp) {\n logger.log(\n '[StallTracking] Stall measurements not added due to `endTimestamp` not being close to now. And no previous stats from child end were found.',\n );\n }\n\n if (latestChildSpanEnd === endTimestamp && transactionStats.atTimestamp) {\n statsOnFinish = transactionStats.atTimestamp.stats;\n }\n }\n\n statsByRootSpan.delete(rootSpan);\n _shouldStopTracking();\n\n if (!statsOnFinish) {\n if (typeof endTimestamp !== 'undefined') {\n logger.log(\n '[StallTracking] Stall measurements not added due to `endTimestamp` not being close to now.',\n 'endTimestamp',\n endTimestamp,\n 'now',\n timestampInSeconds(),\n );\n }\n\n return;\n }\n\n setSpanMeasurement(\n rootSpan,\n STALL_COUNT,\n statsOnFinish.stall_count.value - transactionStats.atStart.stall_count.value,\n transactionStats.atStart.stall_count.unit,\n );\n\n setSpanMeasurement(\n rootSpan,\n STALL_TOTAL_TIME,\n statsOnFinish.stall_total_time.value - transactionStats.atStart.stall_total_time.value,\n transactionStats.atStart.stall_total_time.unit,\n );\n\n setSpanMeasurement(\n rootSpan,\n STALL_LONGEST_TIME,\n statsOnFinish.stall_longest_time.value,\n statsOnFinish.stall_longest_time.unit,\n );\n };\n\n const _onChildSpanEnd = (childSpan: Span): void => {\n const rootSpan = getRootSpan(childSpan);\n\n const finalEndTimestamp = spanToJSON(childSpan).timestamp;\n if (finalEndTimestamp) {\n _markSpanFinish(rootSpan, finalEndTimestamp);\n }\n };\n\n /**\n * Logs the finish time of the span for use in `trimEnd: true` transactions.\n */\n const _markSpanFinish = (rootSpan: Span, childSpanEndTime: number): void => {\n const previousStats = statsByRootSpan.get(rootSpan);\n if (previousStats) {\n if (Math.abs(timestampInSeconds() - childSpanEndTime) > MARGIN_OF_ERROR_SECONDS) {\n logger.log(\n '[StallTracking] Span end not logged due to end timestamp being outside the margin of error from now.',\n );\n\n if (previousStats.atTimestamp && previousStats.atTimestamp.timestamp < childSpanEndTime) {\n // We also need to delete the stat for the last span, as the transaction would be trimmed to this span not the last one.\n statsByRootSpan.set(rootSpan, {\n ...previousStats,\n atTimestamp: null,\n });\n }\n } else {\n statsByRootSpan.set(rootSpan, {\n ...previousStats,\n atTimestamp: {\n timestamp: childSpanEndTime,\n stats: _getCurrentStats(rootSpan),\n },\n });\n }\n }\n };\n\n /**\n * Get the current stats for a transaction at a given time.\n */\n const _getCurrentStats = (span: Span): StallMeasurements => {\n return {\n stall_count: { value: state.stallCount, unit: 'none' },\n stall_total_time: { value: state.totalStallTime, unit: 'millisecond' },\n stall_longest_time: {\n value: statsByRootSpan.get(span)?.longestStallTime ?? 0,\n unit: 'millisecond',\n },\n };\n };\n\n /**\n * Start tracking stalls\n */\n const _startTracking = (): void => {\n if (!state.isTracking) {\n state.isTracking = true;\n state.lastIntervalMs = Math.floor(timestampInSeconds() * 1000);\n\n state.iteration();\n }\n };\n\n /**\n * Stops the stall tracking interval and calls reset().\n */\n const _stopTracking = (): void => {\n state.isTracking = false;\n\n if (state.timeout !== null) {\n clearTimeout(state.timeout);\n state.timeout = null;\n }\n\n _reset();\n };\n\n /**\n * Will stop tracking if there are no more transactions.\n */\n const _shouldStopTracking = (): void => {\n if (statsByRootSpan.size === 0) {\n _stopTracking();\n }\n };\n\n /**\n * Clears all the collected stats\n */\n const _reset = (): void => {\n state.stallCount = 0;\n state.totalStallTime = 0;\n state.lastIntervalMs = 0;\n statsByRootSpan.clear();\n };\n\n /**\n * Deletes leaked transactions (Earliest transactions when we have more than MAX_RUNNING_TRANSACTIONS transactions.)\n */\n const _flushLeakedTransactions = (): void => {\n if (statsByRootSpan.size > MAX_RUNNING_TRANSACTIONS) {\n let counter = 0;\n const len = statsByRootSpan.size - MAX_RUNNING_TRANSACTIONS;\n const transactions = statsByRootSpan.keys();\n for (const t of transactions) {\n if (counter >= len) break;\n counter += 1;\n statsByRootSpan.delete(t);\n }\n }\n };\n\n // Avoids throwing any error if using React Native on a environment that doesn't implement AppState.\n if (AppState?.isAvailable) {\n // eslint-disable-next-line @typescript-eslint/unbound-method\n AppState.addEventListener('change', state.backgroundEventListener);\n }\n\n return {\n name: INTEGRATION_NAME,\n setup,\n\n /** For testing only @private */\n _internalState: state,\n } as Integration;\n};\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Integration, Span } from '@sentry/types';
|
|
2
|
+
export declare const userInteractionIntegration: () => Integration;
|
|
3
|
+
/**
|
|
4
|
+
* Starts a new transaction for a user interaction.
|
|
5
|
+
* @param userInteractionId Consists of `op` representation UI Event and `elementId` unique element identifier on current screen.
|
|
6
|
+
*/
|
|
7
|
+
export declare const startUserInteractionSpan: (userInteractionId: {
|
|
8
|
+
elementId: string | undefined;
|
|
9
|
+
op: string;
|
|
10
|
+
}) => Span | undefined;
|
|
11
|
+
//# sourceMappingURL=userInteraction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userInteraction.d.ts","sourceRoot":"","sources":["../../../../src/js/tracing/integrations/userInteraction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAoB,MAAM,eAAe,CAAC;AAWzE,eAAO,MAAM,0BAA0B,QAAO,WAI7C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,sBAAuB;IAC1D,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,EAAE,EAAE,MAAM,CAAC;CACZ,KAAG,IAAI,GAAG,SAmEV,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getActiveSpan, getClient, getCurrentScope, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON } from '@sentry/core';
|
|
2
|
+
import { logger } from '@sentry/utils';
|
|
3
|
+
import { onlySampleIfChildSpans } from '../onSpanEndUtils';
|
|
4
|
+
import { SPAN_ORIGIN_AUTO_INTERACTION } from '../origin';
|
|
5
|
+
import { getCurrentReactNativeTracingIntegration } from '../reactnativetracing';
|
|
6
|
+
import { clearActiveSpanFromScope, isSentryInteractionSpan, startIdleSpan } from '../span';
|
|
7
|
+
const INTEGRATION_NAME = 'UserInteraction';
|
|
8
|
+
export const userInteractionIntegration = () => {
|
|
9
|
+
return {
|
|
10
|
+
name: INTEGRATION_NAME,
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Starts a new transaction for a user interaction.
|
|
15
|
+
* @param userInteractionId Consists of `op` representation UI Event and `elementId` unique element identifier on current screen.
|
|
16
|
+
*/
|
|
17
|
+
export const startUserInteractionSpan = (userInteractionId) => {
|
|
18
|
+
const client = getClient();
|
|
19
|
+
if (!client) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const tracing = getCurrentReactNativeTracingIntegration();
|
|
23
|
+
if (!tracing) {
|
|
24
|
+
logger.log(`[${INTEGRATION_NAME}] Tracing integration is not available. Can not start user interaction span.`);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const options = client.getOptions();
|
|
28
|
+
const { elementId, op } = userInteractionId;
|
|
29
|
+
if (!options.enableUserInteractionTracing) {
|
|
30
|
+
logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing is disabled.`);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (!elementId) {
|
|
34
|
+
logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing can not create transaction with undefined elementId.`);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
if (!tracing.state.currentRoute) {
|
|
38
|
+
logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing can not create transaction without a current route.`);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const activeTransaction = getActiveSpan();
|
|
42
|
+
const activeTransactionIsNotInteraction = activeTransaction && !isSentryInteractionSpan(activeTransaction);
|
|
43
|
+
if (activeTransaction && activeTransactionIsNotInteraction) {
|
|
44
|
+
logger.warn(`[${INTEGRATION_NAME}] Did not create ${op} transaction because active transaction ${spanToJSON(activeTransaction).description} exists on the scope.`);
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const name = `${tracing.state.currentRoute}.${elementId}`;
|
|
48
|
+
if (activeTransaction &&
|
|
49
|
+
spanToJSON(activeTransaction).description === name &&
|
|
50
|
+
spanToJSON(activeTransaction).op === op) {
|
|
51
|
+
logger.warn(`[${INTEGRATION_NAME}] Did not create ${op} transaction because it the same transaction ${spanToJSON(activeTransaction).description} already exists on the scope.`);
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const scope = getCurrentScope();
|
|
55
|
+
const context = {
|
|
56
|
+
name,
|
|
57
|
+
op,
|
|
58
|
+
scope,
|
|
59
|
+
};
|
|
60
|
+
clearActiveSpanFromScope(scope);
|
|
61
|
+
const newSpan = startIdleSpan(context, {
|
|
62
|
+
idleTimeout: tracing.options.idleTimeoutMs,
|
|
63
|
+
finalTimeout: tracing.options.finalTimeoutMs,
|
|
64
|
+
});
|
|
65
|
+
newSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_AUTO_INTERACTION);
|
|
66
|
+
onlySampleIfChildSpans(client, newSpan);
|
|
67
|
+
logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing Created ${op} transaction ${name}.`);
|
|
68
|
+
return newSpan;
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=userInteraction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userInteraction.js","sourceRoot":"","sources":["../../../../src/js/tracing/integrations/userInteraction.ts"],"names":[],"mappings":"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","sourcesContent":["import { getActiveSpan, getClient, getCurrentScope, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON } from '@sentry/core';\nimport type { Integration, Span, StartSpanOptions } from '@sentry/types';\nimport { logger } from '@sentry/utils';\n\nimport type { ReactNativeClientOptions } from '../../options';\nimport { onlySampleIfChildSpans } from '../onSpanEndUtils';\nimport { SPAN_ORIGIN_AUTO_INTERACTION } from '../origin';\nimport { getCurrentReactNativeTracingIntegration } from '../reactnativetracing';\nimport { clearActiveSpanFromScope, isSentryInteractionSpan, startIdleSpan } from '../span';\n\nconst INTEGRATION_NAME = 'UserInteraction';\n\nexport const userInteractionIntegration = (): Integration => {\n return {\n name: INTEGRATION_NAME,\n };\n};\n\n/**\n * Starts a new transaction for a user interaction.\n * @param userInteractionId Consists of `op` representation UI Event and `elementId` unique element identifier on current screen.\n */\nexport const startUserInteractionSpan = (userInteractionId: {\n elementId: string | undefined;\n op: string;\n}): Span | undefined => {\n const client = getClient();\n if (!client) {\n return undefined;\n }\n\n const tracing = getCurrentReactNativeTracingIntegration();\n if (!tracing) {\n logger.log(`[${INTEGRATION_NAME}] Tracing integration is not available. Can not start user interaction span.`);\n return undefined;\n }\n\n const options = client.getOptions() as ReactNativeClientOptions;\n const { elementId, op } = userInteractionId;\n if (!options.enableUserInteractionTracing) {\n logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing is disabled.`);\n return undefined;\n }\n if (!elementId) {\n logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing can not create transaction with undefined elementId.`);\n return undefined;\n }\n if (!tracing.state.currentRoute) {\n logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing can not create transaction without a current route.`);\n return undefined;\n }\n\n const activeTransaction = getActiveSpan();\n const activeTransactionIsNotInteraction = activeTransaction && !isSentryInteractionSpan(activeTransaction);\n if (activeTransaction && activeTransactionIsNotInteraction) {\n logger.warn(\n `[${INTEGRATION_NAME}] Did not create ${op} transaction because active transaction ${\n spanToJSON(activeTransaction).description\n } exists on the scope.`,\n );\n return undefined;\n }\n\n const name = `${tracing.state.currentRoute}.${elementId}`;\n if (\n activeTransaction &&\n spanToJSON(activeTransaction).description === name &&\n spanToJSON(activeTransaction).op === op\n ) {\n logger.warn(\n `[${INTEGRATION_NAME}] Did not create ${op} transaction because it the same transaction ${\n spanToJSON(activeTransaction).description\n } already exists on the scope.`,\n );\n return undefined;\n }\n\n const scope = getCurrentScope();\n const context: StartSpanOptions = {\n name,\n op,\n scope,\n };\n clearActiveSpanFromScope(scope);\n const newSpan = startIdleSpan(context, {\n idleTimeout: tracing.options.idleTimeoutMs,\n finalTimeout: tracing.options.finalTimeoutMs,\n });\n newSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_AUTO_INTERACTION);\n onlySampleIfChildSpans(client, newSpan);\n logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing Created ${op} transaction ${name}.`);\n return newSpan;\n};\n"]}
|