From cd27f2bb7c89b5afb27bbaad14ee44e3d009cfb7 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 12 May 2026 16:31:21 -0500 Subject: [PATCH] feat(memory) add heap monitor for frontend --- meshchatx/src/frontend/js/HeapMonitor.js | 81 ++++++++++++++++++++++++ meshchatx/src/frontend/main.js | 1 + 2 files changed, 82 insertions(+) create mode 100644 meshchatx/src/frontend/js/HeapMonitor.js diff --git a/meshchatx/src/frontend/js/HeapMonitor.js b/meshchatx/src/frontend/js/HeapMonitor.js new file mode 100644 index 0000000..50413a5 --- /dev/null +++ b/meshchatx/src/frontend/js/HeapMonitor.js @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: 0BSD + +let intervalId = null; +const DEFAULT_INTERVAL = 5000; + +function formatBytes(bytes) { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / 1048576).toFixed(1)} MB`; +} + +function sample() { + const m = performance.memory; + if (!m) { + console.log( + "[HeapMonitor] performance.memory not available (requires Chrome with --enable-precise-memory-info)" + ); + return null; + } + return { + heap: m.usedJSHeapSize, + total: m.totalJSHeapSize, + limit: m.jsHeapSizeLimit, + ts: Date.now(), + }; +} + +function log() { + const s = sample(); + if (!s) return; + console.log( + `[HeapMonitor] ${formatBytes(s.heap)} used / ${formatBytes(s.total)} total / ${formatBytes(s.limit)} limit (${((s.heap / s.limit) * 100).toFixed(1)}%)` + ); +} + +export function enableHeapMonitor(intervalMs) { + if (!performance.memory) { + console.warn( + "[HeapMonitor] performance.memory is not available. Use Chrome and pass --enable-precise-memory-info." + ); + return; + } + if (intervalId !== null) { + clearInterval(intervalId); + } + const ms = intervalMs && intervalMs > 0 ? intervalMs : DEFAULT_INTERVAL; + log(); + intervalId = setInterval(log, ms); + console.log(`[HeapMonitor] enabled, sampling every ${ms}ms`); +} + +export function disableHeapMonitor() { + if (intervalId !== null) { + clearInterval(intervalId); + intervalId = null; + } + console.log("[HeapMonitor] disabled"); +} + +export function heapSnapshot() { + const s = sample(); + if (s) { + console.table(s); + console.log( + `[HeapMonitor] heap usage: ${formatBytes(s.heap)} / ${formatBytes(s.limit)} (${((s.heap / s.limit) * 100).toFixed(1)}%)` + ); + } +} + +// Auto-start if VITE_HEAP_MONITOR is set +if (typeof import.meta !== "undefined" && import.meta.env && import.meta.env.VITE_HEAP_MONITOR) { + const interval = parseInt(import.meta.env.VITE_HEAP_MONITOR, 10); + enableHeapMonitor(interval > 0 ? interval : undefined); +} + +// Expose on window for runtime toggle +if (typeof window !== "undefined") { + window.enableHeapMonitor = enableHeapMonitor; + window.disableHeapMonitor = disableHeapMonitor; + window.heapSnapshot = heapSnapshot; +} diff --git a/meshchatx/src/frontend/main.js b/meshchatx/src/frontend/main.js index a70f598..f3890e0 100644 --- a/meshchatx/src/frontend/main.js +++ b/meshchatx/src/frontend/main.js @@ -13,6 +13,7 @@ import "@mdi/font/css/materialdesignicons.css"; import "./fonts/RobotoMonoNerdFont/font.css"; import { startCodec2ScriptsBackgroundLoad } from "./js/Codec2Loader"; import { createApiClient } from "./js/apiClient.js"; +import "./js/HeapMonitor.js"; import App from "./components/App.vue"; import ChangelogModal from "./components/ChangelogModal.vue";