@swmansion/argent 0.32.1-next.12 → 0.32.1-next.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/dist/cli-cmds.mjs +3 -1
- package/dist/client.js +2 -0
- package/dist/installer.mjs +3 -1
- package/dist/mcp-server.mjs +2 -0
- package/dist/tool-server.cjs +624 -281
- package/package.json +1 -1
- package/skills/argent-react-native-profiler/SKILL.md +2 -2
package/dist/tool-server.cjs
CHANGED
|
@@ -1709,6 +1709,8 @@ var init_failure_codes = __esm({
|
|
|
1709
1709
|
REACT_PROFILER_START_FAILED: "REACT_PROFILER_START_FAILED",
|
|
1710
1710
|
REACT_PROFILER_START_VERIFY_FAILED: "REACT_PROFILER_START_VERIFY_FAILED",
|
|
1711
1711
|
REACT_PROFILER_SESSION_TORN_DOWN: "REACT_PROFILER_SESSION_TORN_DOWN",
|
|
1712
|
+
REACT_PROFILER_SESSION_TAKEN_OVER: "REACT_PROFILER_SESSION_TAKEN_OVER",
|
|
1713
|
+
REACT_PROFILER_STOP_IN_PROGRESS: "REACT_PROFILER_STOP_IN_PROGRESS",
|
|
1712
1714
|
REACT_PROFILER_HOOK_ERROR: "REACT_PROFILER_HOOK_ERROR",
|
|
1713
1715
|
REACT_PROFILER_ANALYZE_NO_DATA: "REACT_PROFILER_ANALYZE_NO_DATA",
|
|
1714
1716
|
NATIVE_PROFILER_FACTORY_OPTIONS_MISSING: "NATIVE_PROFILER_FACTORY_OPTIONS_MISSING",
|
|
@@ -101186,7 +101188,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
|
|
|
101186
101188
|
var SESSION_ID = (0, import_node_crypto3.randomUUID)();
|
|
101187
101189
|
function readCliVersion() {
|
|
101188
101190
|
if (true) {
|
|
101189
|
-
return "0.32.1-next.
|
|
101191
|
+
return "0.32.1-next.14";
|
|
101190
101192
|
}
|
|
101191
101193
|
return "0.0.0";
|
|
101192
101194
|
}
|
|
@@ -146304,23 +146306,31 @@ var BOOTSTRAP_DEVTOOLS_BACKEND_SCRIPT = `
|
|
|
146304
146306
|
});
|
|
146305
146307
|
})()
|
|
146306
146308
|
`;
|
|
146307
|
-
|
|
146309
|
+
function buildHeartbeatScript(sessionId) {
|
|
146310
|
+
return `
|
|
146308
146311
|
(function(){
|
|
146312
|
+
var o = globalThis.__ARGENT_PROFILER_OWNER__;
|
|
146313
|
+
if (!o || o.sessionId !== ${JSON.stringify(sessionId)}) return;
|
|
146309
146314
|
if (typeof globalThis.__argent_profilerHeartbeat === 'function') {
|
|
146310
146315
|
try { globalThis.__argent_profilerHeartbeat(); } catch (_e) {}
|
|
146311
146316
|
}
|
|
146312
146317
|
})()
|
|
146313
146318
|
`;
|
|
146319
|
+
}
|
|
146314
146320
|
var READ_STATE_SCRIPT = `
|
|
146315
146321
|
(function __argent_readState() {
|
|
146316
146322
|
var h = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
146317
146323
|
if (!h) return JSON.stringify({ hookExists: false });
|
|
146318
146324
|
var rendererInterfaceFound = false;
|
|
146319
146325
|
var isRunning = false;
|
|
146326
|
+
var rendererStartedAtEpochMs = [];
|
|
146320
146327
|
if (h.rendererInterfaces && typeof h.rendererInterfaces.forEach === 'function') {
|
|
146321
146328
|
h.rendererInterfaces.forEach(function (ri) {
|
|
146322
146329
|
rendererInterfaceFound = true;
|
|
146323
146330
|
if (ri && ri.__argent_isProfiling__ === true) isRunning = true;
|
|
146331
|
+
if (ri && typeof ri.__argent_startedAtEpochMs__ === 'number') {
|
|
146332
|
+
rendererStartedAtEpochMs.push(ri.__argent_startedAtEpochMs__);
|
|
146333
|
+
}
|
|
146324
146334
|
});
|
|
146325
146335
|
}
|
|
146326
146336
|
if (!rendererInterfaceFound) {
|
|
@@ -146333,6 +146343,7 @@ var READ_STATE_SCRIPT = `
|
|
|
146333
146343
|
isRunning: isRunning,
|
|
146334
146344
|
owner: globalThis.__ARGENT_PROFILER_OWNER__ || null,
|
|
146335
146345
|
nowEpochMs: Date.now(),
|
|
146346
|
+
rendererStartedAtEpochMs: rendererStartedAtEpochMs,
|
|
146336
146347
|
});
|
|
146337
146348
|
})()
|
|
146338
146349
|
`;
|
|
@@ -146618,6 +146629,53 @@ var RESOLVE_FIBER_META_SCRIPT = `
|
|
|
146618
146629
|
})()
|
|
146619
146630
|
`;
|
|
146620
146631
|
|
|
146632
|
+
// ../tool-server/src/utils/react-profiler/session-ownership.ts
|
|
146633
|
+
var DEFAULT_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
146634
|
+
var STOP_IN_PROGRESS_NOTE = "A react-profiler-stop is collecting this run. Wait for its result before you call react-profiler-start or react-profiler-stop.";
|
|
146635
|
+
var STALE_OWN_RUN_NOTE = "This recording was started through this tool-server, but its heartbeat is stale, so it may be left over from another agent or an earlier conversation. If you started it, call react-profiler-stop to collect it. If not, ask the user before you discard it with react-profiler-start { force: true }.";
|
|
146636
|
+
function classifyStaleness({
|
|
146637
|
+
owner,
|
|
146638
|
+
nowEpochMs,
|
|
146639
|
+
staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS
|
|
146640
|
+
}) {
|
|
146641
|
+
if (!owner) {
|
|
146642
|
+
return {
|
|
146643
|
+
stale: false,
|
|
146644
|
+
ageSeconds: null,
|
|
146645
|
+
heartbeatAgeSeconds: null,
|
|
146646
|
+
canReclaimWithoutForce: true
|
|
146647
|
+
};
|
|
146648
|
+
}
|
|
146649
|
+
const ageMs = nowEpochMs - owner.startedAtEpochMs;
|
|
146650
|
+
const heartbeatAgeMs = nowEpochMs - owner.lastHeartbeatEpochMs;
|
|
146651
|
+
const stale = heartbeatAgeMs > staleThresholdMs;
|
|
146652
|
+
return {
|
|
146653
|
+
stale,
|
|
146654
|
+
ageSeconds: Math.max(0, ageMs) / 1e3,
|
|
146655
|
+
heartbeatAgeSeconds: Math.max(0, Math.round(heartbeatAgeMs / 1e3)),
|
|
146656
|
+
canReclaimWithoutForce: stale
|
|
146657
|
+
};
|
|
146658
|
+
}
|
|
146659
|
+
function takeoverOf(state3, run2) {
|
|
146660
|
+
if (run2.sessionId === null) return void 0;
|
|
146661
|
+
if (state3.isRunning) {
|
|
146662
|
+
if (state3.owner?.sessionId === run2.sessionId) return void 0;
|
|
146663
|
+
return { by: state3.owner?.sessionId ?? null, stillRecording: true };
|
|
146664
|
+
}
|
|
146665
|
+
const starts = state3.rendererStartedAtEpochMs ?? [];
|
|
146666
|
+
if (starts.length === 0 || run2.profileStartWallMs === null) return void 0;
|
|
146667
|
+
if (starts.includes(run2.profileStartWallMs)) return void 0;
|
|
146668
|
+
return { by: null, stillRecording: false };
|
|
146669
|
+
}
|
|
146670
|
+
async function readTakeover(cdp, run2) {
|
|
146671
|
+
try {
|
|
146672
|
+
const raw = await cdp.evaluate(READ_STATE_SCRIPT);
|
|
146673
|
+
return raw ? takeoverOf(JSON.parse(raw), run2) : void 0;
|
|
146674
|
+
} catch {
|
|
146675
|
+
return void 0;
|
|
146676
|
+
}
|
|
146677
|
+
}
|
|
146678
|
+
|
|
146621
146679
|
// ../tool-server/src/blueprints/react-profiler-session.ts
|
|
146622
146680
|
var REACT_PROFILER_SESSION_NAMESPACE = "ReactProfilerSession";
|
|
146623
146681
|
var reactProfilerSessionBlueprint = {
|
|
@@ -146674,6 +146732,7 @@ var reactProfilerSessionBlueprint = {
|
|
|
146674
146732
|
detectedArchitecture: null,
|
|
146675
146733
|
sessionPaths: null,
|
|
146676
146734
|
profilingActive: false,
|
|
146735
|
+
stopping: false,
|
|
146677
146736
|
scriptSources: /* @__PURE__ */ new Map(),
|
|
146678
146737
|
anyCompilerOptimized: null,
|
|
146679
146738
|
hotCommitIndices: null,
|
|
@@ -146744,13 +146803,16 @@ var reactProfilerSessionBlueprint = {
|
|
|
146744
146803
|
);
|
|
146745
146804
|
};
|
|
146746
146805
|
cdp.events.on("disconnected", onDisconnected);
|
|
146806
|
+
let runtimeReloaded = false;
|
|
146747
146807
|
const onCdpEvent = (method) => {
|
|
146748
146808
|
if (method !== "Runtime.executionContextDestroyed" && method !== "Runtime.executionContextsCleared") {
|
|
146749
146809
|
return;
|
|
146750
146810
|
}
|
|
146751
|
-
|
|
146811
|
+
runtimeReloaded = true;
|
|
146812
|
+
if (state3.profilingActive || state3.stopping) {
|
|
146752
146813
|
state3.profilingActive = false;
|
|
146753
146814
|
clearCachedProfilerPaths(state3.port, state3.deviceId);
|
|
146815
|
+
recordLostProfilerRun(state3, "js_reload");
|
|
146754
146816
|
stopSamplerInNextRuntime(cdp);
|
|
146755
146817
|
}
|
|
146756
146818
|
events.emit("terminated");
|
|
@@ -146763,13 +146825,23 @@ var reactProfilerSessionBlueprint = {
|
|
|
146763
146825
|
cdp.events.off("scriptParsed", onScriptParsed);
|
|
146764
146826
|
cdp.events.off("disconnected", onDisconnected);
|
|
146765
146827
|
cdp.events.off("event", onCdpEvent);
|
|
146766
|
-
if (state3.profilingActive) {
|
|
146828
|
+
if (!runtimeReloaded && (state3.profilingActive || state3.sessionId !== null)) {
|
|
146767
146829
|
state3.profilingActive = false;
|
|
146768
|
-
|
|
146769
|
-
|
|
146770
|
-
|
|
146771
|
-
|
|
146772
|
-
|
|
146830
|
+
const takeover = cdp.isConnected() ? await readTakeover(cdp, state3) : void 0;
|
|
146831
|
+
if (takeover) {
|
|
146832
|
+
recordLostProfilerRun(state3, "taken_over", takeover.by);
|
|
146833
|
+
if (takeover.by === null) await cdp.send("Profiler.stop").catch(ignore);
|
|
146834
|
+
} else {
|
|
146835
|
+
recordLostProfilerRun(
|
|
146836
|
+
state3,
|
|
146837
|
+
cdp.isConnected() ? "session_torn_down" : "cdp_disconnected"
|
|
146838
|
+
);
|
|
146839
|
+
await cdp.send("Runtime.evaluate", {
|
|
146840
|
+
expression: STOP_FOR_TAKEOVER_SCRIPT,
|
|
146841
|
+
returnByValue: true
|
|
146842
|
+
}).catch(warnOnError("STOP_FOR_TAKEOVER_SCRIPT"));
|
|
146843
|
+
await cdp.send("Profiler.stop").catch(ignore);
|
|
146844
|
+
}
|
|
146773
146845
|
}
|
|
146774
146846
|
await cdp.send("Profiler.disable").catch(ignore);
|
|
146775
146847
|
},
|
|
@@ -146815,6 +146887,36 @@ function clearCachedProfilerPaths(port, deviceId) {
|
|
|
146815
146887
|
profilerPathsCache.delete(cacheKey(port, id));
|
|
146816
146888
|
}
|
|
146817
146889
|
}
|
|
146890
|
+
var lostRuns = /* @__PURE__ */ new Map();
|
|
146891
|
+
function findLostRun(port, deviceId) {
|
|
146892
|
+
for (const id of deviceIdsOf(deviceId)) {
|
|
146893
|
+
const entry = lostRuns.get(cacheKey(port, id));
|
|
146894
|
+
if (entry) return entry;
|
|
146895
|
+
}
|
|
146896
|
+
return void 0;
|
|
146897
|
+
}
|
|
146898
|
+
function recordLostProfilerRun(run2, cause, takenOverBy) {
|
|
146899
|
+
const prior = findLostRun(run2.port, run2.deviceId)?.run;
|
|
146900
|
+
if (prior && prior.sessionId !== null && prior.sessionId === run2.sessionId) return;
|
|
146901
|
+
clearLostProfilerRun(run2.port, run2.deviceId);
|
|
146902
|
+
const entry = {
|
|
146903
|
+
run: {
|
|
146904
|
+
cause,
|
|
146905
|
+
sessionId: run2.sessionId,
|
|
146906
|
+
startedAtEpochMs: run2.profileStartWallMs,
|
|
146907
|
+
lostAtEpochMs: Date.now(),
|
|
146908
|
+
...takenOverBy !== void 0 ? { takenOverBy } : {}
|
|
146909
|
+
},
|
|
146910
|
+
keys: deviceIdsOf(run2.deviceId).map((id) => cacheKey(run2.port, id))
|
|
146911
|
+
};
|
|
146912
|
+
for (const key2 of entry.keys) lostRuns.set(key2, entry);
|
|
146913
|
+
}
|
|
146914
|
+
function getLostProfilerRun(port, deviceId) {
|
|
146915
|
+
return findLostRun(port, deviceId)?.run;
|
|
146916
|
+
}
|
|
146917
|
+
function clearLostProfilerRun(port, deviceId) {
|
|
146918
|
+
for (const key2 of findLostRun(port, deviceId)?.keys ?? []) lostRuns.delete(key2);
|
|
146919
|
+
}
|
|
146818
146920
|
|
|
146819
146921
|
// ../tool-server/src/tools/devices/boot-device.ts
|
|
146820
146922
|
var import_node_child_process32 = require("node:child_process");
|
|
@@ -157339,10 +157441,11 @@ var VEGA_CONTENT_ROLES = /* @__PURE__ */ new Set([...CONTENT_ROLES, "button", "t
|
|
|
157339
157441
|
function clampFinite(n) {
|
|
157340
157442
|
return Number.isFinite(n) ? n : 0;
|
|
157341
157443
|
}
|
|
157444
|
+
function fmtCoord(n) {
|
|
157445
|
+
return clampFinite(n).toFixed(3);
|
|
157446
|
+
}
|
|
157342
157447
|
function fmtFrame(f) {
|
|
157343
|
-
return `(${
|
|
157344
|
-
f.width
|
|
157345
|
-
).toFixed(3)}, ${clampFinite(f.height).toFixed(3)})`;
|
|
157448
|
+
return `(${fmtCoord(f.x)},${fmtCoord(f.y)},${fmtCoord(f.width)},${fmtCoord(f.height)})`;
|
|
157346
157449
|
}
|
|
157347
157450
|
function escapeForLine(s) {
|
|
157348
157451
|
return s.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
@@ -158757,30 +158860,6 @@ var crypto6 = __toESM(require("node:crypto"));
|
|
|
158757
158860
|
init_zod();
|
|
158758
158861
|
init_src2();
|
|
158759
158862
|
|
|
158760
|
-
// ../tool-server/src/utils/react-profiler/session-ownership.ts
|
|
158761
|
-
var DEFAULT_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
158762
|
-
function classifyStaleness({
|
|
158763
|
-
owner,
|
|
158764
|
-
nowEpochMs,
|
|
158765
|
-
staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS
|
|
158766
|
-
}) {
|
|
158767
|
-
if (!owner) {
|
|
158768
|
-
return {
|
|
158769
|
-
stale: false,
|
|
158770
|
-
ageSeconds: null,
|
|
158771
|
-
canReclaimWithoutForce: true
|
|
158772
|
-
};
|
|
158773
|
-
}
|
|
158774
|
-
const ageMs = nowEpochMs - owner.startedAtEpochMs;
|
|
158775
|
-
const heartbeatAgeMs = nowEpochMs - owner.lastHeartbeatEpochMs;
|
|
158776
|
-
const stale = heartbeatAgeMs > staleThresholdMs;
|
|
158777
|
-
return {
|
|
158778
|
-
stale,
|
|
158779
|
-
ageSeconds: Math.max(0, ageMs) / 1e3,
|
|
158780
|
-
canReclaimWithoutForce: stale
|
|
158781
|
-
};
|
|
158782
|
-
}
|
|
158783
|
-
|
|
158784
158863
|
// ../tool-server/src/utils/debugger/runtime-device.ts
|
|
158785
158864
|
var import_node_crypto13 = require("node:crypto");
|
|
158786
158865
|
var import_node_child_process43 = require("node:child_process");
|
|
@@ -158919,6 +158998,46 @@ function logicalDeviceWarning(deviceId, runtimeApp) {
|
|
|
158919
158998
|
var NO_DEVTOOLS_HOOK_ERROR = "React DevTools hook (__REACT_DEVTOOLS_GLOBAL_HOOK__) is not present in this app's JavaScript runtime. React profiling requires a development build with React DevTools enabled. Likely causes: (1) the app is a release/production build \u2014 DevTools is stripped to reduce bundle size; (2) you connected to the wrong JS runtime; (3) this isn't a React (Native) app. Fix: rebuild in debug/dev mode (e.g. `npx react-native run-ios` without --configuration Release; for Expo, run a dev client). Once the app is running with DevTools attached, call react-profiler-start again.";
|
|
158920
158999
|
var NO_RENDERERS_ATTACHED_ERROR = "React DevTools hook is present but no React renderer has registered yet. The hook is loaded but no fiber renderer has attached \u2014 typically because the app has not committed its first render, or the DevTools backend has not been bootstrapped on a bridgeless React Native dev build. Fix: ensure the app has rendered (interact with it once, then retry); if it stays empty, call react-profiler-start first \u2014 it will attempt to attach the DevTools backend automatically.";
|
|
158921
159000
|
var UNKNOWN_DEVICE_NOTE = "Two or more devices share this Metro, and Argent could not tell which of them runs this app. So taps and swipes on that device still return a screenshot and an element tree, and the capture loads the app while the profiler records. To avoid this, run the app on its own Metro port.";
|
|
159001
|
+
function stopInProgress() {
|
|
159002
|
+
return { already_running: true, mine: true, note: STOP_IN_PROGRESS_NOTE };
|
|
159003
|
+
}
|
|
159004
|
+
function describeTakeover(input2) {
|
|
159005
|
+
const { owner, mine, ownRunWasActive, stale, heartbeatAgeSeconds } = input2;
|
|
159006
|
+
if (!owner) {
|
|
159007
|
+
return {
|
|
159008
|
+
session_id: null,
|
|
159009
|
+
reason: "unattributed",
|
|
159010
|
+
note: "Stopped a running profiling session that had no owner record (likely a DevTools client outside argent) and started this one."
|
|
159011
|
+
};
|
|
159012
|
+
}
|
|
159013
|
+
const id = owner.sessionId;
|
|
159014
|
+
if (mine) {
|
|
159015
|
+
return ownRunWasActive ? {
|
|
159016
|
+
session_id: id,
|
|
159017
|
+
reason: "forced",
|
|
159018
|
+
note: `Discarded this tool-server's own running recording (session ${id}) because force: true was passed.`
|
|
159019
|
+
} : {
|
|
159020
|
+
session_id: id,
|
|
159021
|
+
reason: "own_unsaved",
|
|
159022
|
+
note: `Stopped this tool-server's earlier run (session ${id}), which the app kept recording after it was lost; it was not saved.`
|
|
159023
|
+
};
|
|
159024
|
+
}
|
|
159025
|
+
return stale ? {
|
|
159026
|
+
session_id: id,
|
|
159027
|
+
reason: "stale",
|
|
159028
|
+
heartbeat_age_seconds: heartbeatAgeSeconds,
|
|
159029
|
+
note: `Took over session ${id} without force: its last heartbeat was ${heartbeatAgeSeconds} s ago (stale after ${DEFAULT_STALE_THRESHOLD_MS / 1e3} s), so its owner was presumed gone. Its recorded data was discarded.`
|
|
159030
|
+
} : {
|
|
159031
|
+
session_id: id,
|
|
159032
|
+
reason: "forced",
|
|
159033
|
+
heartbeat_age_seconds: heartbeatAgeSeconds,
|
|
159034
|
+
note: `Took over session ${id} because force: true was passed. Its recorded data was discarded.`
|
|
159035
|
+
};
|
|
159036
|
+
}
|
|
159037
|
+
function failedTakeoverText({ session_id, reason: reason2 }) {
|
|
159038
|
+
const run2 = session_id ? `session ${session_id}` : "a recording with no owner record";
|
|
159039
|
+
return `Before it failed, this start stopped ${run2} (reason: ${reason2}), and nothing is recording now.`;
|
|
159040
|
+
}
|
|
158922
159041
|
function safeGetState(registry2, urn) {
|
|
158923
159042
|
try {
|
|
158924
159043
|
return registry2.getServiceState(urn);
|
|
@@ -158943,7 +159062,7 @@ function createReactProfilerStartTool(registry2) {
|
|
|
158943
159062
|
Use when you need to profile a user interaction.
|
|
158944
159063
|
On an iOS simulator or Android device, also call native-profiler-start in the same message with the same device_id, unless the user has said no to native profiling. profiler-combined-report then correlates the two.
|
|
158945
159064
|
After starting, ask the user to perform the interaction to profile, then call react-profiler-stop.
|
|
158946
|
-
Returns { started_at, startedAtEpochMs, hermes_version, detected_architecture }. On Android, a warning names the apps with native network capture on (started by native-network-logs): their timings in the profile can differ. If
|
|
159065
|
+
Returns { started_at, startedAtEpochMs, hermes_version, detected_architecture }. On Android, a warning names the apps with native network capture on (started by native-network-logs): their timings in the profile can differ. If a session is already running, returns { already_running: true, mine, owner, \u2026 } instead, without clobbering its data, unless it is another tool-server's stale run, has no owner record, or is a lost run of this tool-server: start replaces those. mine: true means this tool-server started the run: if it is yours, call react-profiler-stop. With stale: true it may be another agent's: follow the note. Otherwise, BEFORE OVERTAKING with { force: true }, ask the user for approval first, as how_to_reclaim explains. A start that replaces a run reports it in took_over.
|
|
158947
159066
|
Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot be established.`,
|
|
158948
159067
|
zodSchema: zodSchema45,
|
|
158949
159068
|
interaction: {
|
|
@@ -158984,6 +159103,7 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
158984
159103
|
await disposeAndWait();
|
|
158985
159104
|
}
|
|
158986
159105
|
let api = await registry2.resolveService(psUrn);
|
|
159106
|
+
if (api.stopping) return stopInProgress();
|
|
158987
159107
|
if (!api.cdp.isConnected()) {
|
|
158988
159108
|
await disposeAndWait();
|
|
158989
159109
|
api = await registry2.resolveService(psUrn);
|
|
@@ -159070,6 +159190,13 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159070
159190
|
);
|
|
159071
159191
|
}
|
|
159072
159192
|
}
|
|
159193
|
+
let tookOver;
|
|
159194
|
+
const withTakeover = (message) => tookOver ? `${message} ${failedTakeoverText(tookOver)}` : message;
|
|
159195
|
+
const rethrowWithTakeover = (err) => {
|
|
159196
|
+
if (err instanceof Error) err.message = withTakeover(err.message);
|
|
159197
|
+
throw err;
|
|
159198
|
+
};
|
|
159199
|
+
if (api.stopping) return stopInProgress();
|
|
159073
159200
|
if (state3.isRunning) {
|
|
159074
159201
|
const owner = state3.owner;
|
|
159075
159202
|
const staleness = classifyStaleness({
|
|
@@ -159077,10 +159204,24 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159077
159204
|
nowEpochMs: state3.nowEpochMs,
|
|
159078
159205
|
staleThresholdMs: DEFAULT_STALE_THRESHOLD_MS
|
|
159079
159206
|
});
|
|
159080
|
-
const
|
|
159207
|
+
const lostRun = getLostProfilerRun(api.port, api.deviceId);
|
|
159208
|
+
const mine = owner !== null && (api.sessionId !== null && owner.sessionId === api.sessionId || lostRun !== void 0 && lostRun.sessionId !== null && owner.sessionId === lostRun.sessionId);
|
|
159209
|
+
if (mine && api.profilingActive && params.force !== true) {
|
|
159210
|
+
return {
|
|
159211
|
+
already_running: true,
|
|
159212
|
+
mine: true,
|
|
159213
|
+
owner,
|
|
159214
|
+
age_seconds: staleness.ageSeconds,
|
|
159215
|
+
stale: staleness.stale,
|
|
159216
|
+
heartbeat_age_seconds: staleness.heartbeatAgeSeconds,
|
|
159217
|
+
note: staleness.stale ? STALE_OWN_RUN_NOTE : "A recording started through this tool-server is still running: yours, unless another agent shares this tool-server. Call react-profiler-stop to collect it. To discard it and start over, call react-profiler-start again with { force: true }."
|
|
159218
|
+
};
|
|
159219
|
+
}
|
|
159220
|
+
const canTakeOverSilently = mine || staleness.canReclaimWithoutForce || params.force === true;
|
|
159081
159221
|
if (!canTakeOverSilently) {
|
|
159082
159222
|
return {
|
|
159083
159223
|
already_running: true,
|
|
159224
|
+
mine: false,
|
|
159084
159225
|
owner,
|
|
159085
159226
|
age_seconds: staleness.ageSeconds,
|
|
159086
159227
|
stale: staleness.stale,
|
|
@@ -159088,11 +159229,16 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159088
159229
|
};
|
|
159089
159230
|
}
|
|
159090
159231
|
await cdp.evaluate(STOP_FOR_TAKEOVER_SCRIPT).catch(ignore);
|
|
159232
|
+
tookOver = describeTakeover({
|
|
159233
|
+
owner,
|
|
159234
|
+
mine,
|
|
159235
|
+
ownRunWasActive: mine && api.profilingActive,
|
|
159236
|
+
stale: staleness.stale,
|
|
159237
|
+
heartbeatAgeSeconds: staleness.heartbeatAgeSeconds
|
|
159238
|
+
});
|
|
159091
159239
|
}
|
|
159092
|
-
|
|
159093
|
-
|
|
159094
|
-
api.profilingActive = false;
|
|
159095
|
-
}
|
|
159240
|
+
await cdp.send("Profiler.stop").catch(ignore);
|
|
159241
|
+
api.profilingActive = false;
|
|
159096
159242
|
const runtimeDevice = isLogicalKeyedDevice(api.deviceId) ? await findRuntimeDevice(cdp) : void 0;
|
|
159097
159243
|
const sessionId = crypto6.randomUUID();
|
|
159098
159244
|
const ownerPayload = {
|
|
@@ -159103,21 +159249,30 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159103
159249
|
lastHeartbeatEpochMs: 0
|
|
159104
159250
|
};
|
|
159105
159251
|
await cdp.send("Profiler.enable").catch(ignore);
|
|
159106
|
-
await cdp.send("Profiler.start", { interval: params.sample_interval_us });
|
|
159107
|
-
const startJson = await cdp.evaluate(buildStartScript(JSON.stringify(ownerPayload)))
|
|
159252
|
+
await cdp.send("Profiler.start", { interval: params.sample_interval_us }).catch(rethrowWithTakeover);
|
|
159253
|
+
const startJson = await cdp.evaluate(buildStartScript(JSON.stringify(ownerPayload))).catch(async (err) => {
|
|
159254
|
+
await cdp.send("Profiler.stop").catch(ignore);
|
|
159255
|
+
return rethrowWithTakeover(err);
|
|
159256
|
+
});
|
|
159108
159257
|
if (!startJson) {
|
|
159109
|
-
|
|
159110
|
-
|
|
159111
|
-
|
|
159112
|
-
|
|
159113
|
-
|
|
159114
|
-
|
|
159258
|
+
await cdp.send("Profiler.stop").catch(ignore);
|
|
159259
|
+
throw new FailureError(
|
|
159260
|
+
withTakeover("Failed to start React profiler (no value returned from runtime)."),
|
|
159261
|
+
{
|
|
159262
|
+
error_code: FAILURE_CODES.REACT_PROFILER_NO_RUNTIME_DATA,
|
|
159263
|
+
failure_stage: "react_profiler_start_runtime_start",
|
|
159264
|
+
failure_area: "tool_server",
|
|
159265
|
+
error_kind: "subprocess"
|
|
159266
|
+
}
|
|
159267
|
+
);
|
|
159115
159268
|
}
|
|
159116
159269
|
const startResult = JSON.parse(startJson);
|
|
159117
159270
|
if (!startResult.ok) {
|
|
159118
159271
|
await cdp.send("Profiler.stop").catch(ignore);
|
|
159119
159272
|
throw new FailureError(
|
|
159120
|
-
|
|
159273
|
+
withTakeover(
|
|
159274
|
+
`React profiler failed to start (${startResult.reason ?? "unknown"}${startResult.message ? `: ${startResult.message}` : ""})`
|
|
159275
|
+
),
|
|
159121
159276
|
{
|
|
159122
159277
|
error_code: FAILURE_CODES.REACT_PROFILER_START_FAILED,
|
|
159123
159278
|
failure_stage: "react_profiler_start_runtime_start",
|
|
@@ -159129,7 +159284,9 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159129
159284
|
if (startResult.isProfilingFlagSet !== true || startResult.ownerInstalled !== true) {
|
|
159130
159285
|
await cdp.send("Profiler.stop").catch(ignore);
|
|
159131
159286
|
throw new FailureError(
|
|
159132
|
-
|
|
159287
|
+
withTakeover(
|
|
159288
|
+
`React profiler failed to start (post-start verification failed: isProfilingFlagSet=${startResult.isProfilingFlagSet === true}, ownerInstalled=${startResult.ownerInstalled === true})`
|
|
159289
|
+
),
|
|
159133
159290
|
{
|
|
159134
159291
|
error_code: FAILURE_CODES.REACT_PROFILER_START_VERIFY_FAILED,
|
|
159135
159292
|
failure_stage: "react_profiler_start_verify",
|
|
@@ -159142,7 +159299,9 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159142
159299
|
await cdp.evaluate(STOP_FOR_TAKEOVER_SCRIPT).catch(ignore);
|
|
159143
159300
|
await cdp.send("Profiler.stop").catch(ignore);
|
|
159144
159301
|
throw new FailureError(
|
|
159145
|
-
|
|
159302
|
+
withTakeover(
|
|
159303
|
+
`The React profiler session for ${api.deviceId} was torn down while the profiler was starting, so nothing is recorded. One tool-server serves every agent using this argent install, so another agent may have run stop-all-simulator-servers. Call react-profiler-start again.`
|
|
159304
|
+
),
|
|
159146
159305
|
{
|
|
159147
159306
|
error_code: FAILURE_CODES.REACT_PROFILER_SESSION_TORN_DOWN,
|
|
159148
159307
|
failure_stage: "react_profiler_start_session_torn_down",
|
|
@@ -159153,6 +159312,7 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159153
159312
|
}
|
|
159154
159313
|
const startedAtEpochMs = startResult.startedAtEpochMs ?? Date.now();
|
|
159155
159314
|
clearCachedProfilerPaths(api.port, api.deviceId);
|
|
159315
|
+
clearLostProfilerRun(api.port, api.deviceId);
|
|
159156
159316
|
api.sessionPaths = null;
|
|
159157
159317
|
api.profilingActive = true;
|
|
159158
159318
|
api.anyCompilerOptimized = null;
|
|
@@ -159169,6 +159329,7 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159169
159329
|
startedAtEpochMs,
|
|
159170
159330
|
hermes_version: api.hermesVersion,
|
|
159171
159331
|
detected_architecture: api.detectedArchitecture,
|
|
159332
|
+
...tookOver ? { took_over: tookOver } : {},
|
|
159172
159333
|
...warning ? { warning } : {},
|
|
159173
159334
|
...isLogicalKeyedDevice(api.deviceId) && !runtimeDevice ? { [RESULT_NOTE_KEY]: UNKNOWN_DEVICE_NOTE } : {}
|
|
159174
159335
|
};
|
|
@@ -159239,6 +159400,55 @@ async function readCommitTree(path59) {
|
|
|
159239
159400
|
return JSON.parse(json2);
|
|
159240
159401
|
}
|
|
159241
159402
|
|
|
159403
|
+
// ../tool-server/src/utils/react-profiler/last-run.ts
|
|
159404
|
+
function savedSessionInfo(paths) {
|
|
159405
|
+
return {
|
|
159406
|
+
session_id: paths.sessionId,
|
|
159407
|
+
commits_path: paths.commitsPath,
|
|
159408
|
+
cpu_profile_path: paths.cpuProfilePath,
|
|
159409
|
+
...paths.loadedFromDisk ? { loaded_from_disk: true } : {}
|
|
159410
|
+
};
|
|
159411
|
+
}
|
|
159412
|
+
function lostRunInfo(run2) {
|
|
159413
|
+
return {
|
|
159414
|
+
session_id: run2.sessionId,
|
|
159415
|
+
cause: run2.cause,
|
|
159416
|
+
lost_at: new Date(run2.lostAtEpochMs).toISOString(),
|
|
159417
|
+
...run2.takenOverBy !== void 0 ? { taken_over_by: run2.takenOverBy } : {}
|
|
159418
|
+
};
|
|
159419
|
+
}
|
|
159420
|
+
function lostRunCauseText(cause, takenOverBy) {
|
|
159421
|
+
switch (cause) {
|
|
159422
|
+
case "js_reload":
|
|
159423
|
+
return "a JS reload (Metro's `r`, a red-box Reload, debugger-reload-metro) ended it";
|
|
159424
|
+
case "cdp_disconnected":
|
|
159425
|
+
return "the debugger connection dropped while it was recording: the app crashed or was killed, Metro restarted, or another debugger took the connection";
|
|
159426
|
+
case "session_torn_down":
|
|
159427
|
+
return "its session was torn down mid-run, typically by stop-all-simulator-servers: the run rides on the device's JS-runtime debugger, which that teardown reaps, and one tool-server serves every agent using this argent install";
|
|
159428
|
+
case "taken_over":
|
|
159429
|
+
return takenOverBy ? `session ${takenOverBy} took it over, which discarded its data` : "another session took it over, which discarded its data";
|
|
159430
|
+
case "stop_failed":
|
|
159431
|
+
return "a react-profiler-stop, or a react-profiler-start that replaced it, failed partway (see the error it returned)";
|
|
159432
|
+
case "ended_in_app":
|
|
159433
|
+
return "it stopped recording in the app without a react-profiler-stop, most likely from outside argent";
|
|
159434
|
+
}
|
|
159435
|
+
}
|
|
159436
|
+
function savedSessionText(paths) {
|
|
159437
|
+
if (paths.loadedFromDisk) {
|
|
159438
|
+
return `Session ${paths.sessionId} is loaded from disk: react-profiler-analyze and the profiler query tools read it.`;
|
|
159439
|
+
}
|
|
159440
|
+
return `Your last run was stopped and saved as session ${paths.sessionId}: react-profiler-analyze and the profiler query tools read it. After a tool-server restart, load it again with profiler-load { mode: "load_react", session_id: "${paths.sessionId}", device_id: <this run's device_id> }. Pass port too if this run passed one.`;
|
|
159441
|
+
}
|
|
159442
|
+
function lostRunText(lost) {
|
|
159443
|
+
const which = lost.sessionId ? `Your last run (session ${lost.sessionId})` : "Your last run";
|
|
159444
|
+
return `${which} was not saved: ${lostRunCauseText(lost.cause, lost.takenOverBy)}.`;
|
|
159445
|
+
}
|
|
159446
|
+
function lastRunText(saved, lost) {
|
|
159447
|
+
if (lost) return lostRunText(lost);
|
|
159448
|
+
if (saved) return savedSessionText(saved);
|
|
159449
|
+
return null;
|
|
159450
|
+
}
|
|
159451
|
+
|
|
159242
159452
|
// ../tool-server/src/tools/profiler/react/react-profiler-stop.ts
|
|
159243
159453
|
function normalizeChangeDescription(raw) {
|
|
159244
159454
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -159308,6 +159518,15 @@ function flattenProfilingData(merged, displayNameById, fiberMeta) {
|
|
|
159308
159518
|
}
|
|
159309
159519
|
return { commits, totalCommits: flatCommitIndex, unattributedByCommit };
|
|
159310
159520
|
}
|
|
159521
|
+
var COLLECT_TIMEOUT_MS = 6e4;
|
|
159522
|
+
function stopInProgressError() {
|
|
159523
|
+
return new FailureError(STOP_IN_PROGRESS_NOTE, {
|
|
159524
|
+
error_code: FAILURE_CODES.REACT_PROFILER_STOP_IN_PROGRESS,
|
|
159525
|
+
failure_stage: "react_profiler_stop_in_progress",
|
|
159526
|
+
failure_area: "tool_server",
|
|
159527
|
+
error_kind: "not_found"
|
|
159528
|
+
});
|
|
159529
|
+
}
|
|
159311
159530
|
var zodSchema46 = external_exports.object({
|
|
159312
159531
|
port: metroPortField,
|
|
159313
159532
|
device_id: external_exports.string().describe(
|
|
@@ -159322,8 +159541,11 @@ Stores the results for react-profiler-analyze or react-profiler-cpu-summary.
|
|
|
159322
159541
|
Call react-profiler-start first, then exercise the app, then call this.
|
|
159323
159542
|
Returns { duration_ms, sample_count, fiber_renders_captured, total_react_commits, hot_commit_indices }.
|
|
159324
159543
|
If some fibers' display names could not be resolved (typically modals, tooltips or animations that unmounted before stop), it also returns { unattributed_ms, unattributed_fiber_count, unattributed_commit_count }: the work missing from the per-component breakdown. Per-commit durations stay correct.
|
|
159325
|
-
Fails if no active profiling session exists
|
|
159544
|
+
Fails if no active profiling session exists, another stop is running, the CDP connection was lost during recording, or another session took the run over.`,
|
|
159326
159545
|
zodSchema: zodSchema46,
|
|
159546
|
+
// Collection can outlast the MCP client's 30 s abort. The client would then
|
|
159547
|
+
// resend the call, and the resend fails on the stop still collecting.
|
|
159548
|
+
longRunning: true,
|
|
159327
159549
|
interaction: {
|
|
159328
159550
|
startedMsg: () => "Stopping React profiler",
|
|
159329
159551
|
completedMsg: () => "Stopped React profiler",
|
|
@@ -159332,12 +159554,18 @@ Fails if no active profiling session exists or the CDP connection was lost durin
|
|
|
159332
159554
|
capability: RN_ONLY_TOOL_CAPABILITY,
|
|
159333
159555
|
services: () => ({}),
|
|
159334
159556
|
async execute(_services, params) {
|
|
159335
|
-
const
|
|
159557
|
+
const port = metroPort(params);
|
|
159558
|
+
const deviceId = canonicalDeviceId(params.device_id) ?? params.device_id;
|
|
159559
|
+
const psUrn = `${REACT_PROFILER_SESSION_NAMESPACE}:${port}:${deviceId}`;
|
|
159336
159560
|
const snapshot = registry2.getSnapshot();
|
|
159337
159561
|
const entry = snapshot.services.get(psUrn);
|
|
159562
|
+
const lastRun = lastRunText(
|
|
159563
|
+
getCachedProfilerPaths(port, deviceId),
|
|
159564
|
+
getLostProfilerRun(port, deviceId)
|
|
159565
|
+
);
|
|
159338
159566
|
if (!entry || entry.state !== "RUNNING" /* RUNNING */) {
|
|
159339
159567
|
throw new FailureError(
|
|
159340
|
-
"No active profiling session. The session may have been lost to a Metro reload, or torn down by a stop-all-simulator-servers \u2014 this session rides on the device's JS-runtime debugger, which that teardown reaps, and one tool-server serves every agent using this argent install. Call react-profiler-start to begin a new session.",
|
|
159568
|
+
lastRun ? `No active profiling session. ${lastRun} Call react-profiler-start to begin a new session.` : "No active profiling session. The session may have been lost to a Metro reload, or torn down by a stop-all-simulator-servers \u2014 this session rides on the device's JS-runtime debugger, which that teardown reaps, and one tool-server serves every agent using this argent install. Call react-profiler-start to begin a new session.",
|
|
159341
159569
|
{
|
|
159342
159570
|
error_code: FAILURE_CODES.REACT_PROFILER_NO_ACTIVE_SESSION,
|
|
159343
159571
|
failure_stage: "react_profiler_stop_session_lookup",
|
|
@@ -159348,6 +159576,7 @@ Fails if no active profiling session exists or the CDP connection was lost durin
|
|
|
159348
159576
|
}
|
|
159349
159577
|
const api = await registry2.resolveService(psUrn);
|
|
159350
159578
|
const cdp = api.cdp;
|
|
159579
|
+
if (api.stopping) throw stopInProgressError();
|
|
159351
159580
|
if (!api.cdp.isConnected()) {
|
|
159352
159581
|
api.profilingActive = false;
|
|
159353
159582
|
throw new FailureError(
|
|
@@ -159361,6 +159590,17 @@ Fails if no active profiling session exists or the CDP connection was lost durin
|
|
|
159361
159590
|
);
|
|
159362
159591
|
}
|
|
159363
159592
|
if (!api.profilingActive) {
|
|
159593
|
+
if (lastRun) {
|
|
159594
|
+
throw new FailureError(
|
|
159595
|
+
`No active profiling run to stop. ${lastRun} Call react-profiler-start to record a new one.`,
|
|
159596
|
+
{
|
|
159597
|
+
error_code: FAILURE_CODES.REACT_PROFILER_NO_ACTIVE_SESSION,
|
|
159598
|
+
failure_stage: "react_profiler_stop_inactive",
|
|
159599
|
+
failure_area: "tool_server",
|
|
159600
|
+
error_kind: "not_found"
|
|
159601
|
+
}
|
|
159602
|
+
);
|
|
159603
|
+
}
|
|
159364
159604
|
throw new FailureError(
|
|
159365
159605
|
"No active profiling run to stop. Either the run ended without its data (a JS reload ends it: Metro's `r`, a red-box Reload, debugger-reload-metro), or Hermes CPU sampling was never started \u2014 typically because react-profiler-start failed before reaching the sampler (often on release builds without React DevTools). After a reload, call react-profiler-start again. Otherwise check the error react-profiler-start returned, address the underlying cause (rebuild in dev mode, reconnect the debugger, etc.), then call react-profiler-start again.",
|
|
159366
159606
|
{
|
|
@@ -159373,180 +159613,210 @@ Fails if no active profiling session exists or the CDP connection was lost durin
|
|
|
159373
159613
|
}
|
|
159374
159614
|
);
|
|
159375
159615
|
}
|
|
159376
|
-
|
|
159377
|
-
|
|
159378
|
-
|
|
159616
|
+
const takeover = await readTakeover(cdp, api);
|
|
159617
|
+
if (takeover) {
|
|
159618
|
+
recordLostProfilerRun(api, "taken_over", takeover.by);
|
|
159619
|
+
if (takeover.by === null) await cdp.send("Profiler.stop").catch(() => {
|
|
159620
|
+
});
|
|
159621
|
+
api.profilingActive = false;
|
|
159622
|
+
api.sessionId = null;
|
|
159623
|
+
api.ownerToolServerPid = null;
|
|
159624
|
+
const howTakeoversHappen = "A takeover happens when another agent or tool-server calls react-profiler-start with force, or once a run's heartbeat is 5 minutes old.";
|
|
159625
|
+
const replacedBy = takeover.by ? `session ${takeover.by}` : "a recording with no owner record (a DevTools client outside argent)";
|
|
159379
159626
|
throw new FailureError(
|
|
159380
|
-
|
|
159627
|
+
takeover.stillRecording ? `This profiling run was taken over by ${replacedBy}, which is still recording. Its data was discarded at the takeover, so there is nothing to collect, and the other recording was left running. ${howTakeoversHappen} Ask the user before taking over with react-profiler-start { force: true }.` : `This profiling run was taken over by another session, which has since stopped. Its data was discarded at the takeover, so there is nothing to collect. ${howTakeoversHappen} Nothing is recording now: call react-profiler-start to record again.`,
|
|
159381
159628
|
{
|
|
159382
|
-
error_code: FAILURE_CODES.
|
|
159383
|
-
failure_stage: "
|
|
159629
|
+
error_code: FAILURE_CODES.REACT_PROFILER_SESSION_TAKEN_OVER,
|
|
159630
|
+
failure_stage: "react_profiler_stop_taken_over",
|
|
159384
159631
|
failure_area: "tool_server",
|
|
159385
|
-
error_kind: "
|
|
159632
|
+
error_kind: "not_found"
|
|
159386
159633
|
}
|
|
159387
159634
|
);
|
|
159388
159635
|
}
|
|
159389
|
-
|
|
159390
|
-
|
|
159391
|
-
|
|
159392
|
-
|
|
159393
|
-
|
|
159394
|
-
|
|
159395
|
-
|
|
159636
|
+
if (api.stopping) throw stopInProgressError();
|
|
159637
|
+
api.profilingActive = false;
|
|
159638
|
+
api.stopping = true;
|
|
159639
|
+
try {
|
|
159640
|
+
const cpuResult = await cdp.send("Profiler.stop", void 0, COLLECT_TIMEOUT_MS);
|
|
159641
|
+
if (!cpuResult?.profile) {
|
|
159642
|
+
throw new FailureError(
|
|
159643
|
+
"Hermes Profiler.stop returned no profile data. The CPU sampler may have been reset between start and stop (Metro reload, debugger disconnect). Call react-profiler-start to begin a fresh session.",
|
|
159644
|
+
{
|
|
159645
|
+
error_code: FAILURE_CODES.REACT_PROFILER_NO_CPU_PROFILE,
|
|
159646
|
+
failure_stage: "react_profiler_stop_cpu_profile",
|
|
159647
|
+
failure_area: "tool_server",
|
|
159648
|
+
error_kind: "unknown"
|
|
159649
|
+
}
|
|
159650
|
+
);
|
|
159651
|
+
}
|
|
159652
|
+
const profile = cpuResult.profile;
|
|
159653
|
+
if (!Array.isArray(profile.samples) || !Array.isArray(profile.nodes) || !Array.isArray(profile.timeDeltas)) {
|
|
159654
|
+
throw new FailureError(
|
|
159655
|
+
"Hermes Profiler.stop returned a malformed profile (missing samples/nodes/timeDeltas). This usually means CPU sampling was never actually started for this session \u2014 most often a release build without React DevTools, or a Metro reload between start and stop. Call react-profiler-start on a dev build and retry.",
|
|
159656
|
+
{
|
|
159657
|
+
error_code: FAILURE_CODES.REACT_PROFILER_NO_CPU_PROFILE,
|
|
159658
|
+
failure_stage: "react_profiler_stop_malformed_profile",
|
|
159659
|
+
failure_area: "tool_server",
|
|
159660
|
+
error_kind: "unknown"
|
|
159661
|
+
}
|
|
159662
|
+
);
|
|
159663
|
+
}
|
|
159664
|
+
const stopReadStr = await cdp.send(
|
|
159665
|
+
"Runtime.evaluate",
|
|
159666
|
+
{ expression: STOP_AND_READ_SCRIPT, returnByValue: true },
|
|
159667
|
+
COLLECT_TIMEOUT_MS
|
|
159668
|
+
);
|
|
159669
|
+
if (stopReadStr.exceptionDetails) {
|
|
159670
|
+
throw new FailureError(
|
|
159671
|
+
`Runtime exception while reading profiling data: ${stopReadStr.exceptionDetails.text ?? "unknown"}`,
|
|
159672
|
+
{
|
|
159673
|
+
error_code: FAILURE_CODES.REACT_PROFILER_RUNTIME_EXCEPTION,
|
|
159674
|
+
failure_stage: "react_profiler_stop_runtime_read",
|
|
159675
|
+
failure_area: "tool_server",
|
|
159676
|
+
error_kind: "unknown"
|
|
159677
|
+
}
|
|
159678
|
+
);
|
|
159679
|
+
}
|
|
159680
|
+
const stopReadRaw = stopReadStr.result?.value;
|
|
159681
|
+
if (!stopReadRaw) {
|
|
159682
|
+
throw new FailureError("No profiling data returned from runtime.", {
|
|
159683
|
+
error_code: FAILURE_CODES.REACT_PROFILER_NO_RUNTIME_DATA,
|
|
159684
|
+
failure_stage: "react_profiler_stop_runtime_read",
|
|
159396
159685
|
failure_area: "tool_server",
|
|
159397
159686
|
error_kind: "unknown"
|
|
159687
|
+
});
|
|
159688
|
+
}
|
|
159689
|
+
const stopRead = JSON.parse(stopReadRaw);
|
|
159690
|
+
const merged = stopRead.live ?? { dataForRoots: [] };
|
|
159691
|
+
const backendCommitCount = merged.dataForRoots.reduce(
|
|
159692
|
+
(a, r) => a + (r.commitData?.length ?? 0),
|
|
159693
|
+
0
|
|
159694
|
+
);
|
|
159695
|
+
let fiberMeta = {};
|
|
159696
|
+
if (backendCommitCount > 0) {
|
|
159697
|
+
try {
|
|
159698
|
+
const metaStr = await cdp.send(
|
|
159699
|
+
"Runtime.evaluate",
|
|
159700
|
+
{ expression: RESOLVE_FIBER_META_SCRIPT, returnByValue: true },
|
|
159701
|
+
15e3
|
|
159702
|
+
);
|
|
159703
|
+
if (metaStr.result?.value) {
|
|
159704
|
+
fiberMeta = JSON.parse(metaStr.result.value);
|
|
159705
|
+
}
|
|
159706
|
+
} catch {
|
|
159398
159707
|
}
|
|
159708
|
+
}
|
|
159709
|
+
const {
|
|
159710
|
+
commits: allCommits,
|
|
159711
|
+
totalCommits,
|
|
159712
|
+
unattributedByCommit
|
|
159713
|
+
} = flattenProfilingData(merged, stopRead.displayNameById, fiberMeta);
|
|
159714
|
+
const commitHeat = /* @__PURE__ */ new Map();
|
|
159715
|
+
let anyCompilerOptimized = false;
|
|
159716
|
+
for (const c of allCommits) {
|
|
159717
|
+
const prev = commitHeat.get(c.commitIndex) ?? 0;
|
|
159718
|
+
if (c.commitDuration > prev) commitHeat.set(c.commitIndex, c.commitDuration);
|
|
159719
|
+
if (c.isCompilerOptimized) anyCompilerOptimized = true;
|
|
159720
|
+
}
|
|
159721
|
+
const ABSOLUTE_FLOOR_MS = 16;
|
|
159722
|
+
const interestingKeys = [...commitHeat.keys()].filter(
|
|
159723
|
+
(k) => (commitHeat.get(k) ?? 0) >= ABSOLUTE_FLOOR_MS
|
|
159399
159724
|
);
|
|
159400
|
-
|
|
159401
|
-
|
|
159402
|
-
|
|
159403
|
-
|
|
159404
|
-
|
|
159405
|
-
|
|
159406
|
-
|
|
159407
|
-
|
|
159408
|
-
|
|
159725
|
+
const hotSet = new Set(interestingKeys);
|
|
159726
|
+
const marginSet = /* @__PURE__ */ new Set();
|
|
159727
|
+
for (const ci of interestingKeys) {
|
|
159728
|
+
if (commitHeat.has(ci - 1) && !hotSet.has(ci - 1)) marginSet.add(ci - 1);
|
|
159729
|
+
if (commitHeat.has(ci + 1) && !hotSet.has(ci + 1)) marginSet.add(ci + 1);
|
|
159730
|
+
}
|
|
159731
|
+
const keepSet = /* @__PURE__ */ new Set([...hotSet, ...marginSet]);
|
|
159732
|
+
const filteredCommits = interestingKeys.length === 0 ? [] : allCommits.filter((c) => keepSet.has(c.commitIndex));
|
|
159733
|
+
api.anyCompilerOptimized = anyCompilerOptimized;
|
|
159734
|
+
api.totalReactCommits = totalCommits;
|
|
159735
|
+
api.hotCommitIndices = interestingKeys;
|
|
159736
|
+
const commitTree = { commits: filteredCommits, hookNames: /* @__PURE__ */ new Map() };
|
|
159737
|
+
const sessionTs = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, (m) => m === "T" ? "-" : "").slice(0, 15);
|
|
159738
|
+
const debugDir = await getDebugDir();
|
|
159739
|
+
const cpuProfilePath = await writeDumpCompact(
|
|
159740
|
+
debugDir,
|
|
159741
|
+
`react-profiler-${sessionTs}_cpu.json`,
|
|
159742
|
+
profile
|
|
159743
|
+
);
|
|
159744
|
+
const commitsPath = await writeDumpCompact(
|
|
159745
|
+
debugDir,
|
|
159746
|
+
`react-profiler-${sessionTs}_commits.json`,
|
|
159409
159747
|
{
|
|
159410
|
-
|
|
159411
|
-
|
|
159412
|
-
|
|
159413
|
-
|
|
159748
|
+
commits: commitTree.commits,
|
|
159749
|
+
meta: {
|
|
159750
|
+
detectedArchitecture: api.detectedArchitecture,
|
|
159751
|
+
anyCompilerOptimized: api.anyCompilerOptimized,
|
|
159752
|
+
hotCommitIndices: api.hotCommitIndices,
|
|
159753
|
+
totalReactCommits: api.totalReactCommits,
|
|
159754
|
+
profileStartWallMs: api.profileStartWallMs,
|
|
159755
|
+
projectRoot: api.projectRoot,
|
|
159756
|
+
deviceId: api.deviceId,
|
|
159757
|
+
port: api.port,
|
|
159758
|
+
appName: api.appName,
|
|
159759
|
+
deviceName: api.deviceName,
|
|
159760
|
+
unattributedByCommit: unattributedByCommit.length > 0 ? unattributedByCommit : null
|
|
159761
|
+
}
|
|
159414
159762
|
}
|
|
159415
159763
|
);
|
|
159416
|
-
|
|
159417
|
-
|
|
159418
|
-
|
|
159419
|
-
|
|
159420
|
-
|
|
159421
|
-
|
|
159422
|
-
|
|
159423
|
-
|
|
159424
|
-
|
|
159425
|
-
|
|
159426
|
-
|
|
159427
|
-
|
|
159428
|
-
|
|
159429
|
-
|
|
159430
|
-
|
|
159431
|
-
|
|
159432
|
-
|
|
159433
|
-
|
|
159434
|
-
|
|
159435
|
-
|
|
159436
|
-
|
|
159437
|
-
|
|
159438
|
-
|
|
159439
|
-
|
|
159440
|
-
|
|
159441
|
-
|
|
159764
|
+
const sessionPaths = {
|
|
159765
|
+
sessionId: sessionTs,
|
|
159766
|
+
debugDir,
|
|
159767
|
+
cpuProfilePath,
|
|
159768
|
+
commitsPath,
|
|
159769
|
+
cpuSampleIndexPath: null,
|
|
159770
|
+
detectedArchitecture: api.detectedArchitecture,
|
|
159771
|
+
anyCompilerOptimized: api.anyCompilerOptimized,
|
|
159772
|
+
hotCommitIndices: api.hotCommitIndices,
|
|
159773
|
+
totalReactCommits: api.totalReactCommits
|
|
159774
|
+
};
|
|
159775
|
+
cacheProfilerPaths(api.port, sessionPaths, api.deviceId);
|
|
159776
|
+
clearLostProfilerRun(api.port, api.deviceId);
|
|
159777
|
+
api.sessionPaths = sessionPaths;
|
|
159778
|
+
api.sessionId = null;
|
|
159779
|
+
api.ownerToolServerPid = null;
|
|
159780
|
+
api.disposeSession();
|
|
159781
|
+
const duration_ms = (profile.endTime - profile.startTime) / 1e3;
|
|
159782
|
+
const response = {
|
|
159783
|
+
duration_ms,
|
|
159784
|
+
sample_count: profile.samples.length,
|
|
159785
|
+
fiber_renders_captured: allCommits.length
|
|
159786
|
+
};
|
|
159787
|
+
if (totalCommits > 0) {
|
|
159788
|
+
response["total_react_commits"] = totalCommits;
|
|
159789
|
+
response["hot_commit_indices"] = api.hotCommitIndices ?? [];
|
|
159790
|
+
response["any_compiler_optimized"] = api.anyCompilerOptimized ?? false;
|
|
159791
|
+
response["fiber_renders_analyzed"] = filteredCommits.length;
|
|
159792
|
+
const hotCount = api.hotCommitIndices?.length ?? 0;
|
|
159793
|
+
if (hotCount === 0) {
|
|
159794
|
+
response["selection_note"] = "All commits below 16ms \u2014 app appears smooth (all-clear)";
|
|
159795
|
+
} else if (hotCount < totalCommits) {
|
|
159796
|
+
response["selection_note"] = `${hotCount} of ${totalCommits} commits at \u226516ms absolute floor`;
|
|
159797
|
+
}
|
|
159798
|
+
if (unattributedByCommit.length > 0) {
|
|
159799
|
+
let totalMs = 0;
|
|
159800
|
+
let totalFibers = 0;
|
|
159801
|
+
for (const [, count2, ms] of unattributedByCommit) {
|
|
159802
|
+
totalFibers += count2;
|
|
159803
|
+
totalMs += ms;
|
|
159804
|
+
}
|
|
159805
|
+
response["unattributed_ms"] = Math.round(totalMs * 100) / 100;
|
|
159806
|
+
response["unattributed_fiber_count"] = totalFibers;
|
|
159807
|
+
response["unattributed_commit_count"] = unattributedByCommit.length;
|
|
159808
|
+
if (stopRead.displayNameApiAvailable === false) {
|
|
159809
|
+
response["unattributed_reason"] = "react-devtools backend in this app does not expose a recognized display-name accessor (neither getDisplayNameForElementID nor getDisplayNameForFiberID). The component breakdown is unavailable for this profiling session; commit-level timing data remains correct.";
|
|
159810
|
+
}
|
|
159442
159811
|
}
|
|
159443
|
-
} catch {
|
|
159444
159812
|
}
|
|
159813
|
+
return response;
|
|
159814
|
+
} catch (err) {
|
|
159815
|
+
recordLostProfilerRun(api, "stop_failed");
|
|
159816
|
+
throw err;
|
|
159817
|
+
} finally {
|
|
159818
|
+
api.stopping = false;
|
|
159445
159819
|
}
|
|
159446
|
-
const {
|
|
159447
|
-
commits: allCommits,
|
|
159448
|
-
totalCommits,
|
|
159449
|
-
unattributedByCommit
|
|
159450
|
-
} = flattenProfilingData(merged, stopRead.displayNameById, fiberMeta);
|
|
159451
|
-
const commitHeat = /* @__PURE__ */ new Map();
|
|
159452
|
-
let anyCompilerOptimized = false;
|
|
159453
|
-
for (const c of allCommits) {
|
|
159454
|
-
const prev = commitHeat.get(c.commitIndex) ?? 0;
|
|
159455
|
-
if (c.commitDuration > prev) commitHeat.set(c.commitIndex, c.commitDuration);
|
|
159456
|
-
if (c.isCompilerOptimized) anyCompilerOptimized = true;
|
|
159457
|
-
}
|
|
159458
|
-
const ABSOLUTE_FLOOR_MS = 16;
|
|
159459
|
-
const interestingKeys = [...commitHeat.keys()].filter(
|
|
159460
|
-
(k) => (commitHeat.get(k) ?? 0) >= ABSOLUTE_FLOOR_MS
|
|
159461
|
-
);
|
|
159462
|
-
const hotSet = new Set(interestingKeys);
|
|
159463
|
-
const marginSet = /* @__PURE__ */ new Set();
|
|
159464
|
-
for (const ci of interestingKeys) {
|
|
159465
|
-
if (commitHeat.has(ci - 1) && !hotSet.has(ci - 1)) marginSet.add(ci - 1);
|
|
159466
|
-
if (commitHeat.has(ci + 1) && !hotSet.has(ci + 1)) marginSet.add(ci + 1);
|
|
159467
|
-
}
|
|
159468
|
-
const keepSet = /* @__PURE__ */ new Set([...hotSet, ...marginSet]);
|
|
159469
|
-
const filteredCommits = interestingKeys.length === 0 ? [] : allCommits.filter((c) => keepSet.has(c.commitIndex));
|
|
159470
|
-
api.anyCompilerOptimized = anyCompilerOptimized;
|
|
159471
|
-
api.totalReactCommits = totalCommits;
|
|
159472
|
-
api.hotCommitIndices = interestingKeys;
|
|
159473
|
-
const commitTree = { commits: filteredCommits, hookNames: /* @__PURE__ */ new Map() };
|
|
159474
|
-
const sessionTs = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, (m) => m === "T" ? "-" : "").slice(0, 15);
|
|
159475
|
-
const debugDir = await getDebugDir();
|
|
159476
|
-
const cpuProfilePath = await writeDumpCompact(
|
|
159477
|
-
debugDir,
|
|
159478
|
-
`react-profiler-${sessionTs}_cpu.json`,
|
|
159479
|
-
profile
|
|
159480
|
-
);
|
|
159481
|
-
const commitsPath = await writeDumpCompact(
|
|
159482
|
-
debugDir,
|
|
159483
|
-
`react-profiler-${sessionTs}_commits.json`,
|
|
159484
|
-
{
|
|
159485
|
-
commits: commitTree.commits,
|
|
159486
|
-
meta: {
|
|
159487
|
-
detectedArchitecture: api.detectedArchitecture,
|
|
159488
|
-
anyCompilerOptimized: api.anyCompilerOptimized,
|
|
159489
|
-
hotCommitIndices: api.hotCommitIndices,
|
|
159490
|
-
totalReactCommits: api.totalReactCommits,
|
|
159491
|
-
profileStartWallMs: api.profileStartWallMs,
|
|
159492
|
-
projectRoot: api.projectRoot,
|
|
159493
|
-
deviceId: api.deviceId,
|
|
159494
|
-
port: api.port,
|
|
159495
|
-
appName: api.appName,
|
|
159496
|
-
deviceName: api.deviceName,
|
|
159497
|
-
unattributedByCommit: unattributedByCommit.length > 0 ? unattributedByCommit : null
|
|
159498
|
-
}
|
|
159499
|
-
}
|
|
159500
|
-
);
|
|
159501
|
-
const sessionPaths = {
|
|
159502
|
-
sessionId: sessionTs,
|
|
159503
|
-
debugDir,
|
|
159504
|
-
cpuProfilePath,
|
|
159505
|
-
commitsPath,
|
|
159506
|
-
cpuSampleIndexPath: null,
|
|
159507
|
-
detectedArchitecture: api.detectedArchitecture,
|
|
159508
|
-
anyCompilerOptimized: api.anyCompilerOptimized,
|
|
159509
|
-
hotCommitIndices: api.hotCommitIndices,
|
|
159510
|
-
totalReactCommits: api.totalReactCommits
|
|
159511
|
-
};
|
|
159512
|
-
cacheProfilerPaths(api.port, sessionPaths, api.deviceId);
|
|
159513
|
-
api.sessionPaths = sessionPaths;
|
|
159514
|
-
api.sessionId = null;
|
|
159515
|
-
api.ownerToolServerPid = null;
|
|
159516
|
-
api.disposeSession();
|
|
159517
|
-
const duration_ms = (profile.endTime - profile.startTime) / 1e3;
|
|
159518
|
-
const response = {
|
|
159519
|
-
duration_ms,
|
|
159520
|
-
sample_count: profile.samples.length,
|
|
159521
|
-
fiber_renders_captured: allCommits.length
|
|
159522
|
-
};
|
|
159523
|
-
if (totalCommits > 0) {
|
|
159524
|
-
response["total_react_commits"] = totalCommits;
|
|
159525
|
-
response["hot_commit_indices"] = api.hotCommitIndices ?? [];
|
|
159526
|
-
response["any_compiler_optimized"] = api.anyCompilerOptimized ?? false;
|
|
159527
|
-
response["fiber_renders_analyzed"] = filteredCommits.length;
|
|
159528
|
-
const hotCount = api.hotCommitIndices?.length ?? 0;
|
|
159529
|
-
if (hotCount === 0) {
|
|
159530
|
-
response["selection_note"] = "All commits below 16ms \u2014 app appears smooth (all-clear)";
|
|
159531
|
-
} else if (hotCount < totalCommits) {
|
|
159532
|
-
response["selection_note"] = `${hotCount} of ${totalCommits} commits at \u226516ms absolute floor`;
|
|
159533
|
-
}
|
|
159534
|
-
if (unattributedByCommit.length > 0) {
|
|
159535
|
-
let totalMs = 0;
|
|
159536
|
-
let totalFibers = 0;
|
|
159537
|
-
for (const [, count2, ms] of unattributedByCommit) {
|
|
159538
|
-
totalFibers += count2;
|
|
159539
|
-
totalMs += ms;
|
|
159540
|
-
}
|
|
159541
|
-
response["unattributed_ms"] = Math.round(totalMs * 100) / 100;
|
|
159542
|
-
response["unattributed_fiber_count"] = totalFibers;
|
|
159543
|
-
response["unattributed_commit_count"] = unattributedByCommit.length;
|
|
159544
|
-
if (stopRead.displayNameApiAvailable === false) {
|
|
159545
|
-
response["unattributed_reason"] = "react-devtools backend in this app does not expose a recognized display-name accessor (neither getDisplayNameForElementID nor getDisplayNameForFiberID). The component breakdown is unavailable for this profiling session; commit-level timing data remains correct.";
|
|
159546
|
-
}
|
|
159547
|
-
}
|
|
159548
|
-
}
|
|
159549
|
-
return response;
|
|
159550
159820
|
}
|
|
159551
159821
|
};
|
|
159552
159822
|
}
|
|
@@ -159562,7 +159832,7 @@ var zodSchema47 = external_exports.object({
|
|
|
159562
159832
|
function createReactProfilerStatusTool(registry2) {
|
|
159563
159833
|
return {
|
|
159564
159834
|
id: "react-profiler-status",
|
|
159565
|
-
description: `Check the state of the React profiler session
|
|
159835
|
+
description: `Check the state of the React profiler session. Use when resuming after an interruption (debugger disconnect, unexpected error, agent pause) to decide whether to continue with react-profiler-stop, start a new session, or reconnect the debugger. Returns { session_status, is_running, current_owner, note, \u2026 }: session_status is active, saved, lost, taken_over, not_started or no_react_runtime, and note says what to do next.`,
|
|
159566
159836
|
zodSchema: zodSchema47,
|
|
159567
159837
|
readOnly: true,
|
|
159568
159838
|
interaction: {
|
|
@@ -159574,7 +159844,19 @@ function createReactProfilerStatusTool(registry2) {
|
|
|
159574
159844
|
capability: RN_ONLY_TOOL_CAPABILITY,
|
|
159575
159845
|
services: () => ({}),
|
|
159576
159846
|
async execute(_services, params) {
|
|
159577
|
-
const
|
|
159847
|
+
const port = metroPort(params);
|
|
159848
|
+
const deviceId = canonicalDeviceId(params.device_id) ?? params.device_id;
|
|
159849
|
+
const psUrn = `${REACT_PROFILER_SESSION_NAMESPACE}:${port}:${deviceId}`;
|
|
159850
|
+
const saved = getCachedProfilerPaths(port, deviceId);
|
|
159851
|
+
const lost = getLostProfilerRun(port, deviceId);
|
|
159852
|
+
const lastRun = {
|
|
159853
|
+
...saved ? { saved_session: savedSessionInfo(saved) } : {},
|
|
159854
|
+
...lost ? { lost_run: lostRunInfo(lost) } : {}
|
|
159855
|
+
};
|
|
159856
|
+
const withLastRun = (note) => {
|
|
159857
|
+
const text = lastRunText(saved, lost);
|
|
159858
|
+
return text ? `${note} ${text}` : note;
|
|
159859
|
+
};
|
|
159578
159860
|
const noRuntime = (note) => ({
|
|
159579
159861
|
hook_exists: false,
|
|
159580
159862
|
renderer_interface_found: false,
|
|
@@ -159582,7 +159864,8 @@ function createReactProfilerStatusTool(registry2) {
|
|
|
159582
159864
|
current_session_id: null,
|
|
159583
159865
|
current_owner: null,
|
|
159584
159866
|
session_status: "no_react_runtime",
|
|
159585
|
-
note
|
|
159867
|
+
note: withLastRun(note),
|
|
159868
|
+
...lastRun
|
|
159586
159869
|
});
|
|
159587
159870
|
let api;
|
|
159588
159871
|
try {
|
|
@@ -159592,6 +159875,16 @@ function createReactProfilerStatusTool(registry2) {
|
|
|
159592
159875
|
"Unable to resolve a React profiler session \u2014 the Hermes runtime may not be reachable. Reconnect the debugger and retry."
|
|
159593
159876
|
);
|
|
159594
159877
|
}
|
|
159878
|
+
const stopInProgress2 = () => ({
|
|
159879
|
+
hook_exists: true,
|
|
159880
|
+
renderer_interface_found: true,
|
|
159881
|
+
is_running: true,
|
|
159882
|
+
current_session_id: api.sessionId,
|
|
159883
|
+
current_owner: null,
|
|
159884
|
+
session_status: "active",
|
|
159885
|
+
note: STOP_IN_PROGRESS_NOTE
|
|
159886
|
+
});
|
|
159887
|
+
if (api.stopping) return stopInProgress2();
|
|
159595
159888
|
const cdp = api.cdp;
|
|
159596
159889
|
if (!cdp.isConnected()) {
|
|
159597
159890
|
return noRuntime(
|
|
@@ -159616,61 +159909,119 @@ function createReactProfilerStatusTool(registry2) {
|
|
|
159616
159909
|
);
|
|
159617
159910
|
}
|
|
159618
159911
|
const state3 = JSON.parse(stateJson);
|
|
159912
|
+
if (api.stopping) return stopInProgress2();
|
|
159619
159913
|
if (!state3.hookExists) {
|
|
159620
|
-
return
|
|
159621
|
-
|
|
159622
|
-
|
|
159623
|
-
is_running: false,
|
|
159624
|
-
current_session_id: null,
|
|
159625
|
-
current_owner: null,
|
|
159626
|
-
session_status: "no_react_runtime",
|
|
159627
|
-
note: "React DevTools hook not attached \u2014 debugger may be disconnected or the app isn't a dev build."
|
|
159628
|
-
};
|
|
159914
|
+
return noRuntime(
|
|
159915
|
+
"React DevTools hook not attached \u2014 debugger may be disconnected or the app isn't a dev build."
|
|
159916
|
+
);
|
|
159629
159917
|
}
|
|
159630
159918
|
if (!("rendererInterfaceFound" in state3) || !state3.rendererInterfaceFound) {
|
|
159631
159919
|
return {
|
|
159632
|
-
|
|
159633
|
-
|
|
159634
|
-
|
|
159635
|
-
|
|
159636
|
-
current_owner: null,
|
|
159637
|
-
session_status: "no_react_runtime",
|
|
159638
|
-
note: "No React renderer attached yet \u2014 the app hasn't rendered its first commit."
|
|
159920
|
+
...noRuntime(
|
|
159921
|
+
"No React renderer attached yet \u2014 the app hasn't rendered its first commit."
|
|
159922
|
+
),
|
|
159923
|
+
hook_exists: true
|
|
159639
159924
|
};
|
|
159640
159925
|
}
|
|
159641
159926
|
const owner = state3.owner;
|
|
159642
|
-
const
|
|
159643
|
-
|
|
159927
|
+
const inApp = {
|
|
159928
|
+
hook_exists: true,
|
|
159929
|
+
renderer_interface_found: true,
|
|
159930
|
+
is_running: state3.isRunning,
|
|
159931
|
+
current_session_id: owner?.sessionId ?? null,
|
|
159932
|
+
current_owner: owner
|
|
159933
|
+
};
|
|
159934
|
+
const ownRunId = api.sessionId;
|
|
159935
|
+
const recordingHere = api.profilingActive && ownRunId !== null;
|
|
159936
|
+
const lostResponse = (sessionId, cause, stillRecording) => ({
|
|
159937
|
+
...inApp,
|
|
159938
|
+
session_status: "lost",
|
|
159939
|
+
note: `Your run${sessionId ? ` (session ${sessionId})` : ""} was not saved: ${lostRunCauseText(cause)}. ` + (stillRecording ? "The app is still recording it, but react-profiler-stop can no longer collect it. Call react-profiler-start: it stops that recording and starts a new run, no force needed." : cause === "ended_in_app" ? "Call react-profiler-stop to collect it. A react-profiler-start would discard it." : "Call react-profiler-start to record again."),
|
|
159940
|
+
...lastRun,
|
|
159941
|
+
lost_run: { session_id: sessionId, cause }
|
|
159942
|
+
});
|
|
159943
|
+
if (state3.isRunning) {
|
|
159944
|
+
if (owner && ownRunId !== null && owner.sessionId === ownRunId) {
|
|
159945
|
+
if (recordingHere) {
|
|
159946
|
+
const { stale, heartbeatAgeSeconds } = classifyStaleness({
|
|
159947
|
+
owner,
|
|
159948
|
+
nowEpochMs: state3.nowEpochMs
|
|
159949
|
+
});
|
|
159950
|
+
await cdp.evaluate(buildHeartbeatScript(ownRunId)).catch(() => {
|
|
159951
|
+
});
|
|
159952
|
+
return {
|
|
159953
|
+
...inApp,
|
|
159954
|
+
session_status: "active",
|
|
159955
|
+
stale,
|
|
159956
|
+
heartbeat_age_seconds: heartbeatAgeSeconds,
|
|
159957
|
+
note: stale ? STALE_OWN_RUN_NOTE : "A profiling session started through this tool-server is still running: yours, unless another agent shares this tool-server. Call react-profiler-stop to collect the data, or continue profiling."
|
|
159958
|
+
};
|
|
159959
|
+
}
|
|
159960
|
+
return lostResponse(ownRunId, "stop_failed", true);
|
|
159961
|
+
}
|
|
159962
|
+
if (owner && lost && lost.sessionId === owner.sessionId) {
|
|
159963
|
+
return {
|
|
159964
|
+
...inApp,
|
|
159965
|
+
session_status: "lost",
|
|
159966
|
+
note: `Your run (session ${owner.sessionId}) was not saved: ${lostRunCauseText(lost.cause, lost.takenOverBy)}. The app is still recording it, but react-profiler-stop can no longer collect it. Call react-profiler-start: it stops that recording and starts a new run, no force needed.`,
|
|
159967
|
+
...lastRun
|
|
159968
|
+
};
|
|
159969
|
+
}
|
|
159970
|
+
const yoursReplaced = recordingHere || lost?.cause === "taken_over";
|
|
159971
|
+
const yourRunId = ownRunId ?? lost?.sessionId ?? null;
|
|
159972
|
+
if (!owner) {
|
|
159973
|
+
return {
|
|
159974
|
+
...inApp,
|
|
159975
|
+
session_status: "taken_over",
|
|
159976
|
+
note: withLastRun(
|
|
159977
|
+
(yoursReplaced ? "Your run was replaced: a profiling session is running in the app, but its ownership is unattributable" : "A profiling session is running but its ownership is unattributable") + " \u2014 likely started by a foreign DevTools client without our setup script, or cleared in a narrow window. Treat as lost and restart."
|
|
159978
|
+
),
|
|
159979
|
+
...lastRun
|
|
159980
|
+
};
|
|
159981
|
+
}
|
|
159644
159982
|
return {
|
|
159645
|
-
|
|
159646
|
-
|
|
159647
|
-
|
|
159648
|
-
|
|
159649
|
-
|
|
159650
|
-
|
|
159651
|
-
note: "No profiling session is currently running. If you expected to be profiling, the session ended \u2014 call react-profiler-start to begin a new one; prior data is not recoverable."
|
|
159983
|
+
...inApp,
|
|
159984
|
+
session_status: "taken_over",
|
|
159985
|
+
note: yoursReplaced ? `Your run${yourRunId ? ` (session ${yourRunId})` : ""} was taken over by session ${owner.sessionId}, which is still recording. Your data was discarded at the takeover. Do not stop their run; ask the user before taking over with react-profiler-start { force: true }.` : withLastRun(
|
|
159986
|
+
`Another profiling session (${owner.sessionId}) is running: another tool-server instance took over, or this tool-server restarted after react-profiler-start. Data from a run it replaced is lost at the takeover moment. Use react-profiler-start { force: true } to reclaim, after asking the user if the run may be someone else's.`
|
|
159987
|
+
),
|
|
159988
|
+
...lastRun
|
|
159652
159989
|
};
|
|
159653
159990
|
}
|
|
159654
|
-
if (
|
|
159991
|
+
if (recordingHere) {
|
|
159992
|
+
if (takeoverOf(state3, api)) {
|
|
159993
|
+
return {
|
|
159994
|
+
...inApp,
|
|
159995
|
+
session_status: "taken_over",
|
|
159996
|
+
note: `Your run (session ${ownRunId}) was not saved: ${lostRunCauseText("taken_over")}. That session has since stopped too. Nothing is recording now; call react-profiler-start to record again.`,
|
|
159997
|
+
...lastRun,
|
|
159998
|
+
lost_run: { session_id: ownRunId, cause: "taken_over" }
|
|
159999
|
+
};
|
|
160000
|
+
}
|
|
160001
|
+
return lostResponse(ownRunId, "ended_in_app", false);
|
|
160002
|
+
}
|
|
160003
|
+
if (ownRunId !== null) return lostResponse(ownRunId, "stop_failed", false);
|
|
160004
|
+
if (lost) {
|
|
160005
|
+
const takenOver = lost.cause === "taken_over";
|
|
159655
160006
|
return {
|
|
159656
|
-
|
|
159657
|
-
|
|
159658
|
-
|
|
159659
|
-
|
|
159660
|
-
|
|
159661
|
-
|
|
159662
|
-
|
|
160007
|
+
...inApp,
|
|
160008
|
+
session_status: takenOver ? "taken_over" : "lost",
|
|
160009
|
+
note: lostRunText(lost) + (takenOver ? " That session has since stopped too. " : " ") + "Nothing is recording now; call react-profiler-start to record again.",
|
|
160010
|
+
...lastRun
|
|
160011
|
+
};
|
|
160012
|
+
}
|
|
160013
|
+
if (saved) {
|
|
160014
|
+
return {
|
|
160015
|
+
...inApp,
|
|
160016
|
+
session_status: "saved",
|
|
160017
|
+
note: `No profiling session is running. ${savedSessionText(saved)} A new react-profiler-start replaces it.`,
|
|
160018
|
+
...lastRun
|
|
159663
160019
|
};
|
|
159664
160020
|
}
|
|
159665
|
-
const isMine = api.sessionId !== null && owner.sessionId === api.sessionId;
|
|
159666
160021
|
return {
|
|
159667
|
-
|
|
159668
|
-
|
|
159669
|
-
|
|
159670
|
-
current_session_id: owner.sessionId,
|
|
159671
|
-
current_owner: owner,
|
|
159672
|
-
session_status: isMine ? "active" : "taken_over",
|
|
159673
|
-
note: isMine ? "Your profiling session is still running. Call react-profiler-stop to collect the data, or continue profiling." : "A different profiling session is running (another tool-server instance took over, this process restarted after start, or a stop-all-simulator-servers reaped this device's JS-runtime debugger and took this session down with it, leaving the in-app owner behind). Data from the prior session is lost at the takeover moment. Use react-profiler-start { force: true } to reclaim."
|
|
160022
|
+
...inApp,
|
|
160023
|
+
session_status: "not_started",
|
|
160024
|
+
note: "No profiling session is running, and this tool-server has no run on record for this device: none was started since it launched. Call react-profiler-start to begin one."
|
|
159674
160025
|
};
|
|
159675
160026
|
}
|
|
159676
160027
|
};
|
|
@@ -159717,13 +160068,6 @@ async function detectSessionContext(input2) {
|
|
|
159717
160068
|
reactCompilerEnabled = true;
|
|
159718
160069
|
}
|
|
159719
160070
|
}
|
|
159720
|
-
let strictModeEnabled = false;
|
|
159721
|
-
const indexJs = await readFileSafe((0, import_path3.join)(projectRoot, "index.js"));
|
|
159722
|
-
const indexTs = await readFileSafe((0, import_path3.join)(projectRoot, "index.ts"));
|
|
159723
|
-
const indexSource = (indexJs ?? "") + (indexTs ?? "");
|
|
159724
|
-
if (indexSource.includes("<StrictMode>") || indexSource.includes("StrictMode")) {
|
|
159725
|
-
strictModeEnabled = true;
|
|
159726
|
-
}
|
|
159727
160071
|
let buildMode = "dev";
|
|
159728
160072
|
if (input2.flamegraph) {
|
|
159729
160073
|
for (const node2 of input2.flamegraph.nodes) {
|
|
@@ -159763,7 +160107,6 @@ async function detectSessionContext(input2) {
|
|
|
159763
160107
|
}
|
|
159764
160108
|
return {
|
|
159765
160109
|
reactCompilerEnabled,
|
|
159766
|
-
strictModeEnabled,
|
|
159767
160110
|
buildMode,
|
|
159768
160111
|
rnArchitecture,
|
|
159769
160112
|
projectRoot,
|
|
@@ -160314,14 +160657,12 @@ function bestRootCause(votes, hookTypeNames) {
|
|
|
160314
160657
|
}
|
|
160315
160658
|
function enrich(input2) {
|
|
160316
160659
|
const { sessionContext } = input2;
|
|
160317
|
-
const strictMode = sessionContext.strictModeEnabled;
|
|
160318
160660
|
const components = /* @__PURE__ */ new Map();
|
|
160319
160661
|
for (const [name, acc] of input2.components) {
|
|
160320
160662
|
const n = acc.n;
|
|
160321
160663
|
if (n === 0) continue;
|
|
160322
160664
|
const mean = acc.sum / n;
|
|
160323
|
-
const totalRenderMs =
|
|
160324
|
-
const normalizedRenderCount = strictMode ? Math.ceil(n / 2) : n;
|
|
160665
|
+
const totalRenderMs = acc.sum;
|
|
160325
160666
|
const dr = dominantReason(acc.reasonHistogram);
|
|
160326
160667
|
const dp = dr === "parent" ? topParent(acc.parentFreq) : void 0;
|
|
160327
160668
|
const isCompilerOptimized = acc.compilerOptimizedCount > n / 2;
|
|
@@ -160329,7 +160670,6 @@ function enrich(input2) {
|
|
|
160329
160670
|
const enriched = {
|
|
160330
160671
|
name,
|
|
160331
160672
|
n,
|
|
160332
|
-
normalizedRenderCount,
|
|
160333
160673
|
mean,
|
|
160334
160674
|
min: acc.min === Infinity ? 0 : acc.min,
|
|
160335
160675
|
max: acc.max === -Infinity ? 0 : acc.max,
|
|
@@ -160397,7 +160737,7 @@ var ACTIONABLE_REASONS = /* @__PURE__ */ new Set([
|
|
|
160397
160737
|
]);
|
|
160398
160738
|
var MAX_FINDINGS = 20;
|
|
160399
160739
|
var PARETO_THRESHOLD_PCT = 5e-3;
|
|
160400
|
-
var
|
|
160740
|
+
var MIN_RENDERS = 3;
|
|
160401
160741
|
var MIN_MAX_DURATION_MS = 30;
|
|
160402
160742
|
function rank(input2) {
|
|
160403
160743
|
const { sessionContext, recordingMs } = input2;
|
|
@@ -160406,7 +160746,7 @@ function rank(input2) {
|
|
|
160406
160746
|
if (comp.isAnimated) continue;
|
|
160407
160747
|
if (comp.isRecyclerChild) continue;
|
|
160408
160748
|
if (!ACTIONABLE_REASONS.has(comp.dominantReason)) continue;
|
|
160409
|
-
const passesRenderCount = comp.
|
|
160749
|
+
const passesRenderCount = comp.n >= MIN_RENDERS;
|
|
160410
160750
|
const passesMaxDuration = comp.max >= MIN_MAX_DURATION_MS;
|
|
160411
160751
|
if (!passesRenderCount && !passesMaxDuration) continue;
|
|
160412
160752
|
candidates.push({ name, comp });
|
|
@@ -160419,7 +160759,7 @@ function rank(input2) {
|
|
|
160419
160759
|
const topChangedHookNames = comp.hookTypeNames !== void 0 ? comp.topChangedHooks.map((idx) => comp.hookTypeNames[idx] ?? `hook[${idx}]`) : [];
|
|
160420
160760
|
const finding = {
|
|
160421
160761
|
component: name,
|
|
160422
|
-
renders: comp.
|
|
160762
|
+
renders: comp.n,
|
|
160423
160763
|
totalMs: Math.round(comp.totalRenderMs * 10) / 10,
|
|
160424
160764
|
avgMs: Math.round(comp.mean * 100) / 100,
|
|
160425
160765
|
maxMs: Math.round(comp.max * 100) / 100,
|
|
@@ -160428,7 +160768,7 @@ function rank(input2) {
|
|
|
160428
160768
|
topChangedHookNames
|
|
160429
160769
|
};
|
|
160430
160770
|
if (comp.isCompilerOptimized) finding.isCompilerOptimized = true;
|
|
160431
|
-
if (sessionContext.reactCompilerEnabled && !comp.isCompilerOptimized && comp.
|
|
160771
|
+
if (sessionContext.reactCompilerEnabled && !comp.isCompilerOptimized && comp.n > 5) {
|
|
160432
160772
|
finding.compilerBailoutSuspected = true;
|
|
160433
160773
|
}
|
|
160434
160774
|
if (comp.parentTrigger !== void 0) finding.parentTrigger = comp.parentTrigger;
|
|
@@ -161623,13 +161963,14 @@ Fails if the React DevTools hook is not present in the runtime or the app is not
|
|
|
161623
161963
|
// RN-only: queries the React DevTools backend hook on the live runtime.
|
|
161624
161964
|
capability: RN_ONLY_TOOL_CAPABILITY,
|
|
161625
161965
|
services: (params) => ({
|
|
161626
|
-
|
|
161966
|
+
// Same key as react-profiler-start, so a forwarded logicalDeviceId reads its run.
|
|
161967
|
+
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${metroPort(params)}:${canonicalDeviceId(params.device_id)}`
|
|
161627
161968
|
}),
|
|
161628
161969
|
async execute(services, params) {
|
|
161629
161970
|
const api = services.profilerSession;
|
|
161630
161971
|
const cdp = api.cdp;
|
|
161631
|
-
if (api.profilingActive && api.ownerToolServerPid === process.pid) {
|
|
161632
|
-
await cdp.evaluate(
|
|
161972
|
+
if (api.profilingActive && api.sessionId && api.ownerToolServerPid === process.pid) {
|
|
161973
|
+
await cdp.evaluate(buildHeartbeatScript(api.sessionId)).catch(() => {
|
|
161633
161974
|
});
|
|
161634
161975
|
}
|
|
161635
161976
|
async function evalRenders() {
|
|
@@ -161800,13 +162141,14 @@ Fails if the React DevTools hook is not present or no fiber roots have been comm
|
|
|
161800
162141
|
// RN-only: walks the fiber tree via the React DevTools backend hook.
|
|
161801
162142
|
capability: RN_ONLY_TOOL_CAPABILITY,
|
|
161802
162143
|
services: (params) => ({
|
|
161803
|
-
|
|
162144
|
+
// Same key as react-profiler-start, so a forwarded logicalDeviceId reads its run.
|
|
162145
|
+
profilerSession: `${REACT_PROFILER_SESSION_NAMESPACE}:${metroPort(params)}:${canonicalDeviceId(params.device_id)}`
|
|
161804
162146
|
}),
|
|
161805
162147
|
async execute(services, params) {
|
|
161806
162148
|
const api = services.profilerSession;
|
|
161807
162149
|
const cdp = api.cdp;
|
|
161808
|
-
if (api.profilingActive && api.ownerToolServerPid === process.pid) {
|
|
161809
|
-
await cdp.evaluate(
|
|
162150
|
+
if (api.profilingActive && api.sessionId && api.ownerToolServerPid === process.pid) {
|
|
162151
|
+
await cdp.evaluate(buildHeartbeatScript(api.sessionId)).catch(() => {
|
|
161810
162152
|
});
|
|
161811
162153
|
}
|
|
161812
162154
|
const script = buildFiberTreeScript(params.max_depth, params.filter ?? "");
|
|
@@ -169040,7 +169382,8 @@ async function loadReactSession(debugDir, sessionId, port, deviceId) {
|
|
|
169040
169382
|
detectedArchitecture,
|
|
169041
169383
|
anyCompilerOptimized,
|
|
169042
169384
|
hotCommitIndices,
|
|
169043
|
-
totalReactCommits
|
|
169385
|
+
totalReactCommits,
|
|
169386
|
+
loadedFromDisk: true
|
|
169044
169387
|
};
|
|
169045
169388
|
cacheProfilerPaths(port, sessionPaths, deviceId);
|
|
169046
169389
|
const lines = [
|