@keepsnappy/react-native 0.0.0-stage → 0.1.0-alpha.12

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.
@@ -0,0 +1,133 @@
1
+ // Hermes sampling profiler around measured windows. Sampling starts when the
2
+ // first window opens. Outside a run it stops when the last open window ends, so
3
+ // overlapping windows (a press and the screen it opens) share one span and one
4
+ // file. Inside a run it samples on until `flush=1` and writes one file there.
5
+ // The file is a DevTools .cpuprofile (nodes/samples/timeDeltas) written by the
6
+ // native module into Documents/perf-lab.
7
+
8
+ export interface CpuProfileNative {
9
+ cpuProfileStart(hz: number): boolean;
10
+ cpuProfileStop(): boolean;
11
+ cpuProfileDump(fileName: string): Record<string, unknown> | null;
12
+ }
13
+
14
+ export interface CpuSpan {
15
+ span: number;
16
+ windows: number[];
17
+ startedAt: number;
18
+ started: boolean;
19
+ }
20
+
21
+ export interface CpuSpanResult {
22
+ span: number;
23
+ windows: number[];
24
+ hz: number;
25
+ started: boolean;
26
+ // Sampling goes on; the file is written at flush (or the next dump()).
27
+ deferred: boolean;
28
+ fileName: string | null;
29
+ stopMs: number;
30
+ dumpMs: number;
31
+ file: Record<string, unknown> | null;
32
+ error: string | null;
33
+ }
34
+
35
+ // `processId` makes file names unique across launches. `deferDump` is true while
36
+ // the process belongs to a run that ends with `flush=1`: then a span is never
37
+ // stopped or dumped at window end (the dump is synchronous on the JS thread and
38
+ // could land inside the next window); it keeps sampling until dump() at flush.
39
+ export const createCpuProfiler = (deps: {
40
+ native: () => CpuProfileNative | null;
41
+ now: () => number;
42
+ processId: string;
43
+ deferDump: () => boolean;
44
+ }) => {
45
+ let span: CpuSpan | null = null;
46
+ let nextSpan = 1;
47
+ let hz = 250;
48
+ let enabled = false;
49
+
50
+ const configure = (on: boolean, frequency?: number) => {
51
+ enabled = on;
52
+ if (frequency !== undefined && Number.isFinite(frequency) && frequency > 0) {
53
+ hz = Math.min(10000, Math.max(10, Math.round(frequency)));
54
+ }
55
+ };
56
+
57
+ const fileNameOf = (current: CpuSpan) =>
58
+ `cpu-${deps.processId}-${current.span}.cpuprofile`;
59
+
60
+ const windowOpened = (windowId: number) => {
61
+ if (!enabled) {
62
+ return null;
63
+ }
64
+ if (span === null) {
65
+ let started = false;
66
+ try {
67
+ started = deps.native()?.cpuProfileStart(hz) ?? false;
68
+ } catch {
69
+ started = false;
70
+ }
71
+ span = { span: nextSpan++, windows: [], startedAt: deps.now(), started };
72
+ }
73
+ span.windows.push(windowId);
74
+ return span.span;
75
+ };
76
+
77
+ const resultOf = (current: CpuSpan, deferred: boolean): CpuSpanResult => ({
78
+ span: current.span,
79
+ windows: [...current.windows],
80
+ hz,
81
+ started: current.started,
82
+ deferred,
83
+ fileName: current.started ? fileNameOf(current) : null,
84
+ stopMs: 0,
85
+ dumpMs: 0,
86
+ file: null,
87
+ error: current.started ? null : 'not started',
88
+ });
89
+
90
+ // Stops the running span and writes its file.
91
+ const dump = (): CpuSpanResult | null => {
92
+ if (span === null) {
93
+ return null;
94
+ }
95
+ const done = span;
96
+ span = null;
97
+ const result = resultOf(done, false);
98
+ if (!done.started) {
99
+ return result;
100
+ }
101
+ const native = deps.native();
102
+ try {
103
+ const t0 = deps.now();
104
+ native?.cpuProfileStop();
105
+ const t1 = deps.now();
106
+ result.file = native?.cpuProfileDump(fileNameOf(done)) ?? null;
107
+ result.stopMs = t1 - t0;
108
+ result.dumpMs = deps.now() - t1;
109
+ } catch (error) {
110
+ result.error = String(error);
111
+ }
112
+ return result;
113
+ };
114
+
115
+ // Called when a window ends; `stillOpen` is the number of windows left.
116
+ const windowClosed = (stillOpen: number): CpuSpanResult | null => {
117
+ if (span === null || stillOpen > 0) {
118
+ return null;
119
+ }
120
+ if (deps.deferDump()) {
121
+ return resultOf(span, true);
122
+ }
123
+ return dump();
124
+ };
125
+
126
+ return {
127
+ configure,
128
+ windowOpened,
129
+ windowClosed,
130
+ dump,
131
+ status: () => ({ enabled, hz, open: span !== null }),
132
+ };
133
+ };
@@ -0,0 +1,77 @@
1
+ // A dormant build that was not activated: the only thing installed is the link
2
+ // wrapper, so an activation link can reach the SDK. Its effect starts with the
3
+ // next launch. Nothing else runs: no React hook, no fetch/XHR wrap, no
4
+ // listener besides Linking, no timer, no log line, no push.
5
+
6
+ import {
7
+ createActivationStore,
8
+ type Decision,
9
+ queryOf,
10
+ } from './activation';
11
+ import { installLinks, linkPatchStatus } from './links';
12
+
13
+ interface DormantNative {
14
+ osLog?: (message: string) => void;
15
+ }
16
+
17
+ interface DormantGlobal {
18
+ __REACT_DEVTOOLS_GLOBAL_HOOK__?: unknown;
19
+ __PERFLAB_HOOK__?: { state?: { mode?: string; listener?: unknown } };
20
+ __PERF_LAB__?: unknown;
21
+ expo?: { modules?: Record<string, unknown> };
22
+ }
23
+
24
+ const g = globalThis as unknown as DormantGlobal;
25
+
26
+ const NET_MARK = '__perflabNet';
27
+
28
+ const marked = (value: unknown) =>
29
+ (typeof value === 'function' || (typeof value === 'object' && value !== null)) &&
30
+ Object.prototype.hasOwnProperty.call(value, NET_MARK);
31
+
32
+ // Reads the global's descriptor, not its value: fetch and XMLHttpRequest are
33
+ // lazy getters in React Native and the check must not evaluate them.
34
+ const globalWrapped = (name: string) => {
35
+ const descriptor = Object.getOwnPropertyDescriptor(g, name);
36
+ if (!descriptor) {
37
+ return false;
38
+ }
39
+ return descriptor.get ? marked(descriptor.get) : marked(descriptor.value);
40
+ };
41
+
42
+ const xhrPatched = () => {
43
+ const descriptor = Object.getOwnPropertyDescriptor(g, 'XMLHttpRequest');
44
+ if (!descriptor || descriptor.get) {
45
+ return false;
46
+ }
47
+ const proto = (descriptor.value as { prototype?: unknown } | undefined)?.prototype;
48
+ return marked(proto);
49
+ };
50
+
51
+ // The checks behind `dump=1` in a dormant process (S8 invariants).
52
+ export const dormantReport = (decision: Decision) => ({
53
+ mode: 'dormant',
54
+ buildMode: decision.mode,
55
+ read: decision.read,
56
+ reactHook: typeof g.__REACT_DEVTOOLS_GLOBAL_HOOK__,
57
+ perflabHook: g.__PERFLAB_HOOK__ ? (g.__PERFLAB_HOOK__.state?.mode ?? 'present') : 'absent',
58
+ sdkGlobal: g.__PERF_LAB__ !== undefined,
59
+ fetchWrapped: globalWrapped('fetch'),
60
+ xhrGetterWrapped: globalWrapped('XMLHttpRequest'),
61
+ xhrPatched: xhrPatched(),
62
+ links: linkPatchStatus(),
63
+ });
64
+
65
+ export const startDormant = (decision: Decision) => {
66
+ const store = createActivationStore(decision.record);
67
+ installLinks(url => {
68
+ const params = queryOf(url);
69
+ store.apply(params);
70
+ if (params.dump === '1') {
71
+ // Asked for: one os_log line (iOS) / logcat line (Android), no log file.
72
+ const native = g.expo?.modules?.PerfLab as DormantNative | undefined;
73
+ const report = { ...dormantReport(decision), activation: store.stats() };
74
+ native?.osLog?.(`PERF_LAB dormant ${JSON.stringify(report)}`);
75
+ }
76
+ });
77
+ };
@@ -0,0 +1,2 @@
1
+ // Inlined by babel-preset-expo at build time.
2
+ declare const process: { env: Record<string, string | undefined> };