@buoy-gg/js-top 7.0.41 → 7.0.42

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 (118) hide show
  1. package/README.md +12 -71
  2. package/lib/commonjs/index.js +1 -99
  3. package/lib/commonjs/js-top/components/BusySparkline.js +1 -56
  4. package/lib/commonjs/js-top/components/JsTopHudOverlay.js +1 -79
  5. package/lib/commonjs/js-top/components/JsTopModal.js +1 -349
  6. package/lib/commonjs/js-top/components/TaskDetailView.js +1 -330
  7. package/lib/commonjs/js-top/components/TaskTable.js +1 -360
  8. package/lib/commonjs/js-top/engine/JsTopController.js +1 -350
  9. package/lib/commonjs/js-top/engine/aggregator.js +1 -148
  10. package/lib/commonjs/js-top/engine/busyProbe.js +1 -101
  11. package/lib/commonjs/js-top/engine/frameStack.js +1 -133
  12. package/lib/commonjs/js-top/engine/longtasks.js +1 -141
  13. package/lib/commonjs/js-top/engine/now.js +1 -38
  14. package/lib/commonjs/js-top/engine/originRegistry.js +1 -402
  15. package/lib/commonjs/js-top/engine/patches/bridge.js +1 -60
  16. package/lib/commonjs/js-top/engine/patches/microtasks.js +1 -27
  17. package/lib/commonjs/js-top/engine/patches/promises.js +1 -65
  18. package/lib/commonjs/js-top/engine/patches/timers.js +1 -66
  19. package/lib/commonjs/js-top/engine/state.js +1 -63
  20. package/lib/commonjs/js-top/engine/symbolicate.js +1 -95
  21. package/lib/commonjs/js-top/sync/jsTopSyncAdapter.js +1 -74
  22. package/lib/commonjs/js-top/types/index.js +1 -46
  23. package/lib/commonjs/js-top/utils/preferences.js +1 -31
  24. package/lib/commonjs/preset.js +1 -75
  25. package/lib/module/index.js +1 -49
  26. package/lib/module/js-top/components/BusySparkline.js +1 -52
  27. package/lib/module/js-top/components/JsTopHudOverlay.js +1 -75
  28. package/lib/module/js-top/components/JsTopModal.js +1 -345
  29. package/lib/module/js-top/components/TaskDetailView.js +1 -326
  30. package/lib/module/js-top/components/TaskTable.js +1 -353
  31. package/lib/module/js-top/engine/JsTopController.js +1 -347
  32. package/lib/module/js-top/engine/aggregator.js +1 -136
  33. package/lib/module/js-top/engine/busyProbe.js +1 -94
  34. package/lib/module/js-top/engine/frameStack.js +1 -126
  35. package/lib/module/js-top/engine/longtasks.js +1 -133
  36. package/lib/module/js-top/engine/now.js +1 -33
  37. package/lib/module/js-top/engine/originRegistry.js +1 -388
  38. package/lib/module/js-top/engine/patches/bridge.js +1 -56
  39. package/lib/module/js-top/engine/patches/microtasks.js +1 -23
  40. package/lib/module/js-top/engine/patches/promises.js +1 -62
  41. package/lib/module/js-top/engine/patches/timers.js +1 -62
  42. package/lib/module/js-top/engine/state.js +1 -54
  43. package/lib/module/js-top/engine/symbolicate.js +1 -91
  44. package/lib/module/js-top/sync/jsTopSyncAdapter.js +1 -70
  45. package/lib/module/js-top/types/index.js +1 -42
  46. package/lib/module/js-top/utils/preferences.js +1 -26
  47. package/lib/module/preset.js +1 -71
  48. package/lib/web/index.mjs +2 -3
  49. package/package.json +2 -2
  50. package/lib/commonjs/index.js.map +0 -1
  51. package/lib/commonjs/js-top/components/BusySparkline.js.map +0 -1
  52. package/lib/commonjs/js-top/components/JsTopHudOverlay.js.map +0 -1
  53. package/lib/commonjs/js-top/components/JsTopModal.js.map +0 -1
  54. package/lib/commonjs/js-top/components/TaskDetailView.js.map +0 -1
  55. package/lib/commonjs/js-top/components/TaskTable.js.map +0 -1
  56. package/lib/commonjs/js-top/engine/JsTopController.js.map +0 -1
  57. package/lib/commonjs/js-top/engine/aggregator.js.map +0 -1
  58. package/lib/commonjs/js-top/engine/busyProbe.js.map +0 -1
  59. package/lib/commonjs/js-top/engine/frameStack.js.map +0 -1
  60. package/lib/commonjs/js-top/engine/longtasks.js.map +0 -1
  61. package/lib/commonjs/js-top/engine/now.js.map +0 -1
  62. package/lib/commonjs/js-top/engine/originRegistry.js.map +0 -1
  63. package/lib/commonjs/js-top/engine/patches/bridge.js.map +0 -1
  64. package/lib/commonjs/js-top/engine/patches/microtasks.js.map +0 -1
  65. package/lib/commonjs/js-top/engine/patches/promises.js.map +0 -1
  66. package/lib/commonjs/js-top/engine/patches/timers.js.map +0 -1
  67. package/lib/commonjs/js-top/engine/state.js.map +0 -1
  68. package/lib/commonjs/js-top/engine/symbolicate.js.map +0 -1
  69. package/lib/commonjs/js-top/sync/jsTopSyncAdapter.js.map +0 -1
  70. package/lib/commonjs/js-top/types/index.js.map +0 -1
  71. package/lib/commonjs/js-top/utils/preferences.js.map +0 -1
  72. package/lib/commonjs/preset.js.map +0 -1
  73. package/lib/module/index.js.map +0 -1
  74. package/lib/module/js-top/components/BusySparkline.js.map +0 -1
  75. package/lib/module/js-top/components/JsTopHudOverlay.js.map +0 -1
  76. package/lib/module/js-top/components/JsTopModal.js.map +0 -1
  77. package/lib/module/js-top/components/TaskDetailView.js.map +0 -1
  78. package/lib/module/js-top/components/TaskTable.js.map +0 -1
  79. package/lib/module/js-top/engine/JsTopController.js.map +0 -1
  80. package/lib/module/js-top/engine/aggregator.js.map +0 -1
  81. package/lib/module/js-top/engine/busyProbe.js.map +0 -1
  82. package/lib/module/js-top/engine/frameStack.js.map +0 -1
  83. package/lib/module/js-top/engine/longtasks.js.map +0 -1
  84. package/lib/module/js-top/engine/now.js.map +0 -1
  85. package/lib/module/js-top/engine/originRegistry.js.map +0 -1
  86. package/lib/module/js-top/engine/patches/bridge.js.map +0 -1
  87. package/lib/module/js-top/engine/patches/microtasks.js.map +0 -1
  88. package/lib/module/js-top/engine/patches/promises.js.map +0 -1
  89. package/lib/module/js-top/engine/patches/timers.js.map +0 -1
  90. package/lib/module/js-top/engine/state.js.map +0 -1
  91. package/lib/module/js-top/engine/symbolicate.js.map +0 -1
  92. package/lib/module/js-top/sync/jsTopSyncAdapter.js.map +0 -1
  93. package/lib/module/js-top/types/index.js.map +0 -1
  94. package/lib/module/js-top/utils/preferences.js.map +0 -1
  95. package/lib/module/preset.js.map +0 -1
  96. package/lib/typescript/index.d.ts.map +0 -1
  97. package/lib/typescript/js-top/components/BusySparkline.d.ts.map +0 -1
  98. package/lib/typescript/js-top/components/JsTopHudOverlay.d.ts.map +0 -1
  99. package/lib/typescript/js-top/components/JsTopModal.d.ts.map +0 -1
  100. package/lib/typescript/js-top/components/TaskDetailView.d.ts.map +0 -1
  101. package/lib/typescript/js-top/components/TaskTable.d.ts.map +0 -1
  102. package/lib/typescript/js-top/engine/JsTopController.d.ts.map +0 -1
  103. package/lib/typescript/js-top/engine/aggregator.d.ts.map +0 -1
  104. package/lib/typescript/js-top/engine/busyProbe.d.ts.map +0 -1
  105. package/lib/typescript/js-top/engine/frameStack.d.ts.map +0 -1
  106. package/lib/typescript/js-top/engine/longtasks.d.ts.map +0 -1
  107. package/lib/typescript/js-top/engine/now.d.ts.map +0 -1
  108. package/lib/typescript/js-top/engine/originRegistry.d.ts.map +0 -1
  109. package/lib/typescript/js-top/engine/patches/bridge.d.ts.map +0 -1
  110. package/lib/typescript/js-top/engine/patches/microtasks.d.ts.map +0 -1
  111. package/lib/typescript/js-top/engine/patches/promises.d.ts.map +0 -1
  112. package/lib/typescript/js-top/engine/patches/timers.d.ts.map +0 -1
  113. package/lib/typescript/js-top/engine/state.d.ts.map +0 -1
  114. package/lib/typescript/js-top/engine/symbolicate.d.ts.map +0 -1
  115. package/lib/typescript/js-top/sync/jsTopSyncAdapter.d.ts.map +0 -1
  116. package/lib/typescript/js-top/types/index.d.ts.map +0 -1
  117. package/lib/typescript/js-top/utils/preferences.d.ts.map +0 -1
  118. package/lib/typescript/preset.d.ts.map +0 -1
@@ -1,350 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.JsTopController = void 0;
7
- var _reactNative = require("react-native");
8
- var _types = require("../types");
9
- var _now = require("./now");
10
- var _state = require("./state");
11
- var _timers = require("./patches/timers");
12
- var _microtasks = require("./patches/microtasks");
13
- var _aggregator = require("./aggregator");
14
- var _originRegistry = require("./originRegistry");
15
- var _busyProbe = require("./busyProbe");
16
- var _preferences = require("../utils/preferences");
17
- /**
18
- * JsTopController — singleton orchestrating the task-accounting engine.
19
- *
20
- * Lifecycle mirrors PerfMonitorController: the engine runs only while
21
- * someone is looking (snapshot subscribers — the HUD/modal — or a desktop
22
- * dashboard's silent remote sampling). Patches install once on first start
23
- * and stay installed; stopping just clears the `active` gate and the probe,
24
- * so a closed tool costs one boolean per schedule and nothing else.
25
- */
26
-
27
- const NOTIFY_INTERVAL_MS = _types.BUCKET_MS;
28
- class JsTopControllerImpl {
29
- enabled = false;
30
- enabledLoaded = false;
31
- paused = false;
32
- remoteSampling = false;
33
- engineRunning = false;
34
- tiers = {
35
- timers: false,
36
- promises: false,
37
- bridge: false,
38
- longtask: false
39
- };
40
- notifyHandle = null;
41
- appStateSub = null;
42
- snapshot = this.buildSnapshot();
43
- subscribers = new Set();
44
- enabledSubscribers = new Set();
45
- /** Phase-2 seam: extra tier installers (promises/bridge/longtasks) register
46
- * here so the controller stays ignorant of their internals. */
47
- tierInstallers = [];
48
- longtaskProvider = null;
49
- freezeStatsProvider = null;
50
- clearHooks = [];
51
- constructor() {
52
- void (0, _preferences.loadJsTopPrefs)().then(prefs => {
53
- this.enabledLoaded = true;
54
- if (prefs.enabled && !this.enabled) this.setEnabled(true);
55
- });
56
- }
57
-
58
- // ── observation lifecycle ────────────────────────────────────────────────
59
-
60
- subscribe(fn) {
61
- this.subscribers.add(fn);
62
- this.syncEngineState();
63
- fn(this.snapshot);
64
- return () => {
65
- this.subscribers.delete(fn);
66
- this.syncEngineState();
67
- };
68
- }
69
- subscribeEnabled(fn) {
70
- this.enabledSubscribers.add(fn);
71
- fn(this.enabled);
72
- return () => {
73
- this.enabledSubscribers.delete(fn);
74
- };
75
- }
76
-
77
- /** HUD visibility toggle (persisted). Not required for the engine to run —
78
- * the modal or a desktop watcher subscribing is what starts sampling. */
79
- setEnabled(on) {
80
- if (this.enabled === on) return;
81
- this.enabled = on;
82
- if (this.enabledLoaded) void (0, _preferences.saveJsTopPrefs)({
83
- enabled: on
84
- });
85
- this.enabledSubscribers.forEach(fn => {
86
- try {
87
- fn(on);
88
- } catch (err) {
89
- if (__DEV__) {
90
- // eslint-disable-next-line no-console
91
- console.error("[js-top] enabled subscriber threw", err);
92
- }
93
- }
94
- });
95
- this.syncEngineState();
96
- }
97
- isEnabled() {
98
- return this.enabled;
99
- }
100
-
101
- /** Silent sampling for a desktop viewer — engine runs, device HUD stays
102
- * hidden (same contract as perf-monitor's setRemoteSampling). */
103
- setRemoteSampling(on) {
104
- if (this.remoteSampling === on) return;
105
- this.remoteSampling = on;
106
- this.syncEngineState();
107
- }
108
- isRemoteSampling() {
109
- return this.remoteSampling;
110
- }
111
- getStatus() {
112
- if (!this.engineRunning) return "disabled";
113
- return this.paused ? "paused" : "running";
114
- }
115
-
116
- // ── controls ─────────────────────────────────────────────────────────────
117
-
118
- pause() {
119
- if (this.paused) return;
120
- this.paused = true;
121
- (0, _state.setActive)(false);
122
- (0, _busyProbe.stopBusyProbe)();
123
- this.emitSnapshot();
124
- }
125
- resume() {
126
- if (!this.paused) return;
127
- this.paused = false;
128
- if (this.engineRunning) {
129
- (0, _state.setActive)(true);
130
- (0, _busyProbe.startBusyProbe)();
131
- }
132
- this.emitSnapshot();
133
- }
134
- clear() {
135
- (0, _originRegistry.resetOrigins)();
136
- (0, _aggregator.resetAggregator)();
137
- for (const hook of this.clearHooks) {
138
- try {
139
- hook();
140
- } catch {
141
- // ignore
142
- }
143
- }
144
- this.emitSnapshot();
145
- }
146
- registerClearHook(fn) {
147
- this.clearHooks.push(fn);
148
- }
149
- getSnapshot() {
150
- return this.snapshot;
151
- }
152
-
153
- /**
154
- * Full detail for one origin (tap-through view). Returns null when the
155
- * origin was evicted or never existed. The pinned "unattributed" row gets
156
- * a synthetic detail built from the probe residuals.
157
- */
158
- getOriginDetail(key) {
159
- const t = (0, _now.now)();
160
- const busy = (0, _aggregator.windowBusyMs)(t);
161
- const freezeStats = this.freezeStatsProvider?.(Date.now());
162
- const freezes = freezeStats?.byOrigin[key];
163
- if (key === "unattributed") {
164
- const buckets = (0, _aggregator.windowResidualBuckets)(t);
165
- const windowMs = Math.round(buckets.reduce((sum, ms) => sum + ms, 0) * 10) / 10;
166
- return {
167
- key,
168
- label: "unattributed (events / React / native call-ins)",
169
- api: "unattributed",
170
- promoted: false,
171
- scheduleCount: 0,
172
- calls: 0,
173
- totalMs: 0,
174
- avgMs: 0,
175
- maxMs: 0,
176
- windowMs,
177
- pctOfBusy: busy > 0 ? Math.min(100, Math.round(windowMs / busy * 1000) / 10) : 0,
178
- lastSeenAgoMs: 0,
179
- buckets,
180
- freezes: freezes?.count ?? 0,
181
- worstFreezeMs: Math.round(freezes?.maxMs ?? 0)
182
- };
183
- }
184
- const base = (0, _originRegistry.getOriginDetailBase)(key, t, busy);
185
- if (!base) return null;
186
- return {
187
- ...base,
188
- freezes: freezes?.count ?? 0,
189
- worstFreezeMs: Math.round(freezes?.maxMs ?? 0)
190
- };
191
- }
192
-
193
- /** Phase-2 registration seam for additional patch tiers. */
194
- registerTierInstaller(install) {
195
- this.tierInstallers.push(install);
196
- if (this.engineRunning) {
197
- Object.assign(this.tiers, this.runInstaller(install));
198
- }
199
- }
200
- registerLongtaskProvider(fn) {
201
- this.longtaskProvider = fn;
202
- }
203
- registerFreezeStatsProvider(fn) {
204
- this.freezeStatsProvider = fn;
205
- }
206
-
207
- // ── engine ───────────────────────────────────────────────────────────────
208
-
209
- shouldRun() {
210
- return this.subscribers.size > 0 || this.remoteSampling;
211
- }
212
- syncEngineState() {
213
- const want = this.shouldRun();
214
- if (want && !this.engineRunning) this.startEngine();else if (!want && this.engineRunning) this.stopEngine();
215
- }
216
- runInstaller(install) {
217
- try {
218
- return install();
219
- } catch (err) {
220
- if (__DEV__) {
221
- // eslint-disable-next-line no-console
222
- console.error("[js-top] tier installer threw", err);
223
- }
224
- return {};
225
- }
226
- }
227
- startEngine() {
228
- this.engineRunning = true;
229
- (0, _timers.installTimerPatches)();
230
- (0, _microtasks.installMicrotaskPatches)();
231
- this.tiers = {
232
- timers: true,
233
- promises: false,
234
- bridge: false,
235
- longtask: false
236
- };
237
- for (const install of this.tierInstallers) {
238
- Object.assign(this.tiers, this.runInstaller(install));
239
- }
240
- (0, _state.setActive)(!this.paused);
241
- if (!this.paused) (0, _busyProbe.startBusyProbe)();
242
- try {
243
- this.appStateSub = _reactNative.AppState.addEventListener("change", () => {
244
- (0, _busyProbe.suppressNextGap)();
245
- });
246
- } catch {
247
- this.appStateSub = null;
248
- }
249
- // The notify tick uses the pre-patch setInterval: the engine must never
250
- // list its own heartbeat as a task.
251
- this.notifyHandle = (0, _state.getOriginals)().setInterval(() => this.emitSnapshot(), NOTIFY_INTERVAL_MS);
252
- this.emitSnapshot();
253
- }
254
- stopEngine() {
255
- this.engineRunning = false;
256
- (0, _state.setActive)(false);
257
- (0, _busyProbe.stopBusyProbe)();
258
- if (this.notifyHandle != null) {
259
- (0, _state.getOriginals)().clearInterval(this.notifyHandle);
260
- this.notifyHandle = null;
261
- }
262
- if (this.appStateSub) {
263
- try {
264
- this.appStateSub.remove();
265
- } catch {
266
- // ignore
267
- }
268
- this.appStateSub = null;
269
- }
270
- }
271
- buildSnapshot() {
272
- const t = (0, _now.now)();
273
- const busy = (0, _aggregator.windowBusyMs)(t);
274
- const attributed = (0, _aggregator.windowAttributedMs)(t);
275
- const unattributed = Math.max(0, busy - attributed);
276
- const rows = (0, _originRegistry.buildRows)(t, busy, _types.SNAPSHOT_ROWS);
277
- if (unattributed >= 1) {
278
- rows.push({
279
- key: "unattributed",
280
- label: "unattributed (events / React / native call-ins)",
281
- api: "unattributed",
282
- windowMs: Math.round(unattributed * 10) / 10,
283
- pctOfBusy: busy > 0 ? Math.min(100, Math.round(unattributed / busy * 1000) / 10) : 0,
284
- totalMs: 0,
285
- calls: 0,
286
- avgMs: 0,
287
- maxMs: 0,
288
- lastSeenAgoMs: 0,
289
- freezes: 0,
290
- worstFreezeMs: 0
291
- });
292
- rows.sort((a, b) => b.windowMs - a.windowMs || b.totalMs - a.totalMs);
293
- }
294
- // Decorate rows with freeze badges (unattributed freezes land on the
295
- // pinned "unattributed" row, whose key matches SYSTEM_FREEZE_KEY).
296
- const freezeStats = this.freezeStatsProvider?.(Date.now()) ?? {
297
- count: 0,
298
- worstMs: 0,
299
- byOrigin: {}
300
- };
301
- for (const row of rows) {
302
- const s = freezeStats.byOrigin[row.key];
303
- if (s) {
304
- row.freezes = s.count;
305
- row.worstFreezeMs = Math.round(s.maxMs);
306
- }
307
- }
308
- const windowSpanMs = _types.WINDOW_BUCKETS * _types.BUCKET_MS;
309
- return {
310
- ts: Date.now(),
311
- paused: this.paused,
312
- busyPct: Math.min(100, Math.round(busy / windowSpanMs * 1000) / 10),
313
- busyHistory: (0, _aggregator.busyHistory)(t),
314
- tiers: {
315
- ...this.tiers
316
- },
317
- rows,
318
- totals: {
319
- attributedWindowMs: Math.round(attributed * 10) / 10,
320
- unattributedWindowMs: Math.round(unattributed * 10) / 10,
321
- windowMs: windowSpanMs
322
- },
323
- freezeSummary: {
324
- count: freezeStats.count,
325
- worstMs: Math.round(freezeStats.worstMs)
326
- },
327
- longtasks: this.longtaskProvider ? this.longtaskProvider() : []
328
- };
329
- }
330
- emitSnapshot() {
331
- this.snapshot = this.buildSnapshot();
332
- this.subscribers.forEach(fn => {
333
- try {
334
- fn(this.snapshot);
335
- } catch (err) {
336
- if (__DEV__) {
337
- // eslint-disable-next-line no-console
338
- console.error("[js-top] snapshot subscriber threw", err);
339
- }
340
- }
341
- });
342
- }
343
-
344
- /** Engine active state, for diagnostics/tests. */
345
- isEngineActive() {
346
- return (0, _state.isActive)();
347
- }
348
- }
349
- const JsTopController = exports.JsTopController = new JsTopControllerImpl();
350
- //# sourceMappingURL=JsTopController.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.JsTopController=void 0;var _reactNative=require("react-native");var _types=require("../types");var _now=require("./now");var _state=require("./state");var _timers=require("./patches/timers");var _microtasks=require("./patches/microtasks");var _aggregator=require("./aggregator");var _originRegistry=require("./originRegistry");var _busyProbe=require("./busyProbe");var _preferences=require("../utils/preferences");const NOTIFY_INTERVAL_MS=_types.BUCKET_MS;class JsTopControllerImpl{enabled=false;enabledLoaded=false;paused=false;remoteSampling=false;engineRunning=false;tiers={timers:false,promises:false,bridge:false,longtask:false};notifyHandle=null;appStateSub=null;snapshot=this.buildSnapshot();subscribers=new Set;enabledSubscribers=new Set;tierInstallers=[];longtaskProvider=null;freezeStatsProvider=null;clearHooks=[];constructor(){void(0,_preferences.loadJsTopPrefs)().then(e=>{this.enabledLoaded=true;if(e.enabled&&!this.enabled)this.setEnabled(true)})}subscribe(e){this.subscribers.add(e);this.syncEngineState();e(this.snapshot);return()=>{this.subscribers.delete(e);this.syncEngineState()}}subscribeEnabled(e){this.enabledSubscribers.add(e);e(this.enabled);return()=>{this.enabledSubscribers.delete(e)}}setEnabled(e){if(this.enabled===e)return;this.enabled=e;if(this.enabledLoaded)void(0,_preferences.saveJsTopPrefs)({enabled:e});this.enabledSubscribers.forEach(t=>{try{t(e)}catch(i){if(__DEV__){console.error("[js-top] enabled subscriber threw",i)}}});this.syncEngineState()}isEnabled(){return this.enabled}setRemoteSampling(e){if(this.remoteSampling===e)return;this.remoteSampling=e;this.syncEngineState()}isRemoteSampling(){return this.remoteSampling}getStatus(){if(!this.engineRunning)return"disabled";return this.paused?"paused":"running"}pause(){if(this.paused)return;this.paused=true;(0,_state.setActive)(false);(0,_busyProbe.stopBusyProbe)();this.emitSnapshot()}resume(){if(!this.paused)return;this.paused=false;if(this.engineRunning){(0,_state.setActive)(true);(0,_busyProbe.startBusyProbe)()}this.emitSnapshot()}clear(){(0,_originRegistry.resetOrigins)();(0,_aggregator.resetAggregator)();for(const e of this.clearHooks){try{e()}catch{}}this.emitSnapshot()}registerClearHook(e){this.clearHooks.push(e)}getSnapshot(){return this.snapshot}getOriginDetail(e){const t=(0,_now.now)();const i=(0,_aggregator.windowBusyMs)(t);const a=this.freezeStatsProvider?.(Date.now());const s=a?.byOrigin[e];if(e==="unattributed"){const u=(0,_aggregator.windowResidualBuckets)(t);const r=Math.round(u.reduce((n,l)=>n+l,0)*10)/10;return{key:e,label:"unattributed (events / React / native call-ins)",api:"unattributed",promoted:false,scheduleCount:0,calls:0,totalMs:0,avgMs:0,maxMs:0,windowMs:r,pctOfBusy:i>0?Math.min(100,Math.round(r/i*1e3)/10):0,lastSeenAgoMs:0,buckets:u,freezes:s?.count??0,worstFreezeMs:Math.round(s?.maxMs??0)}}const o=(0,_originRegistry.getOriginDetailBase)(e,t,i);if(!o)return null;return{...o,freezes:s?.count??0,worstFreezeMs:Math.round(s?.maxMs??0)}}registerTierInstaller(e){this.tierInstallers.push(e);if(this.engineRunning){Object.assign(this.tiers,this.runInstaller(e))}}registerLongtaskProvider(e){this.longtaskProvider=e}registerFreezeStatsProvider(e){this.freezeStatsProvider=e}shouldRun(){return this.subscribers.size>0||this.remoteSampling}syncEngineState(){const e=this.shouldRun();if(e&&!this.engineRunning)this.startEngine();else if(!e&&this.engineRunning)this.stopEngine()}runInstaller(e){try{return e()}catch(t){if(__DEV__){console.error("[js-top] tier installer threw",t)}return{}}}startEngine(){this.engineRunning=true;(0,_timers.installTimerPatches)();(0,_microtasks.installMicrotaskPatches)();this.tiers={timers:true,promises:false,bridge:false,longtask:false};for(const e of this.tierInstallers){Object.assign(this.tiers,this.runInstaller(e))}(0,_state.setActive)(!this.paused);if(!this.paused)(0,_busyProbe.startBusyProbe)();try{this.appStateSub=_reactNative.AppState.addEventListener("change",()=>{(0,_busyProbe.suppressNextGap)()})}catch{this.appStateSub=null}this.notifyHandle=(0,_state.getOriginals)().setInterval(()=>this.emitSnapshot(),NOTIFY_INTERVAL_MS);this.emitSnapshot()}stopEngine(){this.engineRunning=false;(0,_state.setActive)(false);(0,_busyProbe.stopBusyProbe)();if(this.notifyHandle!=null){(0,_state.getOriginals)().clearInterval(this.notifyHandle);this.notifyHandle=null}if(this.appStateSub){try{this.appStateSub.remove()}catch{}this.appStateSub=null}}buildSnapshot(){const e=(0,_now.now)();const t=(0,_aggregator.windowBusyMs)(e);const i=(0,_aggregator.windowAttributedMs)(e);const a=Math.max(0,t-i);const s=(0,_originRegistry.buildRows)(e,t,_types.SNAPSHOT_ROWS);if(a>=1){s.push({key:"unattributed",label:"unattributed (events / React / native call-ins)",api:"unattributed",windowMs:Math.round(a*10)/10,pctOfBusy:t>0?Math.min(100,Math.round(a/t*1e3)/10):0,totalMs:0,calls:0,avgMs:0,maxMs:0,lastSeenAgoMs:0,freezes:0,worstFreezeMs:0});s.sort((r,n)=>n.windowMs-r.windowMs||n.totalMs-r.totalMs)}const o=this.freezeStatsProvider?.(Date.now())??{count:0,worstMs:0,byOrigin:{}};for(const r of s){const n=o.byOrigin[r.key];if(n){r.freezes=n.count;r.worstFreezeMs=Math.round(n.maxMs)}}const u=_types.WINDOW_BUCKETS*_types.BUCKET_MS;return{ts:Date.now(),paused:this.paused,busyPct:Math.min(100,Math.round(t/u*1e3)/10),busyHistory:(0,_aggregator.busyHistory)(e),tiers:{...this.tiers},rows:s,totals:{attributedWindowMs:Math.round(i*10)/10,unattributedWindowMs:Math.round(a*10)/10,windowMs:u},freezeSummary:{count:o.count,worstMs:Math.round(o.worstMs)},longtasks:this.longtaskProvider?this.longtaskProvider():[]}}emitSnapshot(){this.snapshot=this.buildSnapshot();this.subscribers.forEach(e=>{try{e(this.snapshot)}catch(t){if(__DEV__){console.error("[js-top] snapshot subscriber threw",t)}}})}isEngineActive(){return(0,_state.isActive)()}}const JsTopController=exports.JsTopController=new JsTopControllerImpl;
@@ -1,148 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.bookAttributedRange = bookAttributedRange;
7
- exports.bookBusyRange = bookBusyRange;
8
- exports.bookLongtaskRange = bookLongtaskRange;
9
- exports.busyHistory = busyHistory;
10
- exports.epochOf = epochOf;
11
- exports.resetAggregator = resetAggregator;
12
- exports.windowAttributedMs = windowAttributedMs;
13
- exports.windowBusyMs = windowBusyMs;
14
- exports.windowResidualBuckets = windowResidualBuckets;
15
- var _types = require("../types");
16
- /**
17
- * Global per-bucket accounting: the engine's clock is divided into 250ms
18
- * buckets (epochs). Each bucket tracks measured busy ms (from the probe) and
19
- * attributed ms (from wrapped tasks); `unattributed = busy − attributed` per
20
- * bucket is what keeps the table honest when work enters the thread through
21
- * a path we can't wrap (bridgeless events, React commits, async/await).
22
- *
23
- * Rings are epoch-stamped parallel arrays: a slot is only valid when its
24
- * stamp matches the epoch being read, so stale slots cost nothing to skip
25
- * and nothing to zero eagerly.
26
- */
27
-
28
- const busyMs = new Float64Array(_types.HISTORY_BUCKETS);
29
- const attributedMs = new Float64Array(_types.HISTORY_BUCKETS);
30
- const longtaskMs = new Float64Array(_types.HISTORY_BUCKETS);
31
- const stamps = new Float64Array(_types.HISTORY_BUCKETS).fill(-1);
32
- function epochOf(t) {
33
- return Math.floor(t / _types.BUCKET_MS);
34
- }
35
- function slotFor(epoch) {
36
- const i = epoch % _types.HISTORY_BUCKETS;
37
- if (stamps[i] !== epoch) {
38
- stamps[i] = epoch;
39
- busyMs[i] = 0;
40
- attributedMs[i] = 0;
41
- longtaskMs[i] = 0;
42
- }
43
- return i;
44
- }
45
-
46
- /**
47
- * Distribute a wall-clock span across the buckets it overlaps. Spans older
48
- * than the history ring are clamped — a pathological hours-long gap books at
49
- * most one ring's worth of buckets.
50
- */
51
- function distribute(start, end, add) {
52
- if (!(end > start)) return;
53
- const clampedStart = Math.max(start, end - _types.HISTORY_BUCKETS * _types.BUCKET_MS);
54
- let e = epochOf(clampedStart);
55
- const lastEpoch = epochOf(end);
56
- let cursor = clampedStart;
57
- while (e <= lastEpoch) {
58
- const bucketEnd = (e + 1) * _types.BUCKET_MS;
59
- const sliceEnd = Math.min(end, bucketEnd);
60
- const ms = sliceEnd - cursor;
61
- if (ms > 0) add(slotFor(e), ms);
62
- cursor = sliceEnd;
63
- e++;
64
- }
65
- }
66
-
67
- /** Book probe-measured busy time over [start, end], capped per bucket. */
68
- function bookBusyRange(start, end) {
69
- distribute(start, end, (i, ms) => {
70
- busyMs[i] = Math.min(_types.BUCKET_MS, busyMs[i] + ms);
71
- });
72
- }
73
-
74
- /**
75
- * Book a task's exclusive ms, spread proportionally over its wall span.
76
- * (Exclusive time isn't contiguous when children interleave; proportional
77
- * spread is exact at 250ms-bucket granularity for all practical tasks.)
78
- */
79
- function bookAttributedRange(start, end, exclusiveMs) {
80
- const span = end - start;
81
- if (!(span > 0) || !(exclusiveMs > 0)) return;
82
- const ratio = Math.min(1, exclusiveMs / span);
83
- distribute(start, end, (i, ms) => {
84
- attributedMs[i] = Math.min(_types.BUCKET_MS, attributedMs[i] + ms * ratio);
85
- });
86
- }
87
-
88
- /** Book a reported longtask span (for history display only). */
89
- function bookLongtaskRange(start, end) {
90
- distribute(start, end, (i, ms) => {
91
- longtaskMs[i] = Math.min(_types.BUCKET_MS, longtaskMs[i] + ms);
92
- });
93
- }
94
- function sumWindow(arr, nowT, buckets) {
95
- const current = epochOf(nowT);
96
- let sum = 0;
97
- for (let e = current - buckets + 1; e <= current; e++) {
98
- const i = e % _types.HISTORY_BUCKETS;
99
- if (i >= 0 && stamps[i] === e) sum += arr[i];
100
- }
101
- return sum;
102
- }
103
- function windowBusyMs(nowT) {
104
- return sumWindow(busyMs, nowT, _types.WINDOW_BUCKETS);
105
- }
106
- function windowAttributedMs(nowT) {
107
- return sumWindow(attributedMs, nowT, _types.WINDOW_BUCKETS);
108
- }
109
-
110
- /**
111
- * Per-bucket UNATTRIBUTED ms (busy − attributed), oldest→newest over the
112
- * rolling window — the "system" row's activity trend for the detail view.
113
- */
114
- function windowResidualBuckets(nowT) {
115
- const current = epochOf(nowT);
116
- const out = [];
117
- for (let e = current - _types.WINDOW_BUCKETS + 1; e <= current; e++) {
118
- const i = e % _types.HISTORY_BUCKETS;
119
- if (i >= 0 && stamps[i] === e) {
120
- out.push(Math.max(0, Math.round((busyMs[i] - attributedMs[i]) * 10) / 10));
121
- } else {
122
- out.push(0);
123
- }
124
- }
125
- return out;
126
- }
127
-
128
- /** Per-bucket busy%, oldest→newest, over the full 30s history ring. */
129
- function busyHistory(nowT) {
130
- const current = epochOf(nowT);
131
- const out = [];
132
- for (let e = current - _types.HISTORY_BUCKETS + 1; e <= current; e++) {
133
- const i = e % _types.HISTORY_BUCKETS;
134
- if (i >= 0 && stamps[i] === e) {
135
- out.push(Math.round(busyMs[i] / _types.BUCKET_MS * 100));
136
- } else {
137
- out.push(0);
138
- }
139
- }
140
- // Trim the leading dead zone from before the engine started.
141
- let firstLive = 0;
142
- while (firstLive < out.length && out[firstLive] === 0) firstLive++;
143
- return out.slice(Math.max(0, Math.min(firstLive, out.length - 1)));
144
- }
145
- function resetAggregator() {
146
- stamps.fill(-1);
147
- }
148
- //# sourceMappingURL=aggregator.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.bookAttributedRange=bookAttributedRange;exports.bookBusyRange=bookBusyRange;exports.bookLongtaskRange=bookLongtaskRange;exports.busyHistory=busyHistory;exports.epochOf=epochOf;exports.resetAggregator=resetAggregator;exports.windowAttributedMs=windowAttributedMs;exports.windowBusyMs=windowBusyMs;exports.windowResidualBuckets=windowResidualBuckets;var _types=require("../types");const busyMs=new Float64Array(_types.HISTORY_BUCKETS);const attributedMs=new Float64Array(_types.HISTORY_BUCKETS);const longtaskMs=new Float64Array(_types.HISTORY_BUCKETS);const stamps=new Float64Array(_types.HISTORY_BUCKETS).fill(-1);function epochOf(o){return Math.floor(o/_types.BUCKET_MS)}function slotFor(o){const t=o%_types.HISTORY_BUCKETS;if(stamps[t]!==o){stamps[t]=o;busyMs[t]=0;attributedMs[t]=0;longtaskMs[t]=0}return t}function distribute(o,t,n){if(!(t>o))return;const e=Math.max(o,t-_types.HISTORY_BUCKETS*_types.BUCKET_MS);let s=epochOf(e);const r=epochOf(t);let u=e;while(s<=r){const a=(s+1)*_types.BUCKET_MS;const i=Math.min(t,a);const c=i-u;if(c>0)n(slotFor(s),c);u=i;s++}}function bookBusyRange(o,t){distribute(o,t,(n,e)=>{busyMs[n]=Math.min(_types.BUCKET_MS,busyMs[n]+e)})}function bookAttributedRange(o,t,n){const e=t-o;if(!(e>0)||!(n>0))return;const s=Math.min(1,n/e);distribute(o,t,(r,u)=>{attributedMs[r]=Math.min(_types.BUCKET_MS,attributedMs[r]+u*s)})}function bookLongtaskRange(o,t){distribute(o,t,(n,e)=>{longtaskMs[n]=Math.min(_types.BUCKET_MS,longtaskMs[n]+e)})}function sumWindow(o,t,n){const e=epochOf(t);let s=0;for(let r=e-n+1;r<=e;r++){const u=r%_types.HISTORY_BUCKETS;if(u>=0&&stamps[u]===r)s+=o[u]}return s}function windowBusyMs(o){return sumWindow(busyMs,o,_types.WINDOW_BUCKETS)}function windowAttributedMs(o){return sumWindow(attributedMs,o,_types.WINDOW_BUCKETS)}function windowResidualBuckets(o){const t=epochOf(o);const n=[];for(let e=t-_types.WINDOW_BUCKETS+1;e<=t;e++){const s=e%_types.HISTORY_BUCKETS;if(s>=0&&stamps[s]===e){n.push(Math.max(0,Math.round((busyMs[s]-attributedMs[s])*10)/10))}else{n.push(0)}}return n}function busyHistory(o){const t=epochOf(o);const n=[];for(let s=t-_types.HISTORY_BUCKETS+1;s<=t;s++){const r=s%_types.HISTORY_BUCKETS;if(r>=0&&stamps[r]===s){n.push(Math.round(busyMs[r]/_types.BUCKET_MS*100))}else{n.push(0)}}let e=0;while(e<n.length&&n[e]===0)e++;return n.slice(Math.max(0,Math.min(e,n.length-1)))}function resetAggregator(){stamps.fill(-1)}
@@ -1,101 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.debugBaseline = debugBaseline;
7
- exports.startBusyProbe = startBusyProbe;
8
- exports.stopBusyProbe = stopBusyProbe;
9
- exports.suppressNextGap = suppressNextGap;
10
- var _now = require("./now");
11
- var _state = require("./state");
12
- var _aggregator = require("./aggregator");
13
- /**
14
- * Busy probe — measures true JS-thread occupancy from timer-gap inflation.
15
- *
16
- * A 16ms heartbeat (created with the PRE-PATCH setInterval so the probe never
17
- * appears in its own table) can only fire when the thread yields; any gap
18
- * beyond the calibrated idle baseline is time the thread was busy. This is
19
- * the same physical signal as perf-monitor's stall-aware sampler, at higher
20
- * frequency: multi-second stalls simply arrive as one huge gap and are booked
21
- * as fully-busy buckets by bookBusyRange (the backfill falls out of the
22
- * range-distribution math; no separate synthetic-sample pass needed).
23
- *
24
- * Calibration: the nominal 16ms is never what a real device delivers at
25
- * idle. We track a rolling min gap (3 one-second slots) and subtract THAT,
26
- * otherwise idle devices read 10–20% phantom busy.
27
- *
28
- * AppState transitions also pause JS timers, so the controller flags them
29
- * via suppressNextGap() — same trick as PerfMonitorController; AppState
30
- * events arrive on the JS thread, so a genuine stall can never set the flag.
31
- */
32
-
33
- const PROBE_INTERVAL_MS = 16;
34
- /** Ignore gaps below this when calibrating (a busy tick isn't a baseline). */
35
- const CALIBRATION_MAX_GAP_MS = 100;
36
- const BASELINE_SLOTS = 3;
37
- const MIN_BASELINE_MS = 8;
38
- let handle = null;
39
- let lastAt = 0;
40
- let suppressNext = false;
41
- let slotMins = [];
42
- let currentSlotMin = Infinity;
43
- let currentSlotStart = 0;
44
- let baseline = PROBE_INTERVAL_MS;
45
- function buoyJsTopProbeTick() {
46
- const t = (0, _now.now)();
47
- const gap = lastAt > 0 ? t - lastAt : 0;
48
- lastAt = t;
49
- if (suppressNext) {
50
- // Backgrounding/foregrounding paused timers — not a stall. Reset
51
- // calibration too: refresh-rate or thermal state may have changed.
52
- suppressNext = false;
53
- currentSlotMin = Infinity;
54
- currentSlotStart = t;
55
- return;
56
- }
57
- if (gap <= 0) return;
58
- if (gap < CALIBRATION_MAX_GAP_MS) {
59
- if (gap < currentSlotMin) currentSlotMin = gap;
60
- if (currentSlotStart === 0) currentSlotStart = t;
61
- if (t - currentSlotStart >= 1000) {
62
- slotMins.push(currentSlotMin);
63
- if (slotMins.length > BASELINE_SLOTS) slotMins.shift();
64
- baseline = Math.max(MIN_BASELINE_MS, Math.min(...slotMins));
65
- currentSlotMin = Infinity;
66
- currentSlotStart = t;
67
- }
68
- }
69
- const busyExcess = gap - baseline;
70
- if (busyExcess > 1) {
71
- // The block ended when this tick finally fired — position it at the tail.
72
- (0, _aggregator.bookBusyRange)(t - busyExcess, t);
73
- }
74
- }
75
- function startBusyProbe() {
76
- if (handle != null) return;
77
- const originals = (0, _state.getOriginals)();
78
- lastAt = (0, _now.now)();
79
- suppressNext = false;
80
- slotMins = [];
81
- currentSlotMin = Infinity;
82
- currentSlotStart = 0;
83
- baseline = PROBE_INTERVAL_MS;
84
- handle = originals.setInterval(buoyJsTopProbeTick, PROBE_INTERVAL_MS);
85
- }
86
- function stopBusyProbe() {
87
- if (handle != null) {
88
- (0, _state.getOriginals)().clearInterval(handle);
89
- handle = null;
90
- }
91
- lastAt = 0;
92
- }
93
- function suppressNextGap() {
94
- suppressNext = true;
95
- }
96
-
97
- /** Test-only. */
98
- function debugBaseline() {
99
- return baseline;
100
- }
101
- //# sourceMappingURL=busyProbe.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.debugBaseline=debugBaseline;exports.startBusyProbe=startBusyProbe;exports.stopBusyProbe=stopBusyProbe;exports.suppressNextGap=suppressNextGap;var _now=require("./now");var _state=require("./state");var _aggregator=require("./aggregator");const PROBE_INTERVAL_MS=16;const CALIBRATION_MAX_GAP_MS=100;const BASELINE_SLOTS=3;const MIN_BASELINE_MS=8;let handle=null;let lastAt=0;let suppressNext=false;let slotMins=[];let currentSlotMin=Infinity;let currentSlotStart=0;let baseline=PROBE_INTERVAL_MS;function buoyJsTopProbeTick(){const t=(0,_now.now)();const e=lastAt>0?t-lastAt:0;lastAt=t;if(suppressNext){suppressNext=false;currentSlotMin=Infinity;currentSlotStart=t;return}if(e<=0)return;if(e<CALIBRATION_MAX_GAP_MS){if(e<currentSlotMin)currentSlotMin=e;if(currentSlotStart===0)currentSlotStart=t;if(t-currentSlotStart>=1e3){slotMins.push(currentSlotMin);if(slotMins.length>BASELINE_SLOTS)slotMins.shift();baseline=Math.max(MIN_BASELINE_MS,Math.min(...slotMins));currentSlotMin=Infinity;currentSlotStart=t}}const n=e-baseline;if(n>1){(0,_aggregator.bookBusyRange)(t-n,t)}}function startBusyProbe(){if(handle!=null)return;const t=(0,_state.getOriginals)();lastAt=(0,_now.now)();suppressNext=false;slotMins=[];currentSlotMin=Infinity;currentSlotStart=0;baseline=PROBE_INTERVAL_MS;handle=t.setInterval(buoyJsTopProbeTick,PROBE_INTERVAL_MS)}function stopBusyProbe(){if(handle!=null){(0,_state.getOriginals)().clearInterval(handle);handle=null}lastAt=0}function suppressNextGap(){suppressNext=true}function debugBaseline(){return baseline}