@keepsnappy/react-native 0.1.0-alpha.12 → 0.1.0-alpha.14

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/js/sdk/core.ts CHANGED
@@ -1,12 +1,12 @@
1
- // perf-lab core: settings, windows, the commit walk, observers, faults, logs.
1
+ // keepsnappy core: settings, windows, the commit walk, observers, faults, logs.
2
2
  // Loaded by boot.ts, which Metro runs before the app's entry module.
3
3
 
4
4
  import {
5
5
  type ActivationStore,
6
6
  type Decision,
7
7
  MODE,
8
- PERF_LAB_DEEP_LINK_PATH,
9
- PERF_LAB_ENABLED,
8
+ KEEPSNAPPY_DEEP_LINK_PATH,
9
+ KEEPSNAPPY_ENABLED,
10
10
  parseQuery,
11
11
  type PolyfillActivation,
12
12
  } from './activation';
@@ -32,6 +32,7 @@ import {
32
32
  summarizeArtifacts,
33
33
  type WrittenFile,
34
34
  } from './artifacts';
35
+ import { createPendingArtifacts, type EarlierArtifacts } from './artifact-pending';
35
36
  import {
36
37
  backgroundSource,
37
38
  type Carrier,
@@ -90,7 +91,7 @@ import {
90
91
  windowAggregate,
91
92
  } from './sampler';
92
93
 
93
- export { PERF_LAB_DEEP_LINK_PATH, PERF_LAB_ENABLED, parseQuery };
94
+ export { KEEPSNAPPY_DEEP_LINK_PATH, KEEPSNAPPY_ENABLED, parseQuery };
94
95
 
95
96
  type LogSink = 'console' | 'native' | 'both';
96
97
 
@@ -126,7 +127,7 @@ interface Settings extends SamplerSettings {
126
127
  diag: boolean;
127
128
  }
128
129
 
129
- interface PerfLabNative extends Partial<CpuProfileNative> {
130
+ interface KeepSnappyNative extends Partial<CpuProfileNative> {
130
131
  beginAsyncSection(name: string, cookie: number): void;
131
132
  endAsyncSection(name: string, cookie: number): void;
132
133
  setCounter(name: string, value: number): void;
@@ -143,13 +144,16 @@ interface PerfLabNative extends Partial<CpuProfileNative> {
143
144
  samplerRecent(sinceT: number): SamplerRecord[];
144
145
  samplerNow(): number;
145
146
  flushSignal?(status: string, detail: string): void;
146
- // GC before the action: Android finishes the pending command broadcast with this result; iOS shows it on `perflab.ack`.
147
+ // The list of profile files not uploaded yet, with run tokens: internal storage, read by a later process.
148
+ artifactsPendingRead?(): string | null;
149
+ artifactsPendingWrite?(content: string): boolean;
150
+ // Command answer (`gc:<n> ok`, `memread:<n> ok`): Android finishes the pending command broadcast with this result; iOS shows it on `keepsnappy.ack`.
147
151
  commandDone?(n: number, data: string): void;
148
152
  /** Android: whether the broadcast of command n still waits for its answer (native finishes it at a deadline). */
149
153
  commandPending?(n: number): boolean;
150
154
  ackSignal?(text: string): void;
151
155
  commandListen?(on: boolean): void;
152
- addListener?(event: 'perflabCommand', listener: (e: { cmd: string; n: number; tNative: number }) => void): { remove(): void };
156
+ addListener?(event: 'keepsnappyCommand', listener: (e: { cmd: string; n: number; tNative: number }) => void): { remove(): void };
153
157
  }
154
158
 
155
159
  interface RendererInternals {
@@ -232,7 +236,7 @@ interface PolyfillHook {
232
236
  attach: (listener: HookListener | null) => void;
233
237
  }
234
238
 
235
- interface PerfLabGlobal {
239
+ interface KeepSnappyGlobal {
236
240
  HermesInternal?: HermesInternalApi;
237
241
  PerformanceObserver?: PerfObserverCtor & { supportedEntryTypes?: string[] };
238
242
  performance?: PerformanceLike;
@@ -240,12 +244,12 @@ interface PerfLabGlobal {
240
244
  console: { log: (...args: unknown[]) => void; timeStamp?: unknown };
241
245
  expo?: { modules?: Record<string, unknown> };
242
246
  __REACT_DEVTOOLS_GLOBAL_HOOK__?: Record<string, unknown>;
243
- __PERFLAB_HOOK__?: PolyfillHook;
244
- __PERFLAB_ACTIVATION__?: PolyfillActivation;
245
- __PERF_LAB__?: unknown;
247
+ __KEEPSNAPPY_HOOK__?: PolyfillHook;
248
+ __KEEPSNAPPY_ACTIVATION__?: PolyfillActivation;
249
+ __KEEPSNAPPY__?: unknown;
246
250
  }
247
251
 
248
- const g = globalThis as unknown as PerfLabGlobal;
252
+ const g = globalThis as unknown as KeepSnappyGlobal;
249
253
 
250
254
  const NO_FAULTS: Faults = {
251
255
  cpu: 0,
@@ -296,14 +300,14 @@ export const now = () => (g.performance ? g.performance.now() : Date.now());
296
300
  export const fmt = (ms: number) => ms.toFixed(3);
297
301
  const noop = () => undefined;
298
302
 
299
- let nativeModule: PerfLabNative | null | undefined;
303
+ let nativeModule: KeepSnappyNative | null | undefined;
300
304
  let nativeMissingLogged = false;
301
305
 
302
- const getNative = (): PerfLabNative | null => {
306
+ const getNative = (): KeepSnappyNative | null => {
303
307
  if (nativeModule) {
304
308
  return nativeModule;
305
309
  }
306
- const found = g.expo?.modules?.PerfLab as PerfLabNative | undefined;
310
+ const found = g.expo?.modules?.KeepSnappy as KeepSnappyNative | undefined;
307
311
  if (found) {
308
312
  nativeModule = found;
309
313
  return found;
@@ -311,7 +315,7 @@ const getNative = (): PerfLabNative | null => {
311
315
  if (!nativeMissingLogged) {
312
316
  nativeMissingLogged = true;
313
317
  g.console.log(
314
- 'PERF_LAB native module PerfLab missing (expo.modules.PerfLab)',
318
+ 'KEEPSNAPPY native module KeepSnappy missing (expo.modules.KeepSnappy)',
315
319
  );
316
320
  }
317
321
  return null;
@@ -344,10 +348,10 @@ const cpu = createCpuProfiler({
344
348
  });
345
349
 
346
350
  export const perfLog = (message: string, sink: LogSink = settings.log) => {
347
- if (!PERF_LAB_ENABLED) {
351
+ if (!KEEPSNAPPY_ENABLED) {
348
352
  return;
349
353
  }
350
- const line = `PERF_LAB ${message}`;
354
+ const line = `KEEPSNAPPY ${message}`;
351
355
  if (sink === 'console' || sink === 'both') {
352
356
  g.console.log(line);
353
357
  }
@@ -412,7 +416,7 @@ const nativeSamplerNow = () => {
412
416
 
413
417
  // On-demand sample: logged natively like the automatic ones and returned. Null when the sampler is off.
414
418
  export const sample = (reason = 'manual'): SamplerRecord | null => {
415
- if (!PERF_LAB_ENABLED || samplerState.mode === 'off') {
419
+ if (!KEEPSNAPPY_ENABLED || samplerState.mode === 'off') {
416
420
  return null;
417
421
  }
418
422
  try {
@@ -816,7 +820,7 @@ const clearIdleGc = () => {
816
820
 
817
821
  const scheduleIdleGc = () => {
818
822
  clearIdleGc();
819
- if (!PERF_LAB_ENABLED || !settings.gcwin || openWindows.size > 0) {
823
+ if (!KEEPSNAPPY_ENABLED || !settings.gcwin || openWindows.size > 0) {
820
824
  return;
821
825
  }
822
826
  idleGcTimer = setTimeout(() => {
@@ -835,7 +839,7 @@ export const lastCommitAt = (key: string) =>
835
839
  export const isWindowOpen = (key?: string) =>
836
840
  key ? openWindows.has(key) : openWindows.size > 0;
837
841
 
838
- const traceName = (name: string) => `perf-lab:${name}`;
842
+ const traceName = (name: string) => `keepsnappy:${name}`;
839
843
 
840
844
  interface StartOptions {
841
845
  key?: string;
@@ -962,7 +966,7 @@ const endBackground = (reason: string) => {
962
966
  };
963
967
 
964
968
  const start = (name: string, options: StartOptions = {}) => {
965
- if (!PERF_LAB_ENABLED || !settings.marks) {
969
+ if (!KEEPSNAPPY_ENABLED || !settings.marks) {
966
970
  return null;
967
971
  }
968
972
  const key = options.key ?? name;
@@ -1091,7 +1095,7 @@ const rename = (key: string, name: string) => {
1091
1095
  };
1092
1096
 
1093
1097
  const point = (name: string) => {
1094
- if (!PERF_LAB_ENABLED || !settings.marks) {
1098
+ if (!KEEPSNAPPY_ENABLED || !settings.marks) {
1095
1099
  return;
1096
1100
  }
1097
1101
  const t = now();
@@ -1277,15 +1281,15 @@ const onCommitFiberUnmount = (_rendererId: number, fiber: Fiber) => {
1277
1281
  };
1278
1282
 
1279
1283
  export const lastRoot = (rendererId = 1): FiberRoot | null =>
1280
- g.__PERFLAB_HOOK__?.state.lastRootByRenderer[rendererId] ?? null;
1284
+ g.__KEEPSNAPPY_HOOK__?.state.lastRootByRenderer[rendererId] ?? null;
1281
1285
 
1282
1286
  // The polyfill (withKeepSnappy) installed the hook before React loaded; attach to
1283
1287
  // it. Without the polyfill, fall back to installing it now, as the prototype did.
1284
1288
  export const attachHook = () => {
1285
- if (!PERF_LAB_ENABLED || hookState.installed) {
1289
+ if (!KEEPSNAPPY_ENABLED || hookState.installed) {
1286
1290
  return;
1287
1291
  }
1288
- const polyfill = g.__PERFLAB_HOOK__;
1292
+ const polyfill = g.__KEEPSNAPPY_HOOK__;
1289
1293
  if (polyfill) {
1290
1294
  polyfill.ensure();
1291
1295
  polyfill.attach({
@@ -1310,7 +1314,7 @@ export const attachHook = () => {
1310
1314
  }
1311
1315
  if (g.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
1312
1316
  hookState.preexisting = true;
1313
- g.console.log('PERF_LAB hook already present, not replacing it');
1317
+ g.console.log('KEEPSNAPPY hook already present, not replacing it');
1314
1318
  return;
1315
1319
  }
1316
1320
  let nextRendererId = 1;
@@ -1327,7 +1331,7 @@ export const attachHook = () => {
1327
1331
  bundleType: internals.bundleType,
1328
1332
  });
1329
1333
  g.console.log(
1330
- `PERF_LAB hook inject id=${id} renderer=${internals.rendererPackageName} version=${internals.version} bundleType=${internals.bundleType}`,
1334
+ `KEEPSNAPPY hook inject id=${id} renderer=${internals.rendererPackageName} version=${internals.version} bundleType=${internals.bundleType}`,
1331
1335
  );
1332
1336
  return id;
1333
1337
  },
@@ -1617,6 +1621,17 @@ export const setArtifactHandler = (handler: ArtifactHandler | null) => {
1617
1621
  // Entries a window interrupted wait for the next flush of this process.
1618
1622
  let artifactBacklog: ArtifactEntry[] = [];
1619
1623
 
1624
+ const pendingArtifacts = createPendingArtifacts(() => {
1625
+ const native = getNative();
1626
+ if (typeof native?.artifactsPendingRead !== 'function' || typeof native.artifactsPendingWrite !== 'function') {
1627
+ return null;
1628
+ }
1629
+ return {
1630
+ read: () => native.artifactsPendingRead!(),
1631
+ write: content => native.artifactsPendingWrite!(content),
1632
+ };
1633
+ }, perfLog);
1634
+
1620
1635
  const writeManifest = (entries: ArtifactEntry[], upload: ArtifactUploadSummary | null) => {
1621
1636
  try {
1622
1637
  const manifest = buildManifest({
@@ -1662,11 +1677,39 @@ const sendArtifacts = async (fresh: ArtifactEntry[], deadline: number) => {
1662
1677
  summary = summarizeArtifacts(entries, 0, 'push not configured');
1663
1678
  }
1664
1679
  artifactBacklog = entries.filter(entry => entry.state === 'pending' || entry.state === 'failed');
1680
+ pendingArtifacts.note(PROCESS_ID, entries, runToken);
1665
1681
  writeManifest(entries, summary);
1666
1682
  perfLog(`artifacts ${JSON.stringify(summary)}`);
1667
1683
  return summary;
1668
1684
  };
1669
1685
 
1686
+ // After this process's own files: what earlier processes left (a flush that never finished, a kill during the upload), each
1687
+ // under its own run token and process id, within the same deadline.
1688
+ const sendEarlierArtifacts = async (deadline: number) => {
1689
+ if (!artifactHandler) {
1690
+ return;
1691
+ }
1692
+ const groups: EarlierArtifacts[] = pendingArtifacts.earlier(PROCESS_ID);
1693
+ if (groups.length === 0) {
1694
+ return;
1695
+ }
1696
+ const tried: EarlierArtifacts[] = [];
1697
+ for (const group of groups) {
1698
+ // What does not fit in this flush waits for the next one, with no try counted.
1699
+ if (Date.now() >= deadline) {
1700
+ break;
1701
+ }
1702
+ tried.push(group);
1703
+ try {
1704
+ const summary = await artifactHandler(group.entries, null, group.processId, deadline);
1705
+ perfLog(`artifacts earlier ${JSON.stringify({ processId: group.processId, ...summary })}`);
1706
+ } catch (error) {
1707
+ perfLog(`artifacts earlier failed ${String(error)}`);
1708
+ }
1709
+ }
1710
+ pendingArtifacts.settleEarlier(tried);
1711
+ };
1712
+
1670
1713
  // Declared on every window line so the backend can tell windows of this model from older ones (REQ-VER-34).
1671
1714
  // 2: a tap or scroll window runs from its touch to the next touch, the background or the flush; no quiet rule, no `+3s` twin.
1672
1715
  // 3: model 2, and a screen window also ends at the next action, the next screen, the background or the flush; no 3 s cap.
@@ -1753,7 +1796,7 @@ const finalizeWindow = (window: MarkerWindow) => {
1753
1796
  const writeWindowRecord = (window: MarkerWindow, summary: Record<string, unknown>) => {
1754
1797
  const json = JSON.stringify(summary);
1755
1798
  perfLog(`window ${json}`);
1756
- windowListener?.(`${Date.now()} PERF_LAB window ${json}`);
1799
+ windowListener?.(`${Date.now()} KEEPSNAPPY window ${json}`);
1757
1800
  if (settings.file) {
1758
1801
  const path = getNative()?.writeText(
1759
1802
  `window-${window.id}-${window.name.replace(/[^\w.:+-]/g, '_')}.json`,
@@ -1948,7 +1991,7 @@ const forwardSamples = () => {
1948
1991
  if (record.t <= samplesSentT) {
1949
1992
  continue;
1950
1993
  }
1951
- windowListener(`${record.wallMs} PERF_LAB sample ${JSON.stringify(record)}`);
1994
+ windowListener(`${record.wallMs} KEEPSNAPPY sample ${JSON.stringify(record)}`);
1952
1995
  samplesSentT = record.t;
1953
1996
  }
1954
1997
  } catch (error) {
@@ -1973,7 +2016,7 @@ export const writeRunEvent = (kind: 'gc' | 'appstate', record: Record<string, un
1973
2016
  const json = JSON.stringify({ ...record, t: now(), wallMs: Date.now() });
1974
2017
  perfLog(`${kind} ${json}`);
1975
2018
  if (inRun() && (kind === 'gc' || gcRanSinceFlush)) {
1976
- windowListener?.(`${Date.now()} PERF_LAB ${kind} ${json}`);
2019
+ windowListener?.(`${Date.now()} KEEPSNAPPY ${kind} ${json}`);
1977
2020
  }
1978
2021
  };
1979
2022
 
@@ -1995,9 +2038,13 @@ export const setFlushHandler = (handler: FlushHandler | null) => {
1995
2038
  };
1996
2039
 
1997
2040
  // `flush=1`: end the open windows, send every unsent line, then show the result
1998
- // to the test driver as an accessibility element (iOS: id `perflab.flushed`,
2041
+ // to the test driver as an accessibility element (iOS: id `keepsnappy.flushed`,
1999
2042
  // label `ok` / `error` / `off`), so a flow can wait for it without network access.
2000
- const flushOnce = async () => {
2043
+ // The driver's name for one flush link (`ack=<n>`), echoed at the end of the answer's detail: the answer element stays up
2044
+ // until the next one replaces it, so without it a later flush in the same process could read an earlier flush's answer.
2045
+ const ACK_PATTERN = /^[A-Za-z0-9_-]{1,32}$/;
2046
+
2047
+ const flushOnce = async (ack: string | undefined) => {
2001
2048
  // One deadline for the whole flush (lines, clock, artifacts); the end report of the upload comes after it.
2002
2049
  const deadline = Date.now() + ARTIFACT_UPLOAD_DEADLINE_MS;
2003
2050
  endAll('flush');
@@ -2008,6 +2055,8 @@ const flushOnce = async () => {
2008
2055
  } catch (error) {
2009
2056
  perfLog(`flush files failed ${String(error)}`);
2010
2057
  }
2058
+ // Before anything that waits on the network: a flush that never ends, or a process killed now, leaves the list to a later one.
2059
+ pendingArtifacts.note(PROCESS_ID, [...artifactBacklog, ...artifacts], runToken);
2011
2060
  let status = 'off';
2012
2061
  let detail = 'push not configured';
2013
2062
  if (flushHandler) {
@@ -2026,10 +2075,25 @@ const flushOnce = async () => {
2026
2075
  const sent = await sendArtifacts(artifacts, deadline);
2027
2076
  if (sent) {
2028
2077
  detail += ` artifacts=${sent.uploaded}/${sent.total}`;
2078
+ // A profile file of a run that did not reach the API is missing data, whatever kept it back: failed after its tries, left
2079
+ // unsent by the deadline, kept on the device (token refused, no endpoint, no native upload) or refused by the API.
2080
+ const missing = sent.failed + sent.pending + (runToken !== null ? sent.local + sent.rejected : 0);
2081
+ if (status === 'ok' && missing > 0) {
2082
+ status = 'error';
2083
+ detail += ` failed=${sent.failed} pending=${sent.pending} local=${sent.local} rejected=${sent.rejected}`;
2084
+ }
2029
2085
  }
2030
2086
  } catch (error) {
2031
2087
  perfLog(`artifacts failed ${String(error)}`);
2032
2088
  }
2089
+ try {
2090
+ await sendEarlierArtifacts(deadline);
2091
+ } catch (error) {
2092
+ perfLog(`artifacts earlier failed ${String(error)}`);
2093
+ }
2094
+ if (ack !== undefined && ACK_PATTERN.test(ack)) {
2095
+ detail += ` ack=${ack}`;
2096
+ }
2033
2097
  perfLog(`flush ${status} ${detail}`);
2034
2098
  try {
2035
2099
  getNative()?.flushSignal?.(status, detail);
@@ -2041,11 +2105,11 @@ const flushOnce = async () => {
2041
2105
  // A second flush link while one runs (the driver's final flush) waits for it: the flush signal means the
2042
2106
  // uploads of the first are done, so it must not be raised by a flush that has nothing left to send.
2043
2107
  let flushing: Promise<void> | null = null;
2044
- export const flushNow = (): Promise<void> => {
2108
+ export const flushNow = (ack?: string): Promise<void> => {
2045
2109
  commandAnswers.clear();
2046
2110
  gcRanSinceFlush = false;
2047
2111
  // The first flush starts at once (it ends the open windows in the same tick as the link).
2048
- const run = flushing === null ? flushOnce() : flushing.then(flushOnce);
2112
+ const run = flushing === null ? flushOnce(ack) : flushing.then(() => flushOnce(ack));
2049
2113
  const tail: Promise<void> = run
2050
2114
  .catch(() => undefined)
2051
2115
  .then(() => {
@@ -2059,7 +2123,7 @@ export const flushNow = (): Promise<void> => {
2059
2123
 
2060
2124
  // The activation's profile/cpuprof turn those on for the whole process; off
2061
2125
  // leaves the build's env defaults.
2062
- // A PerfLab run (one with a run token) forces an idle GC between windows so heap-after-GC
2126
+ // A KeepSnappy run (one with a run token) forces an idle GC between windows so heap-after-GC
2063
2127
  // readings exist for the leak slope (plan 41 Q7); `gcwin=0` in a link still turns it off.
2064
2128
  const applyDecision = (decision: Decision) => {
2065
2129
  settings.diag = decision.diag;
@@ -2093,13 +2157,13 @@ const activationReport = () => {
2093
2157
  mode: 'active',
2094
2158
  buildMode: decision.mode,
2095
2159
  read: decision.read,
2096
- installMs: g.__PERFLAB_ACTIVATION__?.installMs,
2160
+ installMs: g.__KEEPSNAPPY_ACTIVATION__?.installMs,
2097
2161
  runToken: runToken !== null,
2098
2162
  expiresAt: decision.expiresAt,
2099
2163
  diag: decision.diag,
2100
2164
  profile: decision.profile,
2101
2165
  cpuprof: decision.cpuprof,
2102
- renderer: g.__PERFLAB_ACTIVATION__?.renderer ?? null,
2166
+ renderer: g.__KEEPSNAPPY_ACTIVATION__?.renderer ?? null,
2103
2167
  next: next
2104
2168
  ? {
2105
2169
  active: next.active,
@@ -2242,10 +2306,32 @@ export const actionGc = (n: number, tNative?: number): string => {
2242
2306
  }
2243
2307
  // The gc-pre / gc-post records go out now: the backend pairs them with this command by their seq.
2244
2308
  forwardSamples();
2309
+ rememberAnswer(key, answer);
2310
+ answerCommand(n, answer);
2311
+ return answer;
2312
+ };
2313
+
2314
+ const rememberAnswer = (key: string, answer: string) => {
2245
2315
  commandAnswers.set(key, answer);
2246
2316
  if (commandAnswers.size > COMMAND_ANSWERS_KEPT) {
2247
2317
  commandAnswers.delete(commandAnswers.keys().next().value as string);
2248
2318
  }
2319
+ };
2320
+
2321
+ // The end GC's manual memory read: the driver's `memread` command, sent with the GC's own n after the GC's answer. One sample, at
2322
+ // once (no timer), reason `endgc-read:<n>`, sent with the run's lines before the answer; the answer is the driver's signal that the
2323
+ // app may go to the background. Derive reads kept from this record (derivation 23).
2324
+ export const endGcRead = (n: number): string => {
2325
+ const key = `${runToken ?? '-'}#read${n}`;
2326
+ const known = commandAnswers.get(key);
2327
+ if (known !== undefined) {
2328
+ answerCommand(n, known);
2329
+ return known;
2330
+ }
2331
+ const record = sample(`endgc-read:${n}`);
2332
+ const answer = `memread:${n} ${record ? 'ok' : 'unavailable'}`;
2333
+ forwardSamples();
2334
+ rememberAnswer(key, answer);
2249
2335
  answerCommand(n, answer);
2250
2336
  return answer;
2251
2337
  };
@@ -2258,44 +2344,48 @@ const syncCommandListener = () => {
2258
2344
  if (!native?.commandListen) {
2259
2345
  return;
2260
2346
  }
2261
- const on = PERF_LAB_ENABLED && runToken !== null;
2347
+ const on = KEEPSNAPPY_ENABLED && runToken !== null;
2262
2348
  if (on && commandSubscription === null) {
2263
2349
  commandSubscription =
2264
- native.addListener?.('perflabCommand', e => {
2350
+ native.addListener?.('keepsnappyCommand', e => {
2265
2351
  // A command native already timed out on is not run: its GC would land in the next step's action.
2266
2352
  if (native.commandPending?.(e.n) === false) {
2267
- perfLog(`command gc:${e.n} late, skipped`);
2353
+ perfLog(`command ${e.cmd}:${e.n} late, skipped`);
2268
2354
  return;
2269
2355
  }
2270
- handlePerfLabCommand(e.cmd, e.n, e.tNative);
2356
+ handleKeepSnappyCommand(e.cmd, e.n, e.tNative);
2271
2357
  }) ?? null;
2272
2358
  }
2273
2359
  native.commandListen(on);
2274
2360
  };
2275
2361
 
2276
2362
  /** A command (`cmd=<name>&n=<n>`), from a link (iOS) or the native receiver (Android); unknown commands are ignored. */
2277
- export const handlePerfLabCommand = (cmd: string | undefined, n: number, tNative?: number): boolean => {
2278
- if (!PERF_LAB_ENABLED || cmd !== 'gc' || !Number.isInteger(n) || n < 1) {
2363
+ export const handleKeepSnappyCommand = (cmd: string | undefined, n: number, tNative?: number): boolean => {
2364
+ if (!KEEPSNAPPY_ENABLED || (cmd !== 'gc' && cmd !== 'memread') || !Number.isInteger(n) || n < 1) {
2279
2365
  return false;
2280
2366
  }
2281
2367
  // Every received command is answered, so the Android receiver is never left pending.
2282
2368
  try {
2283
- actionGc(n, tNative);
2369
+ if (cmd === 'gc') {
2370
+ actionGc(n, tNative);
2371
+ } else {
2372
+ endGcRead(n);
2373
+ }
2284
2374
  } catch (error) {
2285
- perfLog(`command gc:${n} failed ${String(error)}`);
2286
- answerCommand(n, `gc:${n} error`);
2375
+ perfLog(`command ${cmd}:${n} failed ${String(error)}`);
2376
+ answerCommand(n, `${cmd}:${n} error`);
2287
2377
  }
2288
2378
  return true;
2289
2379
  };
2290
2380
 
2291
- export const handlePerfLabDeepLink = (url: string) => {
2292
- if (!PERF_LAB_ENABLED) {
2381
+ export const handleKeepSnappyDeepLink = (url: string) => {
2382
+ if (!KEEPSNAPPY_ENABLED) {
2293
2383
  return;
2294
2384
  }
2295
2385
  const params = parseQuery(url.split('?')[1]);
2296
2386
  // A command changes no settings and writes no activation record.
2297
2387
  if (params.cmd !== undefined) {
2298
- handlePerfLabCommand(params.cmd, Number(params.n));
2388
+ handleKeepSnappyCommand(params.cmd, Number(params.n));
2299
2389
  return;
2300
2390
  }
2301
2391
  const written = activation?.store.apply(params) ?? null;
@@ -2306,7 +2396,7 @@ export const handlePerfLabDeepLink = (url: string) => {
2306
2396
  }
2307
2397
  applySettings(params);
2308
2398
  if (params.flush === '1') {
2309
- void flushNow();
2399
+ void flushNow(params.ack);
2310
2400
  }
2311
2401
  };
2312
2402
 
@@ -2326,7 +2416,7 @@ export const addStartupSection = (name: string, read: () => unknown) => {
2326
2416
 
2327
2417
  export const logStartup = (phase: string) => {
2328
2418
  const native = getNative();
2329
- const polyfill = g.__PERFLAB_HOOK__?.state;
2419
+ const polyfill = g.__KEEPSNAPPY_HOOK__?.state;
2330
2420
  const extra: Record<string, unknown> = {};
2331
2421
  for (const [name, read] of extraStartupSections) {
2332
2422
  extra[name] = safe(read);
@@ -2391,7 +2481,7 @@ export const logStartup = (phase: string) => {
2391
2481
  };
2392
2482
 
2393
2483
  export const exposeGlobal = () => {
2394
- g.__PERF_LAB__ = {
2484
+ g.__KEEPSNAPPY__ = {
2395
2485
  settings,
2396
2486
  set: applySettings,
2397
2487
  markers,
package/js/sdk/cpuprof.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // overlapping windows (a press and the screen it opens) share one span and one
4
4
  // file. Inside a run it samples on until `flush=1` and writes one file there.
5
5
  // The file is a DevTools .cpuprofile (nodes/samples/timeDeltas) written by the
6
- // native module into Documents/perf-lab.
6
+ // native module into Documents/keepsnappy.
7
7
 
8
8
  export interface CpuProfileNative {
9
9
  cpuProfileStart(hz: number): boolean;
package/js/sdk/dormant.ts CHANGED
@@ -16,14 +16,14 @@ interface DormantNative {
16
16
 
17
17
  interface DormantGlobal {
18
18
  __REACT_DEVTOOLS_GLOBAL_HOOK__?: unknown;
19
- __PERFLAB_HOOK__?: { state?: { mode?: string; listener?: unknown } };
20
- __PERF_LAB__?: unknown;
19
+ __KEEPSNAPPY_HOOK__?: { state?: { mode?: string; listener?: unknown } };
20
+ __KEEPSNAPPY__?: unknown;
21
21
  expo?: { modules?: Record<string, unknown> };
22
22
  }
23
23
 
24
24
  const g = globalThis as unknown as DormantGlobal;
25
25
 
26
- const NET_MARK = '__perflabNet';
26
+ const NET_MARK = '__keepsnappyNet';
27
27
 
28
28
  const marked = (value: unknown) =>
29
29
  (typeof value === 'function' || (typeof value === 'object' && value !== null)) &&
@@ -54,8 +54,8 @@ export const dormantReport = (decision: Decision) => ({
54
54
  buildMode: decision.mode,
55
55
  read: decision.read,
56
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,
57
+ keepsnappyHook: g.__KEEPSNAPPY_HOOK__ ? (g.__KEEPSNAPPY_HOOK__.state?.mode ?? 'present') : 'absent',
58
+ sdkGlobal: g.__KEEPSNAPPY__ !== undefined,
59
59
  fetchWrapped: globalWrapped('fetch'),
60
60
  xhrGetterWrapped: globalWrapped('XMLHttpRequest'),
61
61
  xhrPatched: xhrPatched(),
@@ -69,9 +69,9 @@ export const startDormant = (decision: Decision) => {
69
69
  store.apply(params);
70
70
  if (params.dump === '1') {
71
71
  // Asked for: one os_log line (iOS) / logcat line (Android), no log file.
72
- const native = g.expo?.modules?.PerfLab as DormantNative | undefined;
72
+ const native = g.expo?.modules?.KeepSnappy as DormantNative | undefined;
73
73
  const report = { ...dormantReport(decision), activation: store.stats() };
74
- native?.osLog?.(`PERF_LAB dormant ${JSON.stringify(report)}`);
74
+ native?.osLog?.(`KEEPSNAPPY dormant ${JSON.stringify(report)}`);
75
75
  }
76
76
  });
77
77
  };
package/js/sdk/links.ts CHANGED
@@ -1,31 +1,31 @@
1
- // Settings links (`<scheme>://devtools/perf-lab?...`) without app code: the SDK
1
+ // Settings links (`<scheme>://devtools/keepsnappy?...`) without app code: the SDK
2
2
  // runs before the router subscribes to Linking, so it wraps the link sources.
3
- // A perf-lab link goes to the handler here and never reaches the app's
3
+ // A keepsnappy link goes to the handler here and never reaches the app's
4
4
  // listeners, so the router does not navigate to an unmatched route. Only a URL
5
- // whose host and path are exactly `devtools/perf-lab` on a custom (non-web)
5
+ // whose host and path are exactly `devtools/keepsnappy` on a custom (non-web)
6
6
  // scheme is one; every other URL passes through unchanged after one substring
7
7
  // check: in a dormant build this is the only work the SDK does after startup.
8
8
 
9
9
  import { Linking } from 'react-native';
10
10
 
11
- import { PERF_LAB_DEEP_LINK_PATH } from './activation';
11
+ import { KEEPSNAPPY_DEEP_LINK_PATH } from './activation';
12
12
 
13
13
  export type LinkHandler = (url: string, via: string) => void;
14
- // Told when an app URL (not a perf-lab one) is handed to the app, before the
14
+ // Told when an app URL (not a keepsnappy one) is handed to the app, before the
15
15
  // app sees it: the start of a link-driven navigation.
16
16
  export type AppLinkHandler = (url: string, via: string) => void;
17
17
 
18
18
  type UrlListener = (event: { url: string }) => void;
19
19
 
20
- // `<scheme>://devtools/perf-lab[/][?query][#fragment]`, also with an empty host
21
- // (`<scheme>:///devtools/perf-lab`). Universal links (http/https) never match,
20
+ // `<scheme>://devtools/keepsnappy[/][?query][#fragment]`, also with an empty host
21
+ // (`<scheme>:///devtools/keepsnappy`). Universal links (http/https) never match,
22
22
  // so a web page cannot route a link here.
23
- // Spells out PERF_LAB_DEEP_LINK_PATH.
24
- const SETTINGS_LINK = /^([a-z][a-z0-9+.-]*):\/\/\/?devtools\/perf-lab\/?(?:[?#]|$)/i;
23
+ // Spells out KEEPSNAPPY_DEEP_LINK_PATH.
24
+ const SETTINGS_LINK = /^([a-z][a-z0-9+.-]*):\/\/\/?devtools\/keepsnappy\/?(?:[?#]|$)/i;
25
25
  const WEB_SCHEMES = new Set(['http', 'https']);
26
26
 
27
- export const isPerfLabUrl = (url: unknown): url is string => {
28
- if (typeof url !== 'string' || !url.includes(PERF_LAB_DEEP_LINK_PATH)) {
27
+ export const isKeepSnappyUrl = (url: unknown): url is string => {
28
+ if (typeof url !== 'string' || !url.includes(KEEPSNAPPY_DEEP_LINK_PATH)) {
29
29
  return false;
30
30
  }
31
31
  const match = SETTINGS_LINK.exec(url);
@@ -126,7 +126,7 @@ const patch = (
126
126
  };
127
127
 
128
128
  const filterInitial = (url: unknown, via: string) => {
129
- if (isPerfLabUrl(url)) {
129
+ if (isKeepSnappyUrl(url)) {
130
130
  apply(url, via);
131
131
  return null;
132
132
  }
@@ -157,7 +157,7 @@ export const installLinks = (
157
157
  return original(type, listener);
158
158
  }
159
159
  const filtered: UrlListener = event => {
160
- if (isPerfLabUrl(event?.url)) {
160
+ if (isKeepSnappyUrl(event?.url)) {
161
161
  apply(event.url, 'event');
162
162
  return;
163
163
  }
package/js/sdk/net.ts CHANGED
@@ -403,8 +403,8 @@ export type NetCounter = ReturnType<typeof createNetCounter>;
403
403
  // images loaded natively (Image, expo-image), WebSockets, and native SDKs
404
404
  // (Stream, LiveKit, Firebase).
405
405
 
406
- const WRAPPED = '__perflabNet';
407
- const XHR_STATE = '__perflabNetXhr';
406
+ const WRAPPED = '__keepsnappyNet';
407
+ const XHR_STATE = '__keepsnappyNetXhr';
408
408
  const EXPO_BUILTIN = Symbol.for('expo.builtin');
409
409
 
410
410
  type AnyFn = (...args: unknown[]) => unknown;
@@ -178,7 +178,7 @@ const changeDescription = (cause: UpdateCause) => ({
178
178
 
179
179
  export interface ReactExport {
180
180
  version: 5;
181
- source: 'perflab';
181
+ source: 'keepsnappy';
182
182
  captureStart: { clock: 'perf-now-ms'; t0: number };
183
183
  dataForRoots: {
184
184
  rootID: number;
@@ -225,11 +225,11 @@ export const buildReactExport = (t0: number, commits: ExportCommit[]): ReactExpo
225
225
  passiveEffectDuration: null,
226
226
  priorityLevel: null,
227
227
  timestamp: Math.max(0, commit.t - t0),
228
- perflabCauses: commit.causes,
229
- perflabCarrier: commit.carrier,
230
- perflabStore: commit.store,
231
- perflabTask: commit.task,
232
- ...(commit.facts ? { perflabFacts: commit.facts } : {}),
228
+ keepsnappyCauses: commit.causes,
229
+ keepsnappyCarrier: commit.carrier,
230
+ keepsnappyStore: commit.store,
231
+ keepsnappyTask: commit.task,
232
+ ...(commit.facts ? { keepsnappyFacts: commit.facts } : {}),
233
233
  updaters: (commit.updaters ?? []).map(displayName => ({
234
234
  displayName,
235
235
  hocDisplayNames: null,
@@ -241,7 +241,7 @@ export const buildReactExport = (t0: number, commits: ExportCommit[]): ReactExpo
241
241
  });
242
242
  return {
243
243
  version: 5,
244
- source: 'perflab',
244
+ source: 'keepsnappy',
245
245
  captureStart: { clock: 'perf-now-ms', t0 },
246
246
  dataForRoots: [
247
247
  {