hprof-analyzer 0.2.0

Fast, low-memory Java HPROF heap-dump analyzer with Eclipse MAT-parity reports (System Overview, Leak Suspects, Top Consumers).
<!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>

<!-- ── Upload screen ─────────────────────────────────────────────────────── -->
<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 &amp; 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>

<!-- ── Connect screen ─────────────────────────────────────────────────────── -->
<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>

<!-- ── Report screen ─────────────────────────────────────────────────────── -->
<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>

<!-- ── Shell screen ───────────────────────────────────────────────────────── -->
<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>

<!-- xterm.js from CDN (only required when opening the shell screen) -->
<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>

<!-- WASM glue: stored as inert text to avoid template-literal escaping issues -->
<script type="text/plain" id="wasm-glue-src">%%WASM_JS%%</script>
<script type="text/plain" id="wasm-b64-src">%%WASM_B64%%</script>

<!-- React bundle: exposes window.hprofRenderViz / hprofRenderQueryViz / hprofUnmountViz -->
<script>%%REACT_BUNDLE%%</script>

<script type="module">
// ── Load WASM glue from inert <script type="text/plain"> tags ─────────────────
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;
}

// Import the wasm-pack glue as a blob-URL ES module so we can call init()
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 logic (inlined from shell.js) ──────────────────────────────────────
%%SHELL_JS%%
</script>
</body>
</html>