@react-perfscope/core 0.6.0 → 0.7.1
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/index.cjs +107 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -4
- package/dist/index.d.ts +49 -4
- package/dist/index.js +106 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
BUFFER_CAP: () => BUFFER_CAP,
|
|
24
24
|
analyzeFrames: () => analyzeFrames,
|
|
25
25
|
analyzeHeapTrend: () => analyzeHeapTrend,
|
|
26
|
+
analyzeLeakTrend: () => analyzeLeakTrend,
|
|
26
27
|
attachLazyStack: () => attachLazyStack,
|
|
27
28
|
attributeLongTaskSignals: () => attributeLongTaskSignals,
|
|
28
29
|
attributeWindow: () => attributeWindow,
|
|
@@ -138,23 +139,25 @@ function isSelfRequest(url) {
|
|
|
138
139
|
// src/sourcemap.ts
|
|
139
140
|
var CHROME_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/;
|
|
140
141
|
var FIREFOX_FRAME = /^(.*?)@(.+?):(\d+):(\d+)$/;
|
|
142
|
+
function lookupFrame(tracer, frame) {
|
|
143
|
+
const pos = (0, import_trace_mapping.originalPositionFor)(tracer, { line: frame.line, column: frame.col - 1 });
|
|
144
|
+
if (pos.source == null || pos.line == null || pos.column == null) {
|
|
145
|
+
return frame;
|
|
146
|
+
}
|
|
147
|
+
const resolved = {
|
|
148
|
+
file: pos.source,
|
|
149
|
+
line: pos.line,
|
|
150
|
+
col: pos.column + 1
|
|
151
|
+
};
|
|
152
|
+
if (pos.name) resolved.fnName = pos.name;
|
|
153
|
+
else if (frame.fnName) resolved.fnName = frame.fnName;
|
|
154
|
+
return resolved;
|
|
155
|
+
}
|
|
141
156
|
async function resolveFrame(frame, fetchMap) {
|
|
142
157
|
try {
|
|
143
158
|
const map = await fetchMap(frame.file);
|
|
144
159
|
if (!map) return frame;
|
|
145
|
-
|
|
146
|
-
const pos = (0, import_trace_mapping.originalPositionFor)(tracer, { line: frame.line, column: frame.col });
|
|
147
|
-
if (pos.source == null || pos.line == null || pos.column == null) {
|
|
148
|
-
return frame;
|
|
149
|
-
}
|
|
150
|
-
const resolved = {
|
|
151
|
-
file: pos.source,
|
|
152
|
-
line: pos.line,
|
|
153
|
-
col: pos.column
|
|
154
|
-
};
|
|
155
|
-
if (pos.name) resolved.fnName = pos.name;
|
|
156
|
-
else if (frame.fnName) resolved.fnName = frame.fnName;
|
|
157
|
-
return resolved;
|
|
160
|
+
return lookupFrame(new import_trace_mapping.TraceMap(map), frame);
|
|
158
161
|
} catch (err) {
|
|
159
162
|
console.warn("[react-perfscope] resolveFrame failed:", err);
|
|
160
163
|
return frame;
|
|
@@ -177,7 +180,7 @@ function attachLazyStack(target, raw, skipTopFrames = 0) {
|
|
|
177
180
|
function createSourceMapResolver(opts = {}) {
|
|
178
181
|
const f = opts.fetch ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
|
|
179
182
|
const cache = /* @__PURE__ */ new Map();
|
|
180
|
-
function
|
|
183
|
+
function fetchTracerFor(sourceUrl) {
|
|
181
184
|
if (!f) return Promise.resolve(null);
|
|
182
185
|
const existing = cache.get(sourceUrl);
|
|
183
186
|
if (existing) return existing;
|
|
@@ -196,13 +199,13 @@ function createSourceMapResolver(opts = {}) {
|
|
|
196
199
|
const header = ref.slice(0, commaIdx);
|
|
197
200
|
const payload = ref.slice(commaIdx + 1);
|
|
198
201
|
const decoded = header.includes("base64") ? typeof atob === "function" ? atob(payload) : globalThis.Buffer.from(payload, "base64").toString("utf8") : decodeURIComponent(payload);
|
|
199
|
-
return JSON.parse(decoded);
|
|
202
|
+
return new import_trace_mapping.TraceMap(JSON.parse(decoded));
|
|
200
203
|
}
|
|
201
204
|
const mapUrl = new URL(ref, sourceUrl).href;
|
|
202
205
|
markSelfRequest(mapUrl);
|
|
203
206
|
const mapRes = await f(mapUrl);
|
|
204
207
|
if (!mapRes || !mapRes.ok) return null;
|
|
205
|
-
return await mapRes.json();
|
|
208
|
+
return new import_trace_mapping.TraceMap(await mapRes.json());
|
|
206
209
|
} catch {
|
|
207
210
|
return null;
|
|
208
211
|
}
|
|
@@ -213,7 +216,9 @@ function createSourceMapResolver(opts = {}) {
|
|
|
213
216
|
return {
|
|
214
217
|
async resolve(frame) {
|
|
215
218
|
try {
|
|
216
|
-
|
|
219
|
+
const tracer = await fetchTracerFor(frame.file);
|
|
220
|
+
if (!tracer) return frame;
|
|
221
|
+
return lookupFrame(tracer, frame);
|
|
217
222
|
} catch {
|
|
218
223
|
return frame;
|
|
219
224
|
}
|
|
@@ -277,6 +282,48 @@ function unsupportedKinds() {
|
|
|
277
282
|
return Object.keys(caps).filter((k) => !caps[k]);
|
|
278
283
|
}
|
|
279
284
|
|
|
285
|
+
// src/leak-trend.ts
|
|
286
|
+
var MIN_SAMPLES = 4;
|
|
287
|
+
var FLOOR_SEGMENTS = 8;
|
|
288
|
+
var MIN_NET_GROWTH = 3;
|
|
289
|
+
function slope(points) {
|
|
290
|
+
const n = points.length;
|
|
291
|
+
if (n < 2) return 0;
|
|
292
|
+
let sx = 0;
|
|
293
|
+
let sy = 0;
|
|
294
|
+
let sxy = 0;
|
|
295
|
+
let sxx = 0;
|
|
296
|
+
for (const { x, y } of points) {
|
|
297
|
+
sx += x;
|
|
298
|
+
sy += y;
|
|
299
|
+
sxy += x * y;
|
|
300
|
+
sxx += x * x;
|
|
301
|
+
}
|
|
302
|
+
const denom = n * sxx - sx * sx;
|
|
303
|
+
if (denom === 0) return 0;
|
|
304
|
+
return (n * sxy - sx * sy) / denom;
|
|
305
|
+
}
|
|
306
|
+
function analyzeLeakTrend(samples) {
|
|
307
|
+
if (samples.length < MIN_SAMPLES) return null;
|
|
308
|
+
const t0 = samples[0].at;
|
|
309
|
+
const segSize = Math.max(1, Math.ceil(samples.length / FLOOR_SEGMENTS));
|
|
310
|
+
const floorPoints = [];
|
|
311
|
+
for (let i = 0; i < samples.length; i += segSize) {
|
|
312
|
+
const seg = samples.slice(i, i + segSize);
|
|
313
|
+
let min = Infinity;
|
|
314
|
+
for (const s of seg) min = Math.min(min, s.retained);
|
|
315
|
+
const mid = seg[Math.floor(seg.length / 2)];
|
|
316
|
+
floorPoints.push({ x: (mid.at - t0) / 6e4, y: min });
|
|
317
|
+
}
|
|
318
|
+
const slopePerMin = slope(floorPoints);
|
|
319
|
+
const spanMin = (samples[samples.length - 1].at - t0) / 6e4;
|
|
320
|
+
const projectedGrowth = slopePerMin * spanMin;
|
|
321
|
+
const recent = floorPoints.slice(Math.floor(floorPoints.length / 2));
|
|
322
|
+
const recentSlope = recent.length >= 2 ? slope(recent) : slopePerMin;
|
|
323
|
+
const leaking = projectedGrowth >= MIN_NET_GROWTH && recentSlope > 0;
|
|
324
|
+
return { leaking, slopePerMin };
|
|
325
|
+
}
|
|
326
|
+
|
|
280
327
|
// src/correlate.ts
|
|
281
328
|
var FRAME_MS = 16;
|
|
282
329
|
function isAnchor(signal) {
|
|
@@ -728,37 +775,40 @@ function createNetworkCollector() {
|
|
|
728
775
|
|
|
729
776
|
// src/collectors/web-vitals.ts
|
|
730
777
|
var import_web_vitals = require("web-vitals");
|
|
778
|
+
var subscribed = false;
|
|
779
|
+
var sinks = /* @__PURE__ */ new Set();
|
|
780
|
+
function ensureSubscribed() {
|
|
781
|
+
if (subscribed) return true;
|
|
782
|
+
try {
|
|
783
|
+
const fan = (name) => (metric) => {
|
|
784
|
+
for (const sink of sinks) sink(name, metric.value);
|
|
785
|
+
};
|
|
786
|
+
(0, import_web_vitals.onLCP)(fan("LCP"));
|
|
787
|
+
(0, import_web_vitals.onINP)(fan("INP"));
|
|
788
|
+
(0, import_web_vitals.onCLS)(fan("CLS"));
|
|
789
|
+
(0, import_web_vitals.onFCP)(fan("FCP"));
|
|
790
|
+
(0, import_web_vitals.onTTFB)(fan("TTFB"));
|
|
791
|
+
subscribed = true;
|
|
792
|
+
} catch (err) {
|
|
793
|
+
console.warn("[react-perfscope] web-vitals collector failed to subscribe:", err);
|
|
794
|
+
}
|
|
795
|
+
return subscribed;
|
|
796
|
+
}
|
|
731
797
|
function createWebVitalsCollector() {
|
|
732
|
-
let active = false;
|
|
733
|
-
let subscribed = false;
|
|
734
798
|
let emit = () => {
|
|
735
799
|
};
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
emit({ kind: "web-vital", name, value: metric.value });
|
|
740
|
-
};
|
|
741
|
-
}
|
|
800
|
+
const sink = (name, value) => {
|
|
801
|
+
emit({ kind: "web-vital", name, value });
|
|
802
|
+
};
|
|
742
803
|
return {
|
|
743
804
|
kind: "web-vital",
|
|
744
805
|
activate(emitFn) {
|
|
806
|
+
if (!ensureSubscribed()) return;
|
|
745
807
|
emit = emitFn;
|
|
746
|
-
|
|
747
|
-
if (subscribed) return;
|
|
748
|
-
try {
|
|
749
|
-
(0, import_web_vitals.onLCP)(makeHandler("LCP"));
|
|
750
|
-
(0, import_web_vitals.onINP)(makeHandler("INP"));
|
|
751
|
-
(0, import_web_vitals.onCLS)(makeHandler("CLS"));
|
|
752
|
-
(0, import_web_vitals.onFCP)(makeHandler("FCP"));
|
|
753
|
-
(0, import_web_vitals.onTTFB)(makeHandler("TTFB"));
|
|
754
|
-
subscribed = true;
|
|
755
|
-
} catch (err) {
|
|
756
|
-
console.warn("[react-perfscope] web-vitals collector failed to subscribe:", err);
|
|
757
|
-
active = false;
|
|
758
|
-
}
|
|
808
|
+
sinks.add(sink);
|
|
759
809
|
},
|
|
760
810
|
deactivate() {
|
|
761
|
-
|
|
811
|
+
sinks.delete(sink);
|
|
762
812
|
}
|
|
763
813
|
};
|
|
764
814
|
}
|
|
@@ -766,19 +816,19 @@ function createWebVitalsCollector() {
|
|
|
766
816
|
// src/collectors/heap.ts
|
|
767
817
|
var SAMPLE_INTERVAL_MS = 250;
|
|
768
818
|
var MAX_SAMPLES = 2e4;
|
|
769
|
-
var
|
|
770
|
-
var
|
|
819
|
+
var MIN_SAMPLES2 = 4;
|
|
820
|
+
var FLOOR_SEGMENTS2 = 8;
|
|
771
821
|
var MB = 1024 * 1024;
|
|
772
822
|
var GROWING_SLOPE = 1 * MB;
|
|
773
823
|
var LEAK_SLOPE = 5 * MB;
|
|
774
|
-
var
|
|
824
|
+
var MIN_NET_GROWTH2 = 2 * MB;
|
|
775
825
|
function readMemory() {
|
|
776
826
|
const perf = globalThis.performance;
|
|
777
827
|
const mem = perf?.memory;
|
|
778
828
|
if (!mem || typeof mem.usedJSHeapSize !== "number") return null;
|
|
779
829
|
return mem;
|
|
780
830
|
}
|
|
781
|
-
function
|
|
831
|
+
function slope2(points) {
|
|
782
832
|
const n = points.length;
|
|
783
833
|
if (n < 2) return 0;
|
|
784
834
|
let sx = 0;
|
|
@@ -796,9 +846,9 @@ function slope(points) {
|
|
|
796
846
|
return (n * sxy - sx * sy) / denom;
|
|
797
847
|
}
|
|
798
848
|
function analyzeHeapTrend(samples) {
|
|
799
|
-
if (samples.length <
|
|
849
|
+
if (samples.length < MIN_SAMPLES2) return null;
|
|
800
850
|
const t0 = samples[0].at;
|
|
801
|
-
const segSize = Math.max(1, Math.ceil(samples.length /
|
|
851
|
+
const segSize = Math.max(1, Math.ceil(samples.length / FLOOR_SEGMENTS2));
|
|
802
852
|
const floorPoints = [];
|
|
803
853
|
for (let i = 0; i < samples.length; i += segSize) {
|
|
804
854
|
const seg = samples.slice(i, i + segSize);
|
|
@@ -807,13 +857,13 @@ function analyzeHeapTrend(samples) {
|
|
|
807
857
|
const mid = seg[Math.floor(seg.length / 2)];
|
|
808
858
|
floorPoints.push({ x: (mid.at - t0) / 6e4, y: min });
|
|
809
859
|
}
|
|
810
|
-
const slopeBytesPerMin =
|
|
860
|
+
const slopeBytesPerMin = slope2(floorPoints);
|
|
811
861
|
const spanMin = (samples[samples.length - 1].at - t0) / 6e4;
|
|
812
862
|
const projectedGrowth = slopeBytesPerMin * spanMin;
|
|
813
863
|
const recent = floorPoints.slice(Math.floor(floorPoints.length / 2));
|
|
814
|
-
const recentSlope = recent.length >= 2 ?
|
|
864
|
+
const recentSlope = recent.length >= 2 ? slope2(recent) : slopeBytesPerMin;
|
|
815
865
|
let classification = "stable";
|
|
816
|
-
const sustained = projectedGrowth >=
|
|
866
|
+
const sustained = projectedGrowth >= MIN_NET_GROWTH2 && recentSlope >= GROWING_SLOPE;
|
|
817
867
|
if (sustained) {
|
|
818
868
|
classification = slopeBytesPerMin >= LEAK_SLOPE && recentSlope >= LEAK_SLOPE ? "leak-suspected" : "growing";
|
|
819
869
|
}
|
|
@@ -851,6 +901,7 @@ function createHeapCollector() {
|
|
|
851
901
|
|
|
852
902
|
// src/collectors/interaction.ts
|
|
853
903
|
var DURATION_THRESHOLD = 40;
|
|
904
|
+
var MAX_ENTRIES = 5e3;
|
|
854
905
|
function selectorFor(target) {
|
|
855
906
|
const el = target;
|
|
856
907
|
if (!el || typeof el.tagName !== "string") return void 0;
|
|
@@ -879,7 +930,10 @@ function createInteractionCollector() {
|
|
|
879
930
|
try {
|
|
880
931
|
observer = new PerformanceObserver((list) => {
|
|
881
932
|
if (!active) return;
|
|
882
|
-
for (const e of list.getEntries())
|
|
933
|
+
for (const e of list.getEntries()) {
|
|
934
|
+
if (entries.length >= MAX_ENTRIES) break;
|
|
935
|
+
entries.push(e);
|
|
936
|
+
}
|
|
883
937
|
});
|
|
884
938
|
observer.observe({ type: "event", buffered: false, durationThreshold: DURATION_THRESHOLD });
|
|
885
939
|
try {
|
|
@@ -903,8 +957,10 @@ function createInteractionCollector() {
|
|
|
903
957
|
}
|
|
904
958
|
},
|
|
905
959
|
finalize(result) {
|
|
960
|
+
const buffered = entries;
|
|
961
|
+
entries = [];
|
|
906
962
|
const byId = /* @__PURE__ */ new Map();
|
|
907
|
-
for (const e of
|
|
963
|
+
for (const e of buffered) {
|
|
908
964
|
const id = e.interactionId;
|
|
909
965
|
if (!id) continue;
|
|
910
966
|
const group = byId.get(id);
|
|
@@ -1146,6 +1202,7 @@ function createSelfProfilingCollector() {
|
|
|
1146
1202
|
BUFFER_CAP,
|
|
1147
1203
|
analyzeFrames,
|
|
1148
1204
|
analyzeHeapTrend,
|
|
1205
|
+
analyzeLeakTrend,
|
|
1149
1206
|
attachLazyStack,
|
|
1150
1207
|
attributeLongTaskSignals,
|
|
1151
1208
|
attributeWindow,
|