@swmansion/argent 0.20.1-next.8 → 0.20.1-next.9

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.
Files changed (2) hide show
  1. package/dist/tool-server.cjs +151 -61
  2. package/package.json +1 -1
@@ -133798,89 +133798,130 @@ var ARGENT_PROFILER_PREFIX = "__argent_";
133798
133798
  function isArgentProfilerFunction(name) {
133799
133799
  return name.startsWith(ARGENT_PROFILER_PREFIX);
133800
133800
  }
133801
- function buildCpuSampleIndex(cpuProfile, firstCommitTimestampMs) {
133801
+ function buildCpuSampleIndex(cpuProfile) {
133802
133802
  const { nodes, samples, timeDeltas, startTime, endTime } = cpuProfile;
133803
133803
  const nodeMap = /* @__PURE__ */ new Map();
133804
133804
  for (const node of nodes) {
133805
133805
  nodeMap.set(node.id, node);
133806
133806
  }
133807
- const cpuStartMs = startTime / 1e3;
133808
- let clockOffsetMs = 0;
133809
- if (firstCommitTimestampMs !== null && firstCommitTimestampMs > 0) {
133810
- const diff = firstCommitTimestampMs - cpuStartMs;
133811
- if (Math.abs(diff) > 1e3) {
133812
- clockOffsetMs = diff;
133813
- }
133814
- }
133815
133807
  const timestampsMs = new Float64Array(samples.length);
133816
- let accumulatedUs = startTime;
133808
+ const intervalStartsMs = new Float64Array(samples.length);
133809
+ let accumulatedUs = 0;
133817
133810
  for (let i = 0; i < samples.length; i++) {
133818
- accumulatedUs += timeDeltas[i] ?? 0;
133819
- timestampsMs[i] = accumulatedUs / 1e3 + clockOffsetMs;
133811
+ const raw = timeDeltas[i];
133812
+ const delta = Number.isFinite(raw) && (raw ?? 0) >= 0 ? raw : 0;
133813
+ intervalStartsMs[i] = accumulatedUs / 1e3;
133814
+ accumulatedUs += delta;
133815
+ timestampsMs[i] = accumulatedUs / 1e3;
133820
133816
  }
133821
133817
  return {
133818
+ childToParent: buildChildToParent(nodeMap),
133822
133819
  timestampsMs,
133820
+ intervalStartsMs,
133823
133821
  sampleNodeIds: samples,
133824
133822
  nodeMap,
133825
133823
  durationMs: (endTime - startTime) / 1e3
133826
133824
  };
133827
133825
  }
133826
+ var IDLE_FRAME_NAMES = /* @__PURE__ */ new Set(["(idle)", "(program)", "(root)", "[idle]", "[root]"]);
133827
+ function isIdleFrame(name) {
133828
+ return !name || IDLE_FRAME_NAMES.has(name);
133829
+ }
133828
133830
  function queryCpuWindow(index, startMs, endMs, topN2 = 5) {
133829
- const { timestampsMs, sampleNodeIds, nodeMap } = index;
133831
+ const { timestampsMs, intervalStartsMs, sampleNodeIds, nodeMap } = index;
133832
+ const n = timestampsMs.length;
133833
+ const sampleRangeMs = {
133834
+ start: n > 0 ? intervalStartsMs[0] : 0,
133835
+ end: n > 0 ? timestampsMs[n - 1] : 0
133836
+ };
133837
+ const empty2 = {
133838
+ hotspots: [],
133839
+ samplesInWindow: 0,
133840
+ coveredMs: 0,
133841
+ idleMs: 0,
133842
+ sampleRangeMs,
133843
+ medianIntervalMs: 0,
133844
+ maxIntervalMs: 0
133845
+ };
133846
+ if (n === 0) return empty2;
133830
133847
  let lo = 0;
133831
- let hi = timestampsMs.length;
133848
+ let hi = n;
133832
133849
  while (lo < hi) {
133833
133850
  const mid = lo + hi >>> 1;
133834
133851
  if (timestampsMs[mid] < startMs) lo = mid + 1;
133835
133852
  else hi = mid;
133836
133853
  }
133837
- const selfHits = /* @__PURE__ */ new Map();
133838
- let totalSamples = 0;
133839
- for (let i = lo; i < timestampsMs.length; i++) {
133840
- if (timestampsMs[i] > endMs) break;
133854
+ const selfMsByNode = /* @__PURE__ */ new Map();
133855
+ const intervals = [];
133856
+ let samplesInWindow = 0;
133857
+ let coveredMs = 0;
133858
+ let idleMs = 0;
133859
+ let maxIntervalMs = 0;
133860
+ for (let i = lo; i < n; i++) {
133861
+ const from2 = intervalStartsMs[i];
133862
+ if (from2 > endMs) break;
133863
+ const overlap = Math.min(endMs, timestampsMs[i]) - Math.max(startMs, from2);
133864
+ if (overlap <= 0) continue;
133865
+ samplesInWindow++;
133866
+ coveredMs += overlap;
133867
+ intervals.push(timestampsMs[i] - from2);
133868
+ if (timestampsMs[i] - from2 > maxIntervalMs) maxIntervalMs = timestampsMs[i] - from2;
133841
133869
  const nodeId = sampleNodeIds[i];
133842
- selfHits.set(nodeId, (selfHits.get(nodeId) ?? 0) + 1);
133843
- totalSamples++;
133844
- }
133845
- if (totalSamples === 0) return [];
133846
- const windowDurationMs = endMs - startMs;
133847
- const avgIntervalMs = totalSamples > 1 ? windowDurationMs / totalSamples : 1;
133848
- const childToParent = /* @__PURE__ */ new Map();
133849
- for (const node of nodeMap.values()) {
133850
- for (const childId of node.children ?? []) {
133851
- childToParent.set(childId, node.id);
133870
+ const node = nodeMap.get(nodeId);
133871
+ if (isIdleFrame(node?.callFrame.functionName)) {
133872
+ idleMs += overlap;
133873
+ continue;
133852
133874
  }
133875
+ selfMsByNode.set(nodeId, (selfMsByNode.get(nodeId) ?? 0) + overlap);
133853
133876
  }
133854
- const totalHits = /* @__PURE__ */ new Map();
133855
- for (const [nodeId, hits] of selfHits) {
133856
- totalHits.set(nodeId, (totalHits.get(nodeId) ?? 0) + hits);
133877
+ if (samplesInWindow === 0) return { ...empty2, sampleRangeMs };
133878
+ intervals.sort((a, b) => a - b);
133879
+ const medianIntervalMs = intervals[Math.floor(intervals.length / 2)] ?? 0;
133880
+ const childToParent = index.childToParent ?? buildChildToParent(nodeMap);
133881
+ const totalMsByNode = /* @__PURE__ */ new Map();
133882
+ for (const [nodeId, ms] of selfMsByNode) {
133883
+ totalMsByNode.set(nodeId, (totalMsByNode.get(nodeId) ?? 0) + ms);
133857
133884
  let current = nodeId;
133885
+ const seen = /* @__PURE__ */ new Set([current]);
133858
133886
  while (childToParent.has(current)) {
133859
133887
  const parent = childToParent.get(current);
133860
- totalHits.set(parent, (totalHits.get(parent) ?? 0) + hits);
133888
+ if (seen.has(parent)) break;
133889
+ seen.add(parent);
133890
+ totalMsByNode.set(parent, (totalMsByNode.get(parent) ?? 0) + ms);
133861
133891
  current = parent;
133862
133892
  }
133863
133893
  }
133864
133894
  const entries = [];
133865
- for (const [nodeId, hits] of selfHits) {
133895
+ for (const [nodeId, ms] of selfMsByNode) {
133866
133896
  const node = nodeMap.get(nodeId);
133867
133897
  if (!node) continue;
133868
133898
  const name = node.callFrame.functionName;
133869
- if (!name || name === "(idle)" || name === "(program)" || name === "(root)" || name === "[idle]" || name === "[root]")
133870
- continue;
133871
133899
  if (isArgentProfilerFunction(name)) continue;
133872
- const selfMs = Math.round(hits * avgIntervalMs * 100) / 100;
133873
- const totalMs = Math.round((totalHits.get(nodeId) ?? hits) * avgIntervalMs * 100) / 100;
133874
133900
  entries.push({
133875
133901
  name,
133876
- selfMs,
133877
- totalMs,
133902
+ selfMs: Math.round(ms * 100) / 100,
133903
+ totalMs: Math.round((totalMsByNode.get(nodeId) ?? ms) * 100) / 100,
133878
133904
  url: node.callFrame.url || void 0,
133879
133905
  lineNumber: node.callFrame.lineNumber >= 0 ? node.callFrame.lineNumber : void 0
133880
133906
  });
133881
133907
  }
133882
133908
  entries.sort((a, b) => b.selfMs - a.selfMs);
133883
- return entries.slice(0, topN2);
133909
+ return {
133910
+ hotspots: entries.slice(0, topN2),
133911
+ samplesInWindow,
133912
+ coveredMs,
133913
+ idleMs,
133914
+ sampleRangeMs,
133915
+ medianIntervalMs,
133916
+ maxIntervalMs
133917
+ };
133918
+ }
133919
+ function buildChildToParent(nodeMap) {
133920
+ const childToParent = /* @__PURE__ */ new Map();
133921
+ for (const node of nodeMap.values()) {
133922
+ for (const childId of node.children ?? []) childToParent.set(childId, node.id);
133923
+ }
133924
+ return childToParent;
133884
133925
  }
133885
133926
  function correlateCpuWithCommits(summaries, index, topNPerCommit = 5) {
133886
133927
  if (!index) return summaries;
@@ -133888,28 +133929,35 @@ function correlateCpuWithCommits(summaries, index, topNPerCommit = 5) {
133888
133929
  if (summary.isMargin) return summary;
133889
133930
  const startMs = summary.timestampMs;
133890
133931
  const endMs = summary.timestampMs + summary.totalRenderMs;
133891
- const hotspots = queryCpuWindow(index, startMs, endMs, topNPerCommit);
133932
+ const { hotspots } = queryCpuWindow(index, startMs, endMs, topNPerCommit);
133892
133933
  if (hotspots.length === 0) return summary;
133893
133934
  return { ...summary, cpuHotspots: hotspots };
133894
133935
  });
133895
133936
  }
133896
133937
  function serializeCpuSampleIndex(index) {
133897
133938
  return {
133939
+ version: 2,
133898
133940
  timestampsMs: Array.from(index.timestampsMs),
133941
+ intervalStartsMs: Array.from(index.intervalStartsMs),
133899
133942
  sampleNodeIds: index.sampleNodeIds,
133900
133943
  nodes: [...index.nodeMap.values()],
133901
133944
  durationMs: index.durationMs
133902
133945
  };
133903
133946
  }
133904
133947
  function deserializeCpuSampleIndex(raw) {
133948
+ if (raw?.version !== 2 || !Array.isArray(raw.timestampsMs) || !Array.isArray(raw.nodes)) {
133949
+ throw new Error("unsupported CPU sample index format");
133950
+ }
133905
133951
  const nodeMap = /* @__PURE__ */ new Map();
133906
133952
  for (const node of raw.nodes) {
133907
133953
  nodeMap.set(node.id, node);
133908
133954
  }
133909
133955
  return {
133910
133956
  timestampsMs: new Float64Array(raw.timestampsMs),
133957
+ intervalStartsMs: new Float64Array(raw.intervalStartsMs ?? []),
133911
133958
  sampleNodeIds: raw.sampleNodeIds,
133912
133959
  nodeMap,
133960
+ childToParent: buildChildToParent(nodeMap),
133913
133961
  durationMs: raw.durationMs
133914
133962
  };
133915
133963
  }
@@ -134212,8 +134260,7 @@ async function runPipeline(input, options) {
134212
134260
  hotCommitIndices,
134213
134261
  input.sessionMeta.unattributedByCommit
134214
134262
  );
134215
- const firstCommitTs = preprocessed.length > 0 ? preprocessed[0].timestamp : null;
134216
- const cpuSampleIndex = input.flamegraph ? buildCpuSampleIndex(input.flamegraph, firstCommitTs) : null;
134263
+ const cpuSampleIndex = input.flamegraph ? buildCpuSampleIndex(input.flamegraph) : null;
134217
134264
  const hotCommitSummaries = correlateCpuWithCommits(rawHotCommitSummaries, cpuSampleIndex);
134218
134265
  const preprocessedCommitTree = { ...input.commitTree, commits: preprocessed };
134219
134266
  const reduceOutput = reduce(
@@ -140619,8 +140666,12 @@ init_zod();
140619
140666
  init_src();
140620
140667
  var import_fs18 = require("fs");
140621
140668
  var timeWindowSchema = external_exports.object({
140622
- start: external_exports.coerce.number().describe("Start of window in ms (performance.now clock)"),
140623
- end: external_exports.coerce.number().describe("End of window in ms (performance.now clock)")
140669
+ start: external_exports.coerce.number().describe(
140670
+ "Start of window in ms since profiling started \u2014 the same clock profiler-commit-query prints"
140671
+ ),
140672
+ end: external_exports.coerce.number().describe(
140673
+ "End of window in ms since profiling started \u2014 the same clock profiler-commit-query prints"
140674
+ )
140624
140675
  });
140625
140676
  var zodSchema53 = external_exports.object({
140626
140677
  port: external_exports.coerce.number().default(8081).describe("Metro server port"),
@@ -140630,7 +140681,9 @@ var zodSchema53 = external_exports.object({
140630
140681
  mode: external_exports.enum(["top_functions", "time_window", "call_tree", "component_cpu"]).describe(
140631
140682
  "Query mode: top_functions (global hotspots), time_window (CPU in a time range), call_tree (callers/callees of a function), component_cpu (CPU during a component's commits)"
140632
140683
  ),
140633
- time_window_ms: timeWindowSchema.optional().describe("Time window filter for time_window mode (ms, performance.now clock)"),
140684
+ time_window_ms: timeWindowSchema.optional().describe(
140685
+ "Time window filter for time_window mode (ms since profiling started \u2014 the same clock profiler-commit-query prints)"
140686
+ ),
140634
140687
  component_name: external_exports.string().optional().describe("Component name for component_cpu mode"),
140635
140688
  function_name: external_exports.string().optional().describe("Function name for call_tree mode"),
140636
140689
  top_n: external_exports.coerce.number().int().positive().default(15).describe("Number of results to return (default 15)"),
@@ -140663,31 +140716,60 @@ async function getIndex(sessionPaths) {
140663
140716
  }
140664
140717
  const cpuProfile = await readCpuProfile(sessionPaths.cpuProfilePath);
140665
140718
  let commitTree = null;
140666
- let firstCommitTs = null;
140667
140719
  if (sessionPaths.commitsPath) {
140668
140720
  const onDisk = await readCommitTree(sessionPaths.commitsPath);
140669
140721
  commitTree = { commits: onDisk.commits };
140670
- firstCommitTs = onDisk.commits[0]?.timestamp ?? null;
140671
140722
  }
140672
- return { index: buildCpuSampleIndex(cpuProfile, firstCommitTs), commitTree };
140723
+ return { index: buildCpuSampleIndex(cpuProfile), commitTree };
140724
+ }
140725
+ function explainEmptyWindow(res, startMs, endMs) {
140726
+ const range = `${res.sampleRangeMs.start.toFixed(1)}\u2013${res.sampleRangeMs.end.toFixed(1)}ms`;
140727
+ const window2 = `${startMs.toFixed(1)}\u2013${endMs.toFixed(1)}ms`;
140728
+ if (res.sampleRangeMs.end === 0 && res.samplesInWindow === 0) {
140729
+ return "_The CPU profile contains no samples. Sampling produced no data for this session \u2014 that is a capture failure, not a measurement of idleness._";
140730
+ }
140731
+ if (endMs < res.sampleRangeMs.start || startMs > res.sampleRangeMs.end) {
140732
+ return `_No CPU samples exist in ${window2} \u2014 that is outside the recorded sample range (${range}). This is a coverage gap, not a measurement: nothing can be concluded about CPU cost here. Sample times are ms since profiling started, the same clock \`profiler-commit-query\` prints._`;
140733
+ }
140734
+ if (res.samplesInWindow > 0) {
140735
+ return `_${res.samplesInWindow} sample(s) covering ${res.coveredMs.toFixed(1)}ms fell inside ${window2}, and all of them were idle \u2014 the JS thread was not executing during this window. Native or UI-thread work would not appear here; use \`native-profiler-start\` for that._`;
140736
+ }
140737
+ return `_No CPU samples fell inside ${window2} (${(endMs - startMs).toFixed(1)}ms wide), although it lies within the recorded range (${range}). The sampler runs roughly every ${res.medianIntervalMs > 0 ? res.medianIntervalMs.toFixed(1) : "13"}ms, so a window this narrow can contain none at all. **Absence of samples is not evidence that this commit was cheap.** Widen the window, or use \`mode=component_cpu\`._`;
140738
+ }
140739
+ function coverageNote(res, startMs, endMs) {
140740
+ const widthMs = endMs - startMs;
140741
+ const lines = [
140742
+ `**Window:** ${startMs.toFixed(1)}ms \u2192 ${endMs.toFixed(1)}ms (${widthMs.toFixed(1)}ms)`,
140743
+ `**Samples:** ${res.samplesInWindow} covering ${res.coveredMs.toFixed(1)}ms` + (res.idleMs > 0 ? `, of which ${res.idleMs.toFixed(1)}ms idle` : "") + ` \u2014 sampling interval ~${res.medianIntervalMs.toFixed(1)}ms. Self-times sum to sampled coverage, not to the window width.`
140744
+ ];
140745
+ if (widthMs > 0 && widthMs < 3 * res.medianIntervalMs) {
140746
+ lines.push(
140747
+ `> This window is narrower than ~3 sampling intervals, so every figure carries \xB11 sample (\u2248${res.medianIntervalMs.toFixed(1)}ms).`
140748
+ );
140749
+ }
140750
+ if (res.maxIntervalMs > 50 && res.maxIntervalMs > 5 * res.medianIntervalMs) {
140751
+ lines.push(
140752
+ `> The sampler stalled for ${res.maxIntervalMs.toFixed(1)}ms inside this window; that whole gap is attributed to whichever function was caught by the sample that ended it.`
140753
+ );
140754
+ }
140755
+ return lines.join("\n\n");
140673
140756
  }
140674
140757
  function renderTopFunctions(index, topN2, startMs, endMs) {
140675
- const windowStart = startMs ?? index.timestampsMs[0];
140676
- const windowEnd = endMs ?? index.timestampsMs[index.timestampsMs.length - 1];
140677
- const hotspots = queryCpuWindow(index, windowStart, windowEnd, topN2);
140678
- if (hotspots.length === 0) return "_No CPU hotspots found in the specified range._";
140758
+ const windowStart = startMs ?? index.intervalStartsMs[0] ?? 0;
140759
+ const windowEnd = endMs ?? index.timestampsMs[index.timestampsMs.length - 1] ?? 0;
140760
+ const res = queryCpuWindow(index, windowStart, windowEnd, topN2);
140761
+ if (res.hotspots.length === 0) return explainEmptyWindow(res, windowStart, windowEnd);
140679
140762
  const header = "| Function | Self (ms) | Total (ms) | Location |";
140680
140763
  const sep7 = "|---|---|---|---|";
140681
- const rows = hotspots.map((hs) => {
140764
+ const rows = res.hotspots.map((hs) => {
140682
140765
  const loc = hs.url ? `${shortenUrl(hs.url)}${hs.lineNumber != null ? `:${hs.lineNumber}` : ""}` : "\u2014";
140683
140766
  return `| \`${hs.name}\` | ${hs.selfMs} | ${hs.totalMs} | ${loc} |`;
140684
140767
  });
140685
- const rangeNote = startMs != null ? `**Window:** ${startMs.toFixed(1)}ms \u2192 ${endMs.toFixed(1)}ms
140686
-
140687
- ` : "";
140688
140768
  return `## CPU Hotspots
140689
140769
 
140690
- ${rangeNote}${header}
140770
+ ${coverageNote(res, windowStart, windowEnd)}
140771
+
140772
+ ${header}
140691
140773
  ${sep7}
140692
140774
  ${rows.join("\n")}`;
140693
140775
  }
@@ -140817,7 +140899,7 @@ function renderComponentCpu(index, commitTree, componentName, topN2) {
140817
140899
  }
140818
140900
  const aggregated = /* @__PURE__ */ new Map();
140819
140901
  for (const window2 of commitWindows.values()) {
140820
- const hotspots = queryCpuWindow(index, window2.start, window2.end, 50);
140902
+ const { hotspots } = queryCpuWindow(index, window2.start, window2.end, 50);
140821
140903
  for (const hs of hotspots) {
140822
140904
  const existing = aggregated.get(hs.name);
140823
140905
  if (existing) {
@@ -140871,6 +140953,10 @@ Requires react-profiler-stop (and ideally react-profiler-analyze) to have been c
140871
140953
  Modes:
140872
140954
  - top_functions: Global CPU hotspots ranked by self-time. Optional time_window_ms to filter.
140873
140955
  - time_window: CPU breakdown for a specific time range (e.g. during a slow commit or hang).
140956
+
140957
+ Self-times are the summed sampling intervals of the samples that landed in the window, so they
140958
+ measure sampled coverage rather than the window's width and do not change if you widen the query.
140959
+ Every table states how many samples it covers and how much of that was idle.
140874
140960
  - call_tree: For a given function_name, show its callees and optionally callers.
140875
140961
  - component_cpu: For a given component_name, aggregate CPU activity across all its commits.
140876
140962
  Use when investigating JS CPU hotspots or correlating CPU cost with specific components.
@@ -140956,8 +141042,12 @@ Fails if no CPU profile is stored \u2014 run react-profiler-stop first.`,
140956
141042
  init_zod();
140957
141043
  init_src();
140958
141044
  var timeRangeSchema = external_exports.object({
140959
- start: external_exports.coerce.number().describe("Start of range in ms (performance.now clock)"),
140960
- end: external_exports.coerce.number().describe("End of range in ms (performance.now clock)")
141045
+ start: external_exports.coerce.number().describe(
141046
+ "Start of range in ms since profiling started \u2014 the same clock profiler-commit-query prints"
141047
+ ),
141048
+ end: external_exports.coerce.number().describe(
141049
+ "End of range in ms since profiling started \u2014 the same clock profiler-commit-query prints"
141050
+ )
140961
141051
  });
140962
141052
  var zodSchema54 = external_exports.object({
140963
141053
  port: external_exports.coerce.number().default(8081).describe("Metro server port"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.20.1-next.8",
3
+ "version": "0.20.1-next.9",
4
4
  "mcpName": "io.github.software-mansion/argent",
5
5
  "description": "MCP server for iOS Simulator and Android Emulator control",
6
6
  "license": "Apache-2.0",