srt-browser-demo / probe.html
RiverRider's picture
record how far a load got, so a memory kill reports its own ceiling
d4e5465 verified
Raw
History Blame Contribute Delete
5.33 kB
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Memory probe — how much will this browser give us?</title>
<style>
body { font: 15px/1.5 -apple-system, system-ui, sans-serif; max-width: 40rem;
margin: 2rem auto; padding: 0 1rem; background: #131233; color: #eae7fb; }
h1 { font-size: 1.3rem; }
button { font: inherit; padding: .6rem 1rem; margin: .3rem .3rem .3rem 0;
background: #a48dff; color: #131233; border: 0; border-radius: 6px; }
pre { background: #1c1a47; padding: .8rem; border-radius: 6px; white-space: pre-wrap;
border: 1px solid #332e66; }
.big { font-size: 1.6rem; color: #a48dff; }
small { color: #a7a1cc; }
</style>
<h1>Memory probe</h1>
<p>This page finds out how much memory this browser will actually hand to a web
page before killing the tab. It stores its progress as it goes, so if the tab
dies the number survives. <strong>If the page reloads itself, come back and read
the result.</strong></p>
<div id="verdict"></div>
<button id="inc">Run incremental probe</button>
<button id="one">Run single-block probe</button>
<button id="reset">Clear</button>
<pre id="out">idle</pre>
<script>
const out = document.getElementById("out");
const verdict = document.getElementById("verdict");
const say = (m) => { out.textContent += "\n" + m; };
const K = {
hw: "probe_highwater_mb", running: "probe_running",
mode: "probe_mode", block: "probe_block_mb",
};
// localStorage is synchronous, so a value written before an out-of-memory kill
// is still there when the tab comes back.
function report() {
const running = localStorage.getItem(K.running);
const hw = localStorage.getItem(K.hw);
const mode = localStorage.getItem(K.mode);
const blk = localStorage.getItem(K.block);
if (running === "1" && hw) {
verdict.innerHTML =
`<p class="big">This browser died at about ${hw} MB.</p>` +
`<p><small>mode: ${mode || "?"}${blk ? ", last single block tried: " + blk + " MB" : ""}. ` +
`That is the ceiling we have to fit under.</small></p>`;
localStorage.setItem(K.running, "0");
} else if (hw) {
verdict.innerHTML =
`<p class="big">Reached ${hw} MB without dying.</p>` +
`<p><small>mode: ${mode || "?"}. The probe finished or was stopped.</small></p>`;
}
}
report();
const env = [];
if (navigator.deviceMemory) env.push(`deviceMemory ${navigator.deviceMemory} GB`);
env.push(`cores ${navigator.hardwareConcurrency || "?"}`);
out.textContent = env.join(", ");
if (navigator.storage && navigator.storage.estimate) {
navigator.storage.estimate().then(e =>
say(`storage quota ${Math.round(e.quota / 1e6)} MB, used ${Math.round(e.usage / 1e6)} MB`));
}
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
// Touch one byte per 4 KB page: allocation alone can be lazy, and a page that
// is never written is not really resident.
function commit(buf) {
const u = new Uint8Array(buf);
for (let i = 0; i < u.length; i += 4096) u[i] = 1;
return u.length;
}
document.getElementById("inc").onclick = async () => {
const STEP = 32;
localStorage.setItem(K.running, "1");
localStorage.setItem(K.mode, `incremental ${STEP} MB steps`);
localStorage.setItem(K.hw, "0");
const held = [];
say(`\nallocating in ${STEP} MB steps, touching every page`);
for (let mb = STEP; mb <= 2048; mb += STEP) {
try {
held.push(new ArrayBuffer(STEP * 1024 * 1024));
commit(held[held.length - 1]);
} catch (e) {
say(`refused at ${mb} MB (${e.name}) — this browser threw instead of dying, which is the good case`);
localStorage.setItem(K.running, "0");
return report();
}
localStorage.setItem(K.hw, String(mb));
out.textContent = env.join(", ") + `\n\nheld: ${mb} MB`;
await sleep(30);
}
say("reached 2048 MB, stopping");
localStorage.setItem(K.running, "0");
report();
};
document.getElementById("one").onclick = async () => {
localStorage.setItem(K.running, "1");
localStorage.setItem(K.mode, "single contiguous block");
localStorage.setItem(K.hw, "0");
say("\nsingle contiguous allocations (this is what loading one big model file does)");
for (const mb of [128, 256, 384, 512, 640, 768, 1024]) {
localStorage.setItem(K.block, String(mb));
try {
const b = new ArrayBuffer(mb * 1024 * 1024);
commit(b);
say(`${mb} MB contiguous: ok`);
localStorage.setItem(K.hw, String(mb));
} catch (e) {
say(`${mb} MB contiguous: refused (${e.name})`);
break;
}
await sleep(120);
}
localStorage.setItem(K.running, "0");
report();
};
document.getElementById("reset").onclick = () => {
Object.values(K).forEach(k => localStorage.removeItem(k));
verdict.innerHTML = "";
say("cleared");
};
// Auto-start: the button was easy to miss, and the whole point is that opening
// the page is the entire interaction.
if (localStorage.getItem(K.running) !== "1") {
let n = 3;
const tick = setInterval(() => {
document.getElementById("inc").textContent =
n > 0 ? `Starting in ${n}… (tap to stop)` : "Running";
if (n-- <= 0) { clearInterval(tick); document.getElementById("inc").click(); }
}, 1000);
document.getElementById("inc").addEventListener("click", () => clearInterval(tick), { once: true });
}
</script>