<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>hprof-analyzer</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css">
<style>
%%STYLE_CSS%%
</style>
</head>
<body>
<div id="upload-screen">
<div id="landing-hero">
<div id="landing-logo">⚙</div>
<h1 id="landing-title">hprof-analyzer</h1>
<p id="landing-tagline">Java heap dump analysis — in your browser</p>
<p id="landing-desc">
The web edition of <a href="https://github.com/parttimenerd/hprof-analyzer" target="_blank" rel="noopener">hprof-analyzer</a>,
a fast Rust-based tool for investigating Java OutOfMemoryErrors and memory leaks.
Your heap dump never leaves your machine — everything runs locally via WebAssembly.
</p>
<ul id="landing-features">
<li><span class="feat-icon">🔍</span> Leak suspects, dominator tree & retained sizes</li>
<li><span class="feat-icon">📊</span> Class histogram, GC roots, allocation sites</li>
<li><span class="feat-icon">🧵</span> Thread stacks, locals, and per-thread objects</li>
<li><span class="feat-icon">💬</span> SQL-like OQL query shell with tab-completion</li>
<li><span class="feat-icon">🔒</span> Fully private — no upload, no server, no telemetry</li>
</ul>
</div>
<div id="drop-zone">
<input type="file" id="file-input" accept=".hprof">
<label for="file-input">
<div id="drop-zone-icon">📂</div>
<div id="drop-zone-text">Drop a <strong>.hprof</strong> file here, or click to select</div>
<div id="drop-zone-hint">Supports up to <strong>3 GB</strong> · gzip-compressed dumps accepted</div>
</label>
</div>
<div id="mode-buttons" style="display:none">
<button id="btn-analyze-report">
<span class="mode-icon">📊</span>
<span class="mode-title">Analysis</span>
<span class="mode-desc">Histogram, leak suspects, dominator tree, GC roots, and retained sizes</span>
</button>
<button id="btn-full-analysis">
<span class="mode-icon">🔬</span>
<span class="mode-title">Full Analysis</span>
<span class="mode-desc">Analysis + duplicate string/array detection and collection fill-ratio</span>
</button>
<button id="btn-oql-shell">
<span class="mode-icon">💬</span>
<span class="mode-title">OQL Shell</span>
<span class="mode-desc">Interactive query shell with tab-completion and named built-in queries</span>
</button>
</div>
<div id="sample-section" style="display:none">
<p class="sample-label">— or try a sample dump —</p>
<div id="sample-list"></div>
</div>
<p id="wasm-load-status" style="display:none"></p>
<div id="landing-footer">
<a href="https://github.com/parttimenerd/hprof-analyzer" target="_blank" rel="noopener">GitHub</a>
<span class="sep">·</span>
<a href="https://github.com/parttimenerd/hprof-analyzer/blob/main/LICENSE" target="_blank" rel="noopener">MIT License</a>
<span class="sep">·</span>
<span>CLI: <a href="https://crates.io/crates/hprof-analyzer" target="_blank" rel="noopener"><code>cargo install hprof-analyzer</code></a></span>
<span class="sep">·</span>
<button id="btn-theme-toggle" title="Cycle theme: auto / light / dark" class="footer-theme-btn">◐ Theme: Auto</button>
</div>
</div>
<div id="connect-screen" style="display:none">
<h1>hprof-analyzer</h1>
<p class="subtitle">Browser UI for Java heap dump analysis</p>
<div class="card">
<h2>Connect to local server</h2>
<p>
Start the local server first:<br>
<code>hprof-analyzer query heap.hprof --server</code>
</p>
<div class="row">
<input id="server-url" type="text" value="http://localhost:7070"
placeholder="http://localhost:7070" spellcheck="false">
<button id="btn-connect">Connect</button>
</div>
<div id="connect-status"></div>
</div>
<div class="card offline-card">
<h2>Named queries (offline preview)</h2>
<p>These 20 built-in queries are loaded from the embedded WASM module:</p>
<div id="offline-query-list"></div>
</div>
</div>
<div id="report-screen" style="display:none">
<div id="report-topbar">
<button id="btn-new-file">↩ New file</button>
<button id="btn-to-shell" title="Switch to OQL shell (Ctrl+Shift+R)">⌨ OQL Shell</button>
<button id="btn-open-report" title="Open full interactive report in a new tab">⧉ Full Report</button>
<button id="btn-download-report" title="Download self-contained HTML report file" style="display:none">⬇ Download</button>
<button id="btn-theme-toggle" title="Cycle theme: auto / light / dark">◐ Theme: Auto</button>
</div>
<div id="report-container">
<p id="report-message"></p>
</div>
</div>
<div id="shell-screen" style="display:none">
<div id="topbar">
<span id="server-badge">● Connected</span>
<span id="server-url-display"></span>
<div id="topbar-actions">
<button id="btn-dashboard" title="Starred results dashboard — hover the terminal to star or visualise the last query result">★ Dashboard</button>
<button id="btn-show-report" title="Show analysis report (Ctrl+Shift+R)">
Show Report
</button>
<span id="analyze-status"></span>
<button id="btn-theme-toggle-shell" title="Cycle theme: auto / light / dark">◐ Theme: Auto</button>
<button id="btn-disconnect">✕ Disconnect</button>
</div>
</div>
<div id="main-area">
<div id="sidebar">
<div class="sidebar-header">Named Queries</div>
<div class="sidebar-search-wrap">
<input id="sidebar-search" type="text" placeholder="Filter queries…" spellcheck="false" autocomplete="off">
</div>
<div id="named-query-list"></div>
</div>
<div id="terminal-container"></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
<script type="text/plain" id="wasm-glue-src">%%WASM_JS%%</script>
<script type="text/plain" id="wasm-b64-src">%%WASM_B64%%</script>
<script>%%REACT_BUNDLE%%</script>
<script type="module">
const _wasmGlue = document.getElementById('wasm-glue-src').textContent;
const _wasmB64 = document.getElementById('wasm-b64-src').textContent.trim();
function _b64toBytes(s) {
const bin = atob(s);
const buf = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
return buf;
}
const _glueBlob = new Blob([_wasmGlue], { type: 'application/javascript' });
const _glueUrl = URL.createObjectURL(_glueBlob);
let wasmModule;
try {
wasmModule = await import(_glueUrl);
} finally {
URL.revokeObjectURL(_glueUrl);
}
const { default: _initWasm, HprofSession, complete: wasmComplete } = wasmModule;
let wasmReady = false;
let namedQueries = [];
try {
await _initWasm({ module_or_path: _b64toBytes(_wasmB64) });
namedQueries = JSON.parse(HprofSession.named_queries());
wasmReady = true;
console.log(`[hprof-analyzer] WASM ready — ${namedQueries.length} named queries loaded`);
} catch (e) {
console.warn('[hprof-analyzer] WASM init failed:', e);
}
window._hprofWasmReady = () => wasmReady;
%%SHELL_JS%%
</script>
</body>
</html>