@swmansion/argent 0.32.1-next.13 → 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 +620 -278
- 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");
|
|
@@ -158758,30 +158860,6 @@ var crypto6 = __toESM(require("node:crypto"));
|
|
|
158758
158860
|
init_zod();
|
|
158759
158861
|
init_src2();
|
|
158760
158862
|
|
|
158761
|
-
// ../tool-server/src/utils/react-profiler/session-ownership.ts
|
|
158762
|
-
var DEFAULT_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
158763
|
-
function classifyStaleness({
|
|
158764
|
-
owner,
|
|
158765
|
-
nowEpochMs,
|
|
158766
|
-
staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS
|
|
158767
|
-
}) {
|
|
158768
|
-
if (!owner) {
|
|
158769
|
-
return {
|
|
158770
|
-
stale: false,
|
|
158771
|
-
ageSeconds: null,
|
|
158772
|
-
canReclaimWithoutForce: true
|
|
158773
|
-
};
|
|
158774
|
-
}
|
|
158775
|
-
const ageMs = nowEpochMs - owner.startedAtEpochMs;
|
|
158776
|
-
const heartbeatAgeMs = nowEpochMs - owner.lastHeartbeatEpochMs;
|
|
158777
|
-
const stale = heartbeatAgeMs > staleThresholdMs;
|
|
158778
|
-
return {
|
|
158779
|
-
stale,
|
|
158780
|
-
ageSeconds: Math.max(0, ageMs) / 1e3,
|
|
158781
|
-
canReclaimWithoutForce: stale
|
|
158782
|
-
};
|
|
158783
|
-
}
|
|
158784
|
-
|
|
158785
158863
|
// ../tool-server/src/utils/debugger/runtime-device.ts
|
|
158786
158864
|
var import_node_crypto13 = require("node:crypto");
|
|
158787
158865
|
var import_node_child_process43 = require("node:child_process");
|
|
@@ -158920,6 +158998,46 @@ function logicalDeviceWarning(deviceId, runtimeApp) {
|
|
|
158920
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.";
|
|
158921
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.";
|
|
158922
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
|
+
}
|
|
158923
159041
|
function safeGetState(registry2, urn) {
|
|
158924
159042
|
try {
|
|
158925
159043
|
return registry2.getServiceState(urn);
|
|
@@ -158944,7 +159062,7 @@ function createReactProfilerStartTool(registry2) {
|
|
|
158944
159062
|
Use when you need to profile a user interaction.
|
|
158945
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.
|
|
158946
159064
|
After starting, ask the user to perform the interaction to profile, then call react-profiler-stop.
|
|
158947
|
-
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.
|
|
158948
159066
|
Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot be established.`,
|
|
158949
159067
|
zodSchema: zodSchema45,
|
|
158950
159068
|
interaction: {
|
|
@@ -158985,6 +159103,7 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
158985
159103
|
await disposeAndWait();
|
|
158986
159104
|
}
|
|
158987
159105
|
let api = await registry2.resolveService(psUrn);
|
|
159106
|
+
if (api.stopping) return stopInProgress();
|
|
158988
159107
|
if (!api.cdp.isConnected()) {
|
|
158989
159108
|
await disposeAndWait();
|
|
158990
159109
|
api = await registry2.resolveService(psUrn);
|
|
@@ -159071,6 +159190,13 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159071
159190
|
);
|
|
159072
159191
|
}
|
|
159073
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();
|
|
159074
159200
|
if (state3.isRunning) {
|
|
159075
159201
|
const owner = state3.owner;
|
|
159076
159202
|
const staleness = classifyStaleness({
|
|
@@ -159078,10 +159204,24 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159078
159204
|
nowEpochMs: state3.nowEpochMs,
|
|
159079
159205
|
staleThresholdMs: DEFAULT_STALE_THRESHOLD_MS
|
|
159080
159206
|
});
|
|
159081
|
-
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;
|
|
159082
159221
|
if (!canTakeOverSilently) {
|
|
159083
159222
|
return {
|
|
159084
159223
|
already_running: true,
|
|
159224
|
+
mine: false,
|
|
159085
159225
|
owner,
|
|
159086
159226
|
age_seconds: staleness.ageSeconds,
|
|
159087
159227
|
stale: staleness.stale,
|
|
@@ -159089,11 +159229,16 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159089
159229
|
};
|
|
159090
159230
|
}
|
|
159091
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
|
+
});
|
|
159092
159239
|
}
|
|
159093
|
-
|
|
159094
|
-
|
|
159095
|
-
api.profilingActive = false;
|
|
159096
|
-
}
|
|
159240
|
+
await cdp.send("Profiler.stop").catch(ignore);
|
|
159241
|
+
api.profilingActive = false;
|
|
159097
159242
|
const runtimeDevice = isLogicalKeyedDevice(api.deviceId) ? await findRuntimeDevice(cdp) : void 0;
|
|
159098
159243
|
const sessionId = crypto6.randomUUID();
|
|
159099
159244
|
const ownerPayload = {
|
|
@@ -159104,21 +159249,30 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159104
159249
|
lastHeartbeatEpochMs: 0
|
|
159105
159250
|
};
|
|
159106
159251
|
await cdp.send("Profiler.enable").catch(ignore);
|
|
159107
|
-
await cdp.send("Profiler.start", { interval: params.sample_interval_us });
|
|
159108
|
-
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
|
+
});
|
|
159109
159257
|
if (!startJson) {
|
|
159110
|
-
|
|
159111
|
-
|
|
159112
|
-
|
|
159113
|
-
|
|
159114
|
-
|
|
159115
|
-
|
|
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
|
+
);
|
|
159116
159268
|
}
|
|
159117
159269
|
const startResult = JSON.parse(startJson);
|
|
159118
159270
|
if (!startResult.ok) {
|
|
159119
159271
|
await cdp.send("Profiler.stop").catch(ignore);
|
|
159120
159272
|
throw new FailureError(
|
|
159121
|
-
|
|
159273
|
+
withTakeover(
|
|
159274
|
+
`React profiler failed to start (${startResult.reason ?? "unknown"}${startResult.message ? `: ${startResult.message}` : ""})`
|
|
159275
|
+
),
|
|
159122
159276
|
{
|
|
159123
159277
|
error_code: FAILURE_CODES.REACT_PROFILER_START_FAILED,
|
|
159124
159278
|
failure_stage: "react_profiler_start_runtime_start",
|
|
@@ -159130,7 +159284,9 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159130
159284
|
if (startResult.isProfilingFlagSet !== true || startResult.ownerInstalled !== true) {
|
|
159131
159285
|
await cdp.send("Profiler.stop").catch(ignore);
|
|
159132
159286
|
throw new FailureError(
|
|
159133
|
-
|
|
159287
|
+
withTakeover(
|
|
159288
|
+
`React profiler failed to start (post-start verification failed: isProfilingFlagSet=${startResult.isProfilingFlagSet === true}, ownerInstalled=${startResult.ownerInstalled === true})`
|
|
159289
|
+
),
|
|
159134
159290
|
{
|
|
159135
159291
|
error_code: FAILURE_CODES.REACT_PROFILER_START_VERIFY_FAILED,
|
|
159136
159292
|
failure_stage: "react_profiler_start_verify",
|
|
@@ -159143,7 +159299,9 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159143
159299
|
await cdp.evaluate(STOP_FOR_TAKEOVER_SCRIPT).catch(ignore);
|
|
159144
159300
|
await cdp.send("Profiler.stop").catch(ignore);
|
|
159145
159301
|
throw new FailureError(
|
|
159146
|
-
|
|
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
|
+
),
|
|
159147
159305
|
{
|
|
159148
159306
|
error_code: FAILURE_CODES.REACT_PROFILER_SESSION_TORN_DOWN,
|
|
159149
159307
|
failure_stage: "react_profiler_start_session_torn_down",
|
|
@@ -159154,6 +159312,7 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159154
159312
|
}
|
|
159155
159313
|
const startedAtEpochMs = startResult.startedAtEpochMs ?? Date.now();
|
|
159156
159314
|
clearCachedProfilerPaths(api.port, api.deviceId);
|
|
159315
|
+
clearLostProfilerRun(api.port, api.deviceId);
|
|
159157
159316
|
api.sessionPaths = null;
|
|
159158
159317
|
api.profilingActive = true;
|
|
159159
159318
|
api.anyCompilerOptimized = null;
|
|
@@ -159170,6 +159329,7 @@ Fails if the Hermes runtime is not reachable or the Metro CDP connection cannot
|
|
|
159170
159329
|
startedAtEpochMs,
|
|
159171
159330
|
hermes_version: api.hermesVersion,
|
|
159172
159331
|
detected_architecture: api.detectedArchitecture,
|
|
159332
|
+
...tookOver ? { took_over: tookOver } : {},
|
|
159173
159333
|
...warning ? { warning } : {},
|
|
159174
159334
|
...isLogicalKeyedDevice(api.deviceId) && !runtimeDevice ? { [RESULT_NOTE_KEY]: UNKNOWN_DEVICE_NOTE } : {}
|
|
159175
159335
|
};
|
|
@@ -159240,6 +159400,55 @@ async function readCommitTree(path59) {
|
|
|
159240
159400
|
return JSON.parse(json2);
|
|
159241
159401
|
}
|
|
159242
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
|
+
|
|
159243
159452
|
// ../tool-server/src/tools/profiler/react/react-profiler-stop.ts
|
|
159244
159453
|
function normalizeChangeDescription(raw) {
|
|
159245
159454
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -159309,6 +159518,15 @@ function flattenProfilingData(merged, displayNameById, fiberMeta) {
|
|
|
159309
159518
|
}
|
|
159310
159519
|
return { commits, totalCommits: flatCommitIndex, unattributedByCommit };
|
|
159311
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
|
+
}
|
|
159312
159530
|
var zodSchema46 = external_exports.object({
|
|
159313
159531
|
port: metroPortField,
|
|
159314
159532
|
device_id: external_exports.string().describe(
|
|
@@ -159323,8 +159541,11 @@ Stores the results for react-profiler-analyze or react-profiler-cpu-summary.
|
|
|
159323
159541
|
Call react-profiler-start first, then exercise the app, then call this.
|
|
159324
159542
|
Returns { duration_ms, sample_count, fiber_renders_captured, total_react_commits, hot_commit_indices }.
|
|
159325
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.
|
|
159326
|
-
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.`,
|
|
159327
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,
|
|
159328
159549
|
interaction: {
|
|
159329
159550
|
startedMsg: () => "Stopping React profiler",
|
|
159330
159551
|
completedMsg: () => "Stopped React profiler",
|
|
@@ -159333,12 +159554,18 @@ Fails if no active profiling session exists or the CDP connection was lost durin
|
|
|
159333
159554
|
capability: RN_ONLY_TOOL_CAPABILITY,
|
|
159334
159555
|
services: () => ({}),
|
|
159335
159556
|
async execute(_services, params) {
|
|
159336
|
-
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}`;
|
|
159337
159560
|
const snapshot = registry2.getSnapshot();
|
|
159338
159561
|
const entry = snapshot.services.get(psUrn);
|
|
159562
|
+
const lastRun = lastRunText(
|
|
159563
|
+
getCachedProfilerPaths(port, deviceId),
|
|
159564
|
+
getLostProfilerRun(port, deviceId)
|
|
159565
|
+
);
|
|
159339
159566
|
if (!entry || entry.state !== "RUNNING" /* RUNNING */) {
|
|
159340
159567
|
throw new FailureError(
|
|
159341
|
-
"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.",
|
|
159342
159569
|
{
|
|
159343
159570
|
error_code: FAILURE_CODES.REACT_PROFILER_NO_ACTIVE_SESSION,
|
|
159344
159571
|
failure_stage: "react_profiler_stop_session_lookup",
|
|
@@ -159349,6 +159576,7 @@ Fails if no active profiling session exists or the CDP connection was lost durin
|
|
|
159349
159576
|
}
|
|
159350
159577
|
const api = await registry2.resolveService(psUrn);
|
|
159351
159578
|
const cdp = api.cdp;
|
|
159579
|
+
if (api.stopping) throw stopInProgressError();
|
|
159352
159580
|
if (!api.cdp.isConnected()) {
|
|
159353
159581
|
api.profilingActive = false;
|
|
159354
159582
|
throw new FailureError(
|
|
@@ -159362,6 +159590,17 @@ Fails if no active profiling session exists or the CDP connection was lost durin
|
|
|
159362
159590
|
);
|
|
159363
159591
|
}
|
|
159364
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
|
+
}
|
|
159365
159604
|
throw new FailureError(
|
|
159366
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.",
|
|
159367
159606
|
{
|
|
@@ -159374,180 +159613,210 @@ Fails if no active profiling session exists or the CDP connection was lost durin
|
|
|
159374
159613
|
}
|
|
159375
159614
|
);
|
|
159376
159615
|
}
|
|
159377
|
-
|
|
159378
|
-
|
|
159379
|
-
|
|
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)";
|
|
159380
159626
|
throw new FailureError(
|
|
159381
|
-
|
|
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.`,
|
|
159382
159628
|
{
|
|
159383
|
-
error_code: FAILURE_CODES.
|
|
159384
|
-
failure_stage: "
|
|
159629
|
+
error_code: FAILURE_CODES.REACT_PROFILER_SESSION_TAKEN_OVER,
|
|
159630
|
+
failure_stage: "react_profiler_stop_taken_over",
|
|
159385
159631
|
failure_area: "tool_server",
|
|
159386
|
-
error_kind: "
|
|
159632
|
+
error_kind: "not_found"
|
|
159387
159633
|
}
|
|
159388
159634
|
);
|
|
159389
159635
|
}
|
|
159390
|
-
|
|
159391
|
-
|
|
159392
|
-
|
|
159393
|
-
|
|
159394
|
-
|
|
159395
|
-
|
|
159396
|
-
|
|
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",
|
|
159397
159685
|
failure_area: "tool_server",
|
|
159398
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 {
|
|
159399
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
|
|
159400
159724
|
);
|
|
159401
|
-
|
|
159402
|
-
|
|
159403
|
-
|
|
159404
|
-
|
|
159405
|
-
|
|
159406
|
-
|
|
159407
|
-
|
|
159408
|
-
|
|
159409
|
-
|
|
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`,
|
|
159410
159747
|
{
|
|
159411
|
-
|
|
159412
|
-
|
|
159413
|
-
|
|
159414
|
-
|
|
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
|
+
}
|
|
159415
159762
|
}
|
|
159416
159763
|
);
|
|
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
|
-
|
|
159442
|
-
|
|
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
|
+
}
|
|
159443
159811
|
}
|
|
159444
|
-
} catch {
|
|
159445
159812
|
}
|
|
159813
|
+
return response;
|
|
159814
|
+
} catch (err) {
|
|
159815
|
+
recordLostProfilerRun(api, "stop_failed");
|
|
159816
|
+
throw err;
|
|
159817
|
+
} finally {
|
|
159818
|
+
api.stopping = false;
|
|
159446
159819
|
}
|
|
159447
|
-
const {
|
|
159448
|
-
commits: allCommits,
|
|
159449
|
-
totalCommits,
|
|
159450
|
-
unattributedByCommit
|
|
159451
|
-
} = flattenProfilingData(merged, stopRead.displayNameById, fiberMeta);
|
|
159452
|
-
const commitHeat = /* @__PURE__ */ new Map();
|
|
159453
|
-
let anyCompilerOptimized = false;
|
|
159454
|
-
for (const c of allCommits) {
|
|
159455
|
-
const prev = commitHeat.get(c.commitIndex) ?? 0;
|
|
159456
|
-
if (c.commitDuration > prev) commitHeat.set(c.commitIndex, c.commitDuration);
|
|
159457
|
-
if (c.isCompilerOptimized) anyCompilerOptimized = true;
|
|
159458
|
-
}
|
|
159459
|
-
const ABSOLUTE_FLOOR_MS = 16;
|
|
159460
|
-
const interestingKeys = [...commitHeat.keys()].filter(
|
|
159461
|
-
(k) => (commitHeat.get(k) ?? 0) >= ABSOLUTE_FLOOR_MS
|
|
159462
|
-
);
|
|
159463
|
-
const hotSet = new Set(interestingKeys);
|
|
159464
|
-
const marginSet = /* @__PURE__ */ new Set();
|
|
159465
|
-
for (const ci of interestingKeys) {
|
|
159466
|
-
if (commitHeat.has(ci - 1) && !hotSet.has(ci - 1)) marginSet.add(ci - 1);
|
|
159467
|
-
if (commitHeat.has(ci + 1) && !hotSet.has(ci + 1)) marginSet.add(ci + 1);
|
|
159468
|
-
}
|
|
159469
|
-
const keepSet = /* @__PURE__ */ new Set([...hotSet, ...marginSet]);
|
|
159470
|
-
const filteredCommits = interestingKeys.length === 0 ? [] : allCommits.filter((c) => keepSet.has(c.commitIndex));
|
|
159471
|
-
api.anyCompilerOptimized = anyCompilerOptimized;
|
|
159472
|
-
api.totalReactCommits = totalCommits;
|
|
159473
|
-
api.hotCommitIndices = interestingKeys;
|
|
159474
|
-
const commitTree = { commits: filteredCommits, hookNames: /* @__PURE__ */ new Map() };
|
|
159475
|
-
const sessionTs = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, (m) => m === "T" ? "-" : "").slice(0, 15);
|
|
159476
|
-
const debugDir = await getDebugDir();
|
|
159477
|
-
const cpuProfilePath = await writeDumpCompact(
|
|
159478
|
-
debugDir,
|
|
159479
|
-
`react-profiler-${sessionTs}_cpu.json`,
|
|
159480
|
-
profile
|
|
159481
|
-
);
|
|
159482
|
-
const commitsPath = await writeDumpCompact(
|
|
159483
|
-
debugDir,
|
|
159484
|
-
`react-profiler-${sessionTs}_commits.json`,
|
|
159485
|
-
{
|
|
159486
|
-
commits: commitTree.commits,
|
|
159487
|
-
meta: {
|
|
159488
|
-
detectedArchitecture: api.detectedArchitecture,
|
|
159489
|
-
anyCompilerOptimized: api.anyCompilerOptimized,
|
|
159490
|
-
hotCommitIndices: api.hotCommitIndices,
|
|
159491
|
-
totalReactCommits: api.totalReactCommits,
|
|
159492
|
-
profileStartWallMs: api.profileStartWallMs,
|
|
159493
|
-
projectRoot: api.projectRoot,
|
|
159494
|
-
deviceId: api.deviceId,
|
|
159495
|
-
port: api.port,
|
|
159496
|
-
appName: api.appName,
|
|
159497
|
-
deviceName: api.deviceName,
|
|
159498
|
-
unattributedByCommit: unattributedByCommit.length > 0 ? unattributedByCommit : null
|
|
159499
|
-
}
|
|
159500
|
-
}
|
|
159501
|
-
);
|
|
159502
|
-
const sessionPaths = {
|
|
159503
|
-
sessionId: sessionTs,
|
|
159504
|
-
debugDir,
|
|
159505
|
-
cpuProfilePath,
|
|
159506
|
-
commitsPath,
|
|
159507
|
-
cpuSampleIndexPath: null,
|
|
159508
|
-
detectedArchitecture: api.detectedArchitecture,
|
|
159509
|
-
anyCompilerOptimized: api.anyCompilerOptimized,
|
|
159510
|
-
hotCommitIndices: api.hotCommitIndices,
|
|
159511
|
-
totalReactCommits: api.totalReactCommits
|
|
159512
|
-
};
|
|
159513
|
-
cacheProfilerPaths(api.port, sessionPaths, api.deviceId);
|
|
159514
|
-
api.sessionPaths = sessionPaths;
|
|
159515
|
-
api.sessionId = null;
|
|
159516
|
-
api.ownerToolServerPid = null;
|
|
159517
|
-
api.disposeSession();
|
|
159518
|
-
const duration_ms = (profile.endTime - profile.startTime) / 1e3;
|
|
159519
|
-
const response = {
|
|
159520
|
-
duration_ms,
|
|
159521
|
-
sample_count: profile.samples.length,
|
|
159522
|
-
fiber_renders_captured: allCommits.length
|
|
159523
|
-
};
|
|
159524
|
-
if (totalCommits > 0) {
|
|
159525
|
-
response["total_react_commits"] = totalCommits;
|
|
159526
|
-
response["hot_commit_indices"] = api.hotCommitIndices ?? [];
|
|
159527
|
-
response["any_compiler_optimized"] = api.anyCompilerOptimized ?? false;
|
|
159528
|
-
response["fiber_renders_analyzed"] = filteredCommits.length;
|
|
159529
|
-
const hotCount = api.hotCommitIndices?.length ?? 0;
|
|
159530
|
-
if (hotCount === 0) {
|
|
159531
|
-
response["selection_note"] = "All commits below 16ms \u2014 app appears smooth (all-clear)";
|
|
159532
|
-
} else if (hotCount < totalCommits) {
|
|
159533
|
-
response["selection_note"] = `${hotCount} of ${totalCommits} commits at \u226516ms absolute floor`;
|
|
159534
|
-
}
|
|
159535
|
-
if (unattributedByCommit.length > 0) {
|
|
159536
|
-
let totalMs = 0;
|
|
159537
|
-
let totalFibers = 0;
|
|
159538
|
-
for (const [, count2, ms] of unattributedByCommit) {
|
|
159539
|
-
totalFibers += count2;
|
|
159540
|
-
totalMs += ms;
|
|
159541
|
-
}
|
|
159542
|
-
response["unattributed_ms"] = Math.round(totalMs * 100) / 100;
|
|
159543
|
-
response["unattributed_fiber_count"] = totalFibers;
|
|
159544
|
-
response["unattributed_commit_count"] = unattributedByCommit.length;
|
|
159545
|
-
if (stopRead.displayNameApiAvailable === false) {
|
|
159546
|
-
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.";
|
|
159547
|
-
}
|
|
159548
|
-
}
|
|
159549
|
-
}
|
|
159550
|
-
return response;
|
|
159551
159820
|
}
|
|
159552
159821
|
};
|
|
159553
159822
|
}
|
|
@@ -159563,7 +159832,7 @@ var zodSchema47 = external_exports.object({
|
|
|
159563
159832
|
function createReactProfilerStatusTool(registry2) {
|
|
159564
159833
|
return {
|
|
159565
159834
|
id: "react-profiler-status",
|
|
159566
|
-
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.`,
|
|
159567
159836
|
zodSchema: zodSchema47,
|
|
159568
159837
|
readOnly: true,
|
|
159569
159838
|
interaction: {
|
|
@@ -159575,7 +159844,19 @@ function createReactProfilerStatusTool(registry2) {
|
|
|
159575
159844
|
capability: RN_ONLY_TOOL_CAPABILITY,
|
|
159576
159845
|
services: () => ({}),
|
|
159577
159846
|
async execute(_services, params) {
|
|
159578
|
-
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
|
+
};
|
|
159579
159860
|
const noRuntime = (note) => ({
|
|
159580
159861
|
hook_exists: false,
|
|
159581
159862
|
renderer_interface_found: false,
|
|
@@ -159583,7 +159864,8 @@ function createReactProfilerStatusTool(registry2) {
|
|
|
159583
159864
|
current_session_id: null,
|
|
159584
159865
|
current_owner: null,
|
|
159585
159866
|
session_status: "no_react_runtime",
|
|
159586
|
-
note
|
|
159867
|
+
note: withLastRun(note),
|
|
159868
|
+
...lastRun
|
|
159587
159869
|
});
|
|
159588
159870
|
let api;
|
|
159589
159871
|
try {
|
|
@@ -159593,6 +159875,16 @@ function createReactProfilerStatusTool(registry2) {
|
|
|
159593
159875
|
"Unable to resolve a React profiler session \u2014 the Hermes runtime may not be reachable. Reconnect the debugger and retry."
|
|
159594
159876
|
);
|
|
159595
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();
|
|
159596
159888
|
const cdp = api.cdp;
|
|
159597
159889
|
if (!cdp.isConnected()) {
|
|
159598
159890
|
return noRuntime(
|
|
@@ -159617,61 +159909,119 @@ function createReactProfilerStatusTool(registry2) {
|
|
|
159617
159909
|
);
|
|
159618
159910
|
}
|
|
159619
159911
|
const state3 = JSON.parse(stateJson);
|
|
159912
|
+
if (api.stopping) return stopInProgress2();
|
|
159620
159913
|
if (!state3.hookExists) {
|
|
159621
|
-
return
|
|
159622
|
-
|
|
159623
|
-
|
|
159624
|
-
is_running: false,
|
|
159625
|
-
current_session_id: null,
|
|
159626
|
-
current_owner: null,
|
|
159627
|
-
session_status: "no_react_runtime",
|
|
159628
|
-
note: "React DevTools hook not attached \u2014 debugger may be disconnected or the app isn't a dev build."
|
|
159629
|
-
};
|
|
159914
|
+
return noRuntime(
|
|
159915
|
+
"React DevTools hook not attached \u2014 debugger may be disconnected or the app isn't a dev build."
|
|
159916
|
+
);
|
|
159630
159917
|
}
|
|
159631
159918
|
if (!("rendererInterfaceFound" in state3) || !state3.rendererInterfaceFound) {
|
|
159632
159919
|
return {
|
|
159633
|
-
|
|
159634
|
-
|
|
159635
|
-
|
|
159636
|
-
|
|
159637
|
-
current_owner: null,
|
|
159638
|
-
session_status: "no_react_runtime",
|
|
159639
|
-
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
|
|
159640
159924
|
};
|
|
159641
159925
|
}
|
|
159642
159926
|
const owner = state3.owner;
|
|
159643
|
-
const
|
|
159644
|
-
|
|
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
|
+
}
|
|
159645
159982
|
return {
|
|
159646
|
-
|
|
159647
|
-
|
|
159648
|
-
|
|
159649
|
-
|
|
159650
|
-
|
|
159651
|
-
|
|
159652
|
-
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
|
|
159653
159989
|
};
|
|
159654
159990
|
}
|
|
159655
|
-
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";
|
|
159656
160006
|
return {
|
|
159657
|
-
|
|
159658
|
-
|
|
159659
|
-
|
|
159660
|
-
|
|
159661
|
-
|
|
159662
|
-
|
|
159663
|
-
|
|
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
|
|
159664
160019
|
};
|
|
159665
160020
|
}
|
|
159666
|
-
const isMine = api.sessionId !== null && owner.sessionId === api.sessionId;
|
|
159667
160021
|
return {
|
|
159668
|
-
|
|
159669
|
-
|
|
159670
|
-
|
|
159671
|
-
current_session_id: owner.sessionId,
|
|
159672
|
-
current_owner: owner,
|
|
159673
|
-
session_status: isMine ? "active" : "taken_over",
|
|
159674
|
-
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."
|
|
159675
160025
|
};
|
|
159676
160026
|
}
|
|
159677
160027
|
};
|
|
@@ -159718,13 +160068,6 @@ async function detectSessionContext(input2) {
|
|
|
159718
160068
|
reactCompilerEnabled = true;
|
|
159719
160069
|
}
|
|
159720
160070
|
}
|
|
159721
|
-
let strictModeEnabled = false;
|
|
159722
|
-
const indexJs = await readFileSafe((0, import_path3.join)(projectRoot, "index.js"));
|
|
159723
|
-
const indexTs = await readFileSafe((0, import_path3.join)(projectRoot, "index.ts"));
|
|
159724
|
-
const indexSource = (indexJs ?? "") + (indexTs ?? "");
|
|
159725
|
-
if (indexSource.includes("<StrictMode>") || indexSource.includes("StrictMode")) {
|
|
159726
|
-
strictModeEnabled = true;
|
|
159727
|
-
}
|
|
159728
160071
|
let buildMode = "dev";
|
|
159729
160072
|
if (input2.flamegraph) {
|
|
159730
160073
|
for (const node2 of input2.flamegraph.nodes) {
|
|
@@ -159764,7 +160107,6 @@ async function detectSessionContext(input2) {
|
|
|
159764
160107
|
}
|
|
159765
160108
|
return {
|
|
159766
160109
|
reactCompilerEnabled,
|
|
159767
|
-
strictModeEnabled,
|
|
159768
160110
|
buildMode,
|
|
159769
160111
|
rnArchitecture,
|
|
159770
160112
|
projectRoot,
|
|
@@ -160315,14 +160657,12 @@ function bestRootCause(votes, hookTypeNames) {
|
|
|
160315
160657
|
}
|
|
160316
160658
|
function enrich(input2) {
|
|
160317
160659
|
const { sessionContext } = input2;
|
|
160318
|
-
const strictMode = sessionContext.strictModeEnabled;
|
|
160319
160660
|
const components = /* @__PURE__ */ new Map();
|
|
160320
160661
|
for (const [name, acc] of input2.components) {
|
|
160321
160662
|
const n = acc.n;
|
|
160322
160663
|
if (n === 0) continue;
|
|
160323
160664
|
const mean = acc.sum / n;
|
|
160324
|
-
const totalRenderMs =
|
|
160325
|
-
const normalizedRenderCount = strictMode ? Math.ceil(n / 2) : n;
|
|
160665
|
+
const totalRenderMs = acc.sum;
|
|
160326
160666
|
const dr = dominantReason(acc.reasonHistogram);
|
|
160327
160667
|
const dp = dr === "parent" ? topParent(acc.parentFreq) : void 0;
|
|
160328
160668
|
const isCompilerOptimized = acc.compilerOptimizedCount > n / 2;
|
|
@@ -160330,7 +160670,6 @@ function enrich(input2) {
|
|
|
160330
160670
|
const enriched = {
|
|
160331
160671
|
name,
|
|
160332
160672
|
n,
|
|
160333
|
-
normalizedRenderCount,
|
|
160334
160673
|
mean,
|
|
160335
160674
|
min: acc.min === Infinity ? 0 : acc.min,
|
|
160336
160675
|
max: acc.max === -Infinity ? 0 : acc.max,
|
|
@@ -160398,7 +160737,7 @@ var ACTIONABLE_REASONS = /* @__PURE__ */ new Set([
|
|
|
160398
160737
|
]);
|
|
160399
160738
|
var MAX_FINDINGS = 20;
|
|
160400
160739
|
var PARETO_THRESHOLD_PCT = 5e-3;
|
|
160401
|
-
var
|
|
160740
|
+
var MIN_RENDERS = 3;
|
|
160402
160741
|
var MIN_MAX_DURATION_MS = 30;
|
|
160403
160742
|
function rank(input2) {
|
|
160404
160743
|
const { sessionContext, recordingMs } = input2;
|
|
@@ -160407,7 +160746,7 @@ function rank(input2) {
|
|
|
160407
160746
|
if (comp.isAnimated) continue;
|
|
160408
160747
|
if (comp.isRecyclerChild) continue;
|
|
160409
160748
|
if (!ACTIONABLE_REASONS.has(comp.dominantReason)) continue;
|
|
160410
|
-
const passesRenderCount = comp.
|
|
160749
|
+
const passesRenderCount = comp.n >= MIN_RENDERS;
|
|
160411
160750
|
const passesMaxDuration = comp.max >= MIN_MAX_DURATION_MS;
|
|
160412
160751
|
if (!passesRenderCount && !passesMaxDuration) continue;
|
|
160413
160752
|
candidates.push({ name, comp });
|
|
@@ -160420,7 +160759,7 @@ function rank(input2) {
|
|
|
160420
160759
|
const topChangedHookNames = comp.hookTypeNames !== void 0 ? comp.topChangedHooks.map((idx) => comp.hookTypeNames[idx] ?? `hook[${idx}]`) : [];
|
|
160421
160760
|
const finding = {
|
|
160422
160761
|
component: name,
|
|
160423
|
-
renders: comp.
|
|
160762
|
+
renders: comp.n,
|
|
160424
160763
|
totalMs: Math.round(comp.totalRenderMs * 10) / 10,
|
|
160425
160764
|
avgMs: Math.round(comp.mean * 100) / 100,
|
|
160426
160765
|
maxMs: Math.round(comp.max * 100) / 100,
|
|
@@ -160429,7 +160768,7 @@ function rank(input2) {
|
|
|
160429
160768
|
topChangedHookNames
|
|
160430
160769
|
};
|
|
160431
160770
|
if (comp.isCompilerOptimized) finding.isCompilerOptimized = true;
|
|
160432
|
-
if (sessionContext.reactCompilerEnabled && !comp.isCompilerOptimized && comp.
|
|
160771
|
+
if (sessionContext.reactCompilerEnabled && !comp.isCompilerOptimized && comp.n > 5) {
|
|
160433
160772
|
finding.compilerBailoutSuspected = true;
|
|
160434
160773
|
}
|
|
160435
160774
|
if (comp.parentTrigger !== void 0) finding.parentTrigger = comp.parentTrigger;
|
|
@@ -161624,13 +161963,14 @@ Fails if the React DevTools hook is not present in the runtime or the app is not
|
|
|
161624
161963
|
// RN-only: queries the React DevTools backend hook on the live runtime.
|
|
161625
161964
|
capability: RN_ONLY_TOOL_CAPABILITY,
|
|
161626
161965
|
services: (params) => ({
|
|
161627
|
-
|
|
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)}`
|
|
161628
161968
|
}),
|
|
161629
161969
|
async execute(services, params) {
|
|
161630
161970
|
const api = services.profilerSession;
|
|
161631
161971
|
const cdp = api.cdp;
|
|
161632
|
-
if (api.profilingActive && api.ownerToolServerPid === process.pid) {
|
|
161633
|
-
await cdp.evaluate(
|
|
161972
|
+
if (api.profilingActive && api.sessionId && api.ownerToolServerPid === process.pid) {
|
|
161973
|
+
await cdp.evaluate(buildHeartbeatScript(api.sessionId)).catch(() => {
|
|
161634
161974
|
});
|
|
161635
161975
|
}
|
|
161636
161976
|
async function evalRenders() {
|
|
@@ -161801,13 +162141,14 @@ Fails if the React DevTools hook is not present or no fiber roots have been comm
|
|
|
161801
162141
|
// RN-only: walks the fiber tree via the React DevTools backend hook.
|
|
161802
162142
|
capability: RN_ONLY_TOOL_CAPABILITY,
|
|
161803
162143
|
services: (params) => ({
|
|
161804
|
-
|
|
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)}`
|
|
161805
162146
|
}),
|
|
161806
162147
|
async execute(services, params) {
|
|
161807
162148
|
const api = services.profilerSession;
|
|
161808
162149
|
const cdp = api.cdp;
|
|
161809
|
-
if (api.profilingActive && api.ownerToolServerPid === process.pid) {
|
|
161810
|
-
await cdp.evaluate(
|
|
162150
|
+
if (api.profilingActive && api.sessionId && api.ownerToolServerPid === process.pid) {
|
|
162151
|
+
await cdp.evaluate(buildHeartbeatScript(api.sessionId)).catch(() => {
|
|
161811
162152
|
});
|
|
161812
162153
|
}
|
|
161813
162154
|
const script = buildFiberTreeScript(params.max_depth, params.filter ?? "");
|
|
@@ -169041,7 +169382,8 @@ async function loadReactSession(debugDir, sessionId, port, deviceId) {
|
|
|
169041
169382
|
detectedArchitecture,
|
|
169042
169383
|
anyCompilerOptimized,
|
|
169043
169384
|
hotCommitIndices,
|
|
169044
|
-
totalReactCommits
|
|
169385
|
+
totalReactCommits,
|
|
169386
|
+
loadedFromDisk: true
|
|
169045
169387
|
};
|
|
169046
169388
|
cacheProfilerPaths(port, sessionPaths, deviceId);
|
|
169047
169389
|
const lines = [
|