@atolljs/devtools 0.1.12 → 0.1.13
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/chunk-5PJAC55Q.js +109 -0
- package/dist/{chunk-6E54FXKS.js → chunk-ZBVH5X6N.js} +18 -7
- package/dist/index.js +2 -2
- package/dist/node.js +2 -2
- package/dist/otel.js +3 -5
- package/package.json +2 -2
- package/dist/chunk-57NGHQUL.js +0 -344
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// src/probes.ts
|
|
2
|
+
import { emitDevtools } from "@atolljs/core";
|
|
3
|
+
var MAX_SCRIPTS = 5;
|
|
4
|
+
var BUDGET_MS = 50;
|
|
5
|
+
var HIDDEN_SCRIPT_MS = 100;
|
|
6
|
+
var STALL_MS = 1e3;
|
|
7
|
+
var refs = 0;
|
|
8
|
+
var teardown = null;
|
|
9
|
+
var isBrowserMain = () => typeof window !== "undefined" && typeof window.document !== "undefined" && typeof requestAnimationFrame === "function" && !(typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope);
|
|
10
|
+
var observeLongFrames = () => {
|
|
11
|
+
if (typeof PerformanceObserver === "undefined") return () => {
|
|
12
|
+
};
|
|
13
|
+
const supported = PerformanceObserver.supportedEntryTypes ?? [];
|
|
14
|
+
const type = supported.includes("long-animation-frame") ? "long-animation-frame" : supported.includes("longtask") ? "longtask" : null;
|
|
15
|
+
if (!type) return () => {
|
|
16
|
+
};
|
|
17
|
+
const obs = new PerformanceObserver((list) => {
|
|
18
|
+
const hidden = document.visibilityState === "hidden";
|
|
19
|
+
for (const e of list.getEntries()) {
|
|
20
|
+
const ms = Math.round(e.duration);
|
|
21
|
+
const blockingMs = Math.round(e.blockingDuration ?? Math.max(0, e.duration - BUDGET_MS));
|
|
22
|
+
const scriptMs = e.scripts ? e.scripts.reduce((n, s) => n + s.duration, 0) : void 0;
|
|
23
|
+
if (hidden) {
|
|
24
|
+
if ((scriptMs ?? e.duration) < HIDDEN_SCRIPT_MS) continue;
|
|
25
|
+
} else if (e.blockingDuration === 0 && (scriptMs ?? 0) < BUDGET_MS) continue;
|
|
26
|
+
const scripts = e.scripts?.length ? [...e.scripts].sort((a, b) => b.duration - a.duration).slice(0, MAX_SCRIPTS).map((s) => ({
|
|
27
|
+
src: s.sourceURL ? s.sourceURL.slice(0, 300) : void 0,
|
|
28
|
+
fn: s.sourceFunctionName || s.invoker || void 0,
|
|
29
|
+
ms: Math.round(s.duration)
|
|
30
|
+
})) : void 0;
|
|
31
|
+
emitDevtools({
|
|
32
|
+
type: "runtime:longframe",
|
|
33
|
+
ms,
|
|
34
|
+
blockingMs,
|
|
35
|
+
...scripts ? { scripts } : {},
|
|
36
|
+
...hidden ? { hidden: true } : {}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
obs.observe({ type, buffered: true });
|
|
42
|
+
} catch {
|
|
43
|
+
return () => {
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return () => obs.disconnect();
|
|
47
|
+
};
|
|
48
|
+
var sampleFrames = () => {
|
|
49
|
+
let raf = 0;
|
|
50
|
+
let last = 0;
|
|
51
|
+
let windowStart = 0;
|
|
52
|
+
let frames = 0;
|
|
53
|
+
let dropped = 0;
|
|
54
|
+
let interval = 1e3 / 60;
|
|
55
|
+
let shortest = Infinity;
|
|
56
|
+
const tick = (now) => {
|
|
57
|
+
raf = requestAnimationFrame(tick);
|
|
58
|
+
if (!last) {
|
|
59
|
+
last = windowStart = now;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const dt = now - last;
|
|
63
|
+
last = now;
|
|
64
|
+
if (dt > STALL_MS) {
|
|
65
|
+
windowStart = now;
|
|
66
|
+
frames = dropped = 0;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
frames++;
|
|
70
|
+
if (dt >= 4 && dt < shortest) shortest = dt;
|
|
71
|
+
if (dt > interval * 2) dropped++;
|
|
72
|
+
const elapsed = now - windowStart;
|
|
73
|
+
if (elapsed >= 1e3) {
|
|
74
|
+
emitDevtools({ type: "runtime:frames", fps: Math.round(frames * 1e3 / elapsed), dropped });
|
|
75
|
+
if (shortest !== Infinity) interval = shortest;
|
|
76
|
+
shortest = Infinity;
|
|
77
|
+
windowStart = now;
|
|
78
|
+
frames = dropped = 0;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
raf = requestAnimationFrame(tick);
|
|
82
|
+
return () => cancelAnimationFrame(raf);
|
|
83
|
+
};
|
|
84
|
+
function installJankProbe() {
|
|
85
|
+
if (!isBrowserMain()) return () => {
|
|
86
|
+
};
|
|
87
|
+
refs++;
|
|
88
|
+
if (!teardown) {
|
|
89
|
+
const stopObs = observeLongFrames();
|
|
90
|
+
const stopRaf = sampleFrames();
|
|
91
|
+
teardown = () => {
|
|
92
|
+
stopObs();
|
|
93
|
+
stopRaf();
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
let released = false;
|
|
97
|
+
return () => {
|
|
98
|
+
if (released) return;
|
|
99
|
+
released = true;
|
|
100
|
+
if (--refs === 0 && teardown) {
|
|
101
|
+
teardown();
|
|
102
|
+
teardown = null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
installJankProbe
|
|
109
|
+
};
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
installJankProbe
|
|
3
|
+
} from "./chunk-5PJAC55Q.js";
|
|
4
|
+
|
|
5
|
+
// src/client.ts
|
|
6
|
+
import {
|
|
7
|
+
installFetchProbe as installFetchProbe2,
|
|
8
|
+
installMemoryProbe as installMemoryProbe2,
|
|
9
|
+
setDevtoolsSink as setDevtoolsSink2
|
|
10
|
+
} from "@atolljs/core";
|
|
11
|
+
|
|
12
|
+
// src/broadcast.ts
|
|
1
13
|
import {
|
|
2
14
|
installFetchProbe,
|
|
3
|
-
installJankProbe,
|
|
4
15
|
installMemoryProbe,
|
|
5
|
-
runDevtoolsCommand,
|
|
6
16
|
setDevtoolsSink
|
|
7
|
-
} from "
|
|
17
|
+
} from "@atolljs/core";
|
|
8
18
|
|
|
9
19
|
// src/control.ts
|
|
20
|
+
import { runDevtoolsCommand } from "@atolljs/core";
|
|
10
21
|
var jsonSafe = (v) => {
|
|
11
22
|
if (v === void 0) return void 0;
|
|
12
23
|
try {
|
|
@@ -189,11 +200,11 @@ function connectWebSocket(opts, session) {
|
|
|
189
200
|
}
|
|
190
201
|
const timer = setInterval(flush, opts.flushMs ?? 100);
|
|
191
202
|
timer.unref?.();
|
|
192
|
-
if (opts.network !== false)
|
|
193
|
-
if (opts.memory !== false)
|
|
203
|
+
if (opts.network !== false) installFetchProbe2(globalThis);
|
|
204
|
+
if (opts.memory !== false) installMemoryProbe2(globalThis);
|
|
194
205
|
const releaseJank = opts.jank !== false ? installJankProbe() : () => {
|
|
195
206
|
};
|
|
196
|
-
|
|
207
|
+
setDevtoolsSink2((event) => {
|
|
197
208
|
if (closed) return;
|
|
198
209
|
pending.push(event);
|
|
199
210
|
if (pending.length > cap) pending.shift();
|
|
@@ -206,7 +217,7 @@ function connectWebSocket(opts, session) {
|
|
|
206
217
|
},
|
|
207
218
|
close() {
|
|
208
219
|
closed = true;
|
|
209
|
-
|
|
220
|
+
setDevtoolsSink2(null);
|
|
210
221
|
flush();
|
|
211
222
|
sock?.close();
|
|
212
223
|
clearInterval(timer);
|
package/dist/index.js
CHANGED
package/dist/node.js
CHANGED
package/dist/otel.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
installJankProbe,
|
|
5
|
-
installMemoryProbe
|
|
6
|
-
} from "./chunk-57NGHQUL.js";
|
|
2
|
+
installJankProbe
|
|
3
|
+
} from "./chunk-5PJAC55Q.js";
|
|
7
4
|
import {
|
|
8
5
|
OtelMapper,
|
|
9
6
|
SEVERITY,
|
|
@@ -17,6 +14,7 @@ import {
|
|
|
17
14
|
} from "./chunk-WIL2P7HG.js";
|
|
18
15
|
|
|
19
16
|
// src/otel.ts
|
|
17
|
+
import { addDevtoolsSink, installFetchProbe, installMemoryProbe } from "@atolljs/core";
|
|
20
18
|
function exportOtel(opts = {}) {
|
|
21
19
|
const sender = createOtlpSender(opts);
|
|
22
20
|
const endpoint = sender.endpoint;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolljs/devtools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Devtools transport + local dashboard server for AtollJS instrumentation events",
|
|
5
5
|
"homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"registry": "https://registry.npmjs.org"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@atolljs/core": "0.1.
|
|
42
|
+
"@atolljs/core": "0.1.13"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
package/dist/chunk-57NGHQUL.js
DELETED
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
// ../../src/devtools.ts
|
|
2
|
-
var THREAD = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope ? "worker" : "main";
|
|
3
|
-
var sink = null;
|
|
4
|
-
var primary = null;
|
|
5
|
-
var extraSinks = /* @__PURE__ */ new Set();
|
|
6
|
-
var recompose = () => {
|
|
7
|
-
if (extraSinks.size === 0) {
|
|
8
|
-
sink = primary;
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
const all = primary ? [primary, ...extraSinks] : [...extraSinks];
|
|
12
|
-
sink = all.length === 1 ? all[0] : (event) => {
|
|
13
|
-
for (const s of all) {
|
|
14
|
-
try {
|
|
15
|
-
s(event);
|
|
16
|
-
} catch {
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
var setDevtoolsSink = (custom) => {
|
|
22
|
-
primary = custom;
|
|
23
|
-
recompose();
|
|
24
|
-
};
|
|
25
|
-
var addDevtoolsSink = (listener) => {
|
|
26
|
-
extraSinks.add(listener);
|
|
27
|
-
recompose();
|
|
28
|
-
return () => {
|
|
29
|
-
if (extraSinks.delete(listener)) recompose();
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
var emitDevtools = (event) => {
|
|
33
|
-
if (sink) sink({ ...event, at: performance.now(), thread: THREAD });
|
|
34
|
-
};
|
|
35
|
-
var commands = /* @__PURE__ */ new Map();
|
|
36
|
-
var listDevtoolsCommands = () => [...commands.keys()].sort();
|
|
37
|
-
var runDevtoolsCommand = async (name, args = {}) => {
|
|
38
|
-
if (name === "devtools.commands") return listDevtoolsCommands();
|
|
39
|
-
const handler = commands.get(name);
|
|
40
|
-
if (!handler) throw new Error(`unknown devtools command '${name}'`);
|
|
41
|
-
return handler(args);
|
|
42
|
-
};
|
|
43
|
-
var PROBED = /* @__PURE__ */ Symbol.for("atoll.devtools.fetch");
|
|
44
|
-
var BODY_PREVIEW = 8 * 1024;
|
|
45
|
-
var TEXTISH = /text|json|javascript|xml|html|css|svg|csv|urlencoded|graphql/;
|
|
46
|
-
var headerList = (h) => h ? [...h.entries()].slice(0, 64) : void 0;
|
|
47
|
-
var bodyPreview = (body) => {
|
|
48
|
-
if (body === null || body === void 0) return void 0;
|
|
49
|
-
if (typeof body === "string") return body.slice(0, BODY_PREVIEW);
|
|
50
|
-
if (body instanceof URLSearchParams) return String(body).slice(0, BODY_PREVIEW);
|
|
51
|
-
if (body instanceof Blob) return `[${body.type || "blob"} ${body.size}B]`;
|
|
52
|
-
if (body instanceof ArrayBuffer) return `[ArrayBuffer ${body.byteLength}B]`;
|
|
53
|
-
if (ArrayBuffer.isView(body)) return `[${body.constructor.name} ${body.byteLength}B]`;
|
|
54
|
-
if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
|
|
55
|
-
return "[stream]";
|
|
56
|
-
};
|
|
57
|
-
var readPreview = async (res, cap) => {
|
|
58
|
-
const reader = res.body?.getReader();
|
|
59
|
-
if (!reader) return void 0;
|
|
60
|
-
const chunks = [];
|
|
61
|
-
let n = 0;
|
|
62
|
-
try {
|
|
63
|
-
while (n < cap) {
|
|
64
|
-
const { done, value } = await reader.read();
|
|
65
|
-
if (done || !value) break;
|
|
66
|
-
chunks.push(value);
|
|
67
|
-
n += value.byteLength;
|
|
68
|
-
}
|
|
69
|
-
} catch {
|
|
70
|
-
}
|
|
71
|
-
if (!n) return void 0;
|
|
72
|
-
const all = new Uint8Array(n);
|
|
73
|
-
let off = 0;
|
|
74
|
-
for (const c of chunks) {
|
|
75
|
-
all.set(c, off);
|
|
76
|
-
off += c.byteLength;
|
|
77
|
-
}
|
|
78
|
-
return new TextDecoder().decode(all) + (n >= cap ? "\u2026" : "");
|
|
79
|
-
};
|
|
80
|
-
var findTiming = async (absUrl, t0) => {
|
|
81
|
-
const perf = globalThis.performance;
|
|
82
|
-
if (!perf?.getEntriesByType) return void 0;
|
|
83
|
-
for (let tries = 0; tries < 12; tries++) {
|
|
84
|
-
let best;
|
|
85
|
-
for (const e of perf.getEntriesByType("resource")) {
|
|
86
|
-
if (e.name !== absUrl || e.startTime < t0 - 5) continue;
|
|
87
|
-
if (!best || e.startTime < best.startTime) best = e;
|
|
88
|
-
}
|
|
89
|
-
if (best) {
|
|
90
|
-
const seg = (a, b) => b > a && a > 0 ? b - a : 0;
|
|
91
|
-
return {
|
|
92
|
-
queue: seg(best.fetchStart, best.domainLookupStart),
|
|
93
|
-
dns: seg(best.domainLookupStart, best.domainLookupEnd),
|
|
94
|
-
tcp: best.secureConnectionStart > 0 ? seg(best.connectStart, best.secureConnectionStart) : seg(best.connectStart, best.connectEnd),
|
|
95
|
-
tls: best.secureConnectionStart > 0 ? seg(best.secureConnectionStart, best.connectEnd) : 0,
|
|
96
|
-
wait: seg(best.requestStart, best.responseStart),
|
|
97
|
-
download: seg(best.responseStart, best.responseEnd),
|
|
98
|
-
transferSize: best.transferSize,
|
|
99
|
-
encodedSize: best.encodedBodySize,
|
|
100
|
-
decodedSize: best.decodedBodySize
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
104
|
-
}
|
|
105
|
-
return void 0;
|
|
106
|
-
};
|
|
107
|
-
var installFetchProbe = (scope) => {
|
|
108
|
-
const original = scope.fetch;
|
|
109
|
-
if (!original || original[PROBED]) return;
|
|
110
|
-
let seq = 0;
|
|
111
|
-
const wrapped = async (input, init) => {
|
|
112
|
-
const t0 = performance.now();
|
|
113
|
-
const id = ++seq;
|
|
114
|
-
const isReq = typeof Request !== "undefined" && input instanceof Request;
|
|
115
|
-
const url = isReq ? input.url : String(input);
|
|
116
|
-
const abs = (() => {
|
|
117
|
-
try {
|
|
118
|
-
return new URL(url, self.location?.href).href;
|
|
119
|
-
} catch {
|
|
120
|
-
return url;
|
|
121
|
-
}
|
|
122
|
-
})();
|
|
123
|
-
const method = (init?.method ?? (isReq ? input.method : "GET")).toUpperCase();
|
|
124
|
-
const reqClone = isReq && !init?.body ? input.clone() : null;
|
|
125
|
-
const reqHeaders = headerList(
|
|
126
|
-
init?.headers ? new Headers(init.headers) : isReq ? input.headers : void 0
|
|
127
|
-
);
|
|
128
|
-
let reqBody = bodyPreview(init?.body);
|
|
129
|
-
try {
|
|
130
|
-
const res = await original.call(scope, input, init);
|
|
131
|
-
const len = res.headers.get("content-length");
|
|
132
|
-
emitDevtools({
|
|
133
|
-
type: "net:fetch",
|
|
134
|
-
id,
|
|
135
|
-
url: url.slice(0, 300),
|
|
136
|
-
method,
|
|
137
|
-
status: res.status,
|
|
138
|
-
ms: performance.now() - t0,
|
|
139
|
-
bytes: len === null ? void 0 : Number(len),
|
|
140
|
-
reqHeaders,
|
|
141
|
-
reqBody
|
|
142
|
-
});
|
|
143
|
-
const resClone = res.clone();
|
|
144
|
-
void (async () => {
|
|
145
|
-
reqBody ??= reqClone ? await reqClone.text().then((t2) => t2.slice(0, BODY_PREVIEW)).catch(() => void 0) : void 0;
|
|
146
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
147
|
-
const resBody = TEXTISH.test(ct) ? await readPreview(resClone, BODY_PREVIEW).catch(() => void 0) : void 0;
|
|
148
|
-
const t = await findTiming(abs, t0);
|
|
149
|
-
emitDevtools({
|
|
150
|
-
type: "net:fetch-detail",
|
|
151
|
-
id,
|
|
152
|
-
reqBody,
|
|
153
|
-
resHeaders: headerList(res.headers),
|
|
154
|
-
resBody,
|
|
155
|
-
timing: t,
|
|
156
|
-
transferSize: t?.transferSize,
|
|
157
|
-
encodedSize: t?.encodedSize,
|
|
158
|
-
decodedSize: t?.decodedSize
|
|
159
|
-
});
|
|
160
|
-
})();
|
|
161
|
-
return res;
|
|
162
|
-
} catch (err) {
|
|
163
|
-
emitDevtools({
|
|
164
|
-
type: "net:fetch",
|
|
165
|
-
id,
|
|
166
|
-
url: url.slice(0, 300),
|
|
167
|
-
method,
|
|
168
|
-
ms: performance.now() - t0,
|
|
169
|
-
error: err instanceof Error ? err.message : String(err),
|
|
170
|
-
reqHeaders,
|
|
171
|
-
reqBody
|
|
172
|
-
});
|
|
173
|
-
throw err;
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
wrapped[PROBED] = true;
|
|
177
|
-
scope.fetch = wrapped;
|
|
178
|
-
};
|
|
179
|
-
var memProbed = /* @__PURE__ */ new WeakSet();
|
|
180
|
-
var installMemoryProbe = (scope, intervalMs = 2500) => {
|
|
181
|
-
if (memProbed.has(scope)) return;
|
|
182
|
-
const perf = scope.performance;
|
|
183
|
-
if (!perf) return;
|
|
184
|
-
const mem = perf.memory;
|
|
185
|
-
const inWorker = typeof WorkerGlobalScope !== "undefined" && globalThis.self instanceof WorkerGlobalScope;
|
|
186
|
-
const measure = perf.measureUserAgentSpecificMemory?.bind(perf);
|
|
187
|
-
const procMem = globalThis.process?.memoryUsage;
|
|
188
|
-
if (!mem && (!measure || inWorker) && !procMem) return;
|
|
189
|
-
memProbed.add(scope);
|
|
190
|
-
if (measure && !inWorker) {
|
|
191
|
-
const timer2 = setInterval(() => {
|
|
192
|
-
measure().then((res) => {
|
|
193
|
-
const contexts = (res.breakdown ?? []).map((b) => ({
|
|
194
|
-
bytes: b.bytes,
|
|
195
|
-
scope: b.attribution?.[0]?.scope,
|
|
196
|
-
url: b.attribution?.[0]?.url
|
|
197
|
-
})).sort((a, b) => b.bytes - a.bytes).slice(0, 24);
|
|
198
|
-
emitDevtools({
|
|
199
|
-
type: "runtime:memory",
|
|
200
|
-
heapBytes: res.bytes,
|
|
201
|
-
heapLimitBytes: mem?.jsHeapSizeLimit,
|
|
202
|
-
contexts
|
|
203
|
-
});
|
|
204
|
-
}).catch(() => {
|
|
205
|
-
if (mem) {
|
|
206
|
-
emitDevtools({
|
|
207
|
-
type: "runtime:memory",
|
|
208
|
-
heapBytes: mem.usedJSHeapSize,
|
|
209
|
-
heapLimitBytes: mem.jsHeapSizeLimit
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}, Math.max(intervalMs, 4e3));
|
|
214
|
-
timer2.unref?.();
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const timer = setInterval(() => {
|
|
218
|
-
if (mem) {
|
|
219
|
-
emitDevtools({
|
|
220
|
-
type: "runtime:memory",
|
|
221
|
-
heapBytes: mem.usedJSHeapSize,
|
|
222
|
-
heapLimitBytes: mem.jsHeapSizeLimit
|
|
223
|
-
});
|
|
224
|
-
} else if (procMem) {
|
|
225
|
-
const u = procMem();
|
|
226
|
-
emitDevtools({ type: "runtime:memory", heapBytes: u.heapUsed, rssBytes: u.rss });
|
|
227
|
-
}
|
|
228
|
-
}, intervalMs);
|
|
229
|
-
timer.unref?.();
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// src/probes.ts
|
|
233
|
-
var MAX_SCRIPTS = 5;
|
|
234
|
-
var BUDGET_MS = 50;
|
|
235
|
-
var HIDDEN_SCRIPT_MS = 100;
|
|
236
|
-
var STALL_MS = 1e3;
|
|
237
|
-
var refs = 0;
|
|
238
|
-
var teardown = null;
|
|
239
|
-
var isBrowserMain = () => typeof window !== "undefined" && typeof window.document !== "undefined" && typeof requestAnimationFrame === "function" && !(typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope);
|
|
240
|
-
var observeLongFrames = () => {
|
|
241
|
-
if (typeof PerformanceObserver === "undefined") return () => {
|
|
242
|
-
};
|
|
243
|
-
const supported = PerformanceObserver.supportedEntryTypes ?? [];
|
|
244
|
-
const type = supported.includes("long-animation-frame") ? "long-animation-frame" : supported.includes("longtask") ? "longtask" : null;
|
|
245
|
-
if (!type) return () => {
|
|
246
|
-
};
|
|
247
|
-
const obs = new PerformanceObserver((list) => {
|
|
248
|
-
const hidden = document.visibilityState === "hidden";
|
|
249
|
-
for (const e of list.getEntries()) {
|
|
250
|
-
const ms = Math.round(e.duration);
|
|
251
|
-
const blockingMs = Math.round(e.blockingDuration ?? Math.max(0, e.duration - BUDGET_MS));
|
|
252
|
-
const scriptMs = e.scripts ? e.scripts.reduce((n, s) => n + s.duration, 0) : void 0;
|
|
253
|
-
if (hidden) {
|
|
254
|
-
if ((scriptMs ?? e.duration) < HIDDEN_SCRIPT_MS) continue;
|
|
255
|
-
} else if (e.blockingDuration === 0 && (scriptMs ?? 0) < BUDGET_MS) continue;
|
|
256
|
-
const scripts = e.scripts?.length ? [...e.scripts].sort((a, b) => b.duration - a.duration).slice(0, MAX_SCRIPTS).map((s) => ({
|
|
257
|
-
src: s.sourceURL ? s.sourceURL.slice(0, 300) : void 0,
|
|
258
|
-
fn: s.sourceFunctionName || s.invoker || void 0,
|
|
259
|
-
ms: Math.round(s.duration)
|
|
260
|
-
})) : void 0;
|
|
261
|
-
emitDevtools({
|
|
262
|
-
type: "runtime:longframe",
|
|
263
|
-
ms,
|
|
264
|
-
blockingMs,
|
|
265
|
-
...scripts ? { scripts } : {},
|
|
266
|
-
...hidden ? { hidden: true } : {}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
try {
|
|
271
|
-
obs.observe({ type, buffered: true });
|
|
272
|
-
} catch {
|
|
273
|
-
return () => {
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
return () => obs.disconnect();
|
|
277
|
-
};
|
|
278
|
-
var sampleFrames = () => {
|
|
279
|
-
let raf = 0;
|
|
280
|
-
let last = 0;
|
|
281
|
-
let windowStart = 0;
|
|
282
|
-
let frames = 0;
|
|
283
|
-
let dropped = 0;
|
|
284
|
-
let interval = 1e3 / 60;
|
|
285
|
-
let shortest = Infinity;
|
|
286
|
-
const tick = (now) => {
|
|
287
|
-
raf = requestAnimationFrame(tick);
|
|
288
|
-
if (!last) {
|
|
289
|
-
last = windowStart = now;
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const dt = now - last;
|
|
293
|
-
last = now;
|
|
294
|
-
if (dt > STALL_MS) {
|
|
295
|
-
windowStart = now;
|
|
296
|
-
frames = dropped = 0;
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
frames++;
|
|
300
|
-
if (dt >= 4 && dt < shortest) shortest = dt;
|
|
301
|
-
if (dt > interval * 2) dropped++;
|
|
302
|
-
const elapsed = now - windowStart;
|
|
303
|
-
if (elapsed >= 1e3) {
|
|
304
|
-
emitDevtools({ type: "runtime:frames", fps: Math.round(frames * 1e3 / elapsed), dropped });
|
|
305
|
-
if (shortest !== Infinity) interval = shortest;
|
|
306
|
-
shortest = Infinity;
|
|
307
|
-
windowStart = now;
|
|
308
|
-
frames = dropped = 0;
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
raf = requestAnimationFrame(tick);
|
|
312
|
-
return () => cancelAnimationFrame(raf);
|
|
313
|
-
};
|
|
314
|
-
function installJankProbe() {
|
|
315
|
-
if (!isBrowserMain()) return () => {
|
|
316
|
-
};
|
|
317
|
-
refs++;
|
|
318
|
-
if (!teardown) {
|
|
319
|
-
const stopObs = observeLongFrames();
|
|
320
|
-
const stopRaf = sampleFrames();
|
|
321
|
-
teardown = () => {
|
|
322
|
-
stopObs();
|
|
323
|
-
stopRaf();
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
let released = false;
|
|
327
|
-
return () => {
|
|
328
|
-
if (released) return;
|
|
329
|
-
released = true;
|
|
330
|
-
if (--refs === 0 && teardown) {
|
|
331
|
-
teardown();
|
|
332
|
-
teardown = null;
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export {
|
|
338
|
-
setDevtoolsSink,
|
|
339
|
-
addDevtoolsSink,
|
|
340
|
-
runDevtoolsCommand,
|
|
341
|
-
installFetchProbe,
|
|
342
|
-
installMemoryProbe,
|
|
343
|
-
installJankProbe
|
|
344
|
-
};
|