(win: BrowserWindow, name: string)
| 5 | const samplePeriod = 15000 |
| 6 | |
| 7 | export function createUnresponsiveSampler(win: BrowserWindow, name: string) { |
| 8 | let sampleTimer: ReturnType<typeof setTimeout> | undefined |
| 9 | let stopTimer: ReturnType<typeof setTimeout> | undefined |
| 10 | let sampling = false |
| 11 | const samples = new Map<string, number>() |
| 12 | |
| 13 | const active = () => sampling && !win.isDestroyed() && !win.webContents.isDestroyed() |
| 14 | const clearTimers = () => { |
| 15 | if (sampleTimer) clearTimeout(sampleTimer) |
| 16 | if (stopTimer) clearTimeout(stopTimer) |
| 17 | sampleTimer = undefined |
| 18 | stopTimer = undefined |
| 19 | } |
| 20 | |
| 21 | const schedule = () => { |
| 22 | sampleTimer = setTimeout(() => { |
| 23 | void collect() |
| 24 | }, sampleInterval) |
| 25 | } |
| 26 | |
| 27 | const collect = async () => { |
| 28 | if (!active()) return |
| 29 | const stack = await win.webContents.mainFrame.collectJavaScriptCallStack().catch((error) => { |
| 30 | writeLog("window", "failed to collect unresponsive sample", { window: name, error }, "error") |
| 31 | return undefined |
| 32 | }) |
| 33 | if (!active()) return |
| 34 | if (stack) samples.set(stack, (samples.get(stack) ?? 0) + 1) |
| 35 | schedule() |
| 36 | } |
| 37 | |
| 38 | const stopAndFlush = () => { |
| 39 | const wasSampling = sampling |
| 40 | sampling = false |
| 41 | clearTimers() |
| 42 | if (samples.size === 0) return wasSampling |
| 43 | |
| 44 | const entries = [...samples.entries()].sort((a, b) => b[1] - a[1]) |
| 45 | const total = entries.reduce((sum, entry) => sum + entry[1], 0) |
| 46 | const message = [ |
| 47 | "renderer unresponsive samples", |
| 48 | `Window: ${name}`, |
| 49 | `URL: ${win.isDestroyed() ? "<destroyed>" : win.webContents.getURL()}`, |
| 50 | ...entries.map((entry) => `<${entry[1]}> ${entry[0]}`), |
| 51 | `Total Samples: ${total}`, |
| 52 | ].join("\n") |
| 53 | writeLog("window", message, undefined, "error") |
| 54 | samples.clear() |
| 55 | return wasSampling |
| 56 | } |
| 57 | |
| 58 | const start = () => { |
| 59 | if (sampling || win.isDestroyed() || win.webContents.isDestroyed() || win.webContents.isDevToolsOpened()) return |
| 60 | sampling = true |
| 61 | samples.clear() |
| 62 | schedule() |
| 63 | stopTimer = setTimeout(stopAndFlush, samplePeriod) |
| 64 | } |
no test coverage detected