@usefy/use-memory-monitor
v0.2.5
Published
A React hook for real-time browser memory monitoring with leak detection
Maintainers
Readme
Overview
@usefy/use-memory-monitor is a comprehensive React hook for monitoring browser memory usage in real-time. It provides heap metrics, memory leak detection, threshold-based alerts, history tracking, and snapshot comparison capabilities.
Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.
Why use-memory-monitor?
- Real-time Monitoring — Track heap usage, total heap, and memory limits in real-time
- Leak Detection — Automatic memory leak detection using linear regression analysis
- Threshold Alerts — Configure custom thresholds for low/medium/high/critical severity levels
- Memory Snapshots — Take snapshots and compare memory usage between different points in time
- History Tracking — Maintain a circular buffer of historical memory data with trend analysis
- Tab Visibility Optimization — Automatically pauses monitoring when tab is hidden
- TypeScript First — Full type safety with comprehensive exported interfaces
- SSR Compatible — Safe to use with Next.js, Remix, and other SSR frameworks
- Browser Fallbacks — Graceful degradation in browsers without full API support
- Well Tested — Comprehensive test coverage (175 tests) with Vitest
Installation
# npm
npm install @usefy/use-memory-monitor
# yarn
yarn add @usefy/use-memory-monitor
# pnpm
pnpm add @usefy/use-memory-monitorPeer Dependencies
This package requires React 18 or 19:
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}Quick Start
import { useMemoryMonitor } from "@usefy/use-memory-monitor";
function MemoryMonitor() {
const {
heapUsed,
heapTotal,
heapLimit,
isSupported,
severity,
formatted,
} = useMemoryMonitor({
interval: 1000,
autoStart: true,
});
if (!isSupported) {
return <div>Memory monitoring not supported in this browser</div>;
}
return (
<div>
<h2>Memory Usage</h2>
<p>Heap Used: {formatted.heapUsed}</p>
<p>Heap Total: {formatted.heapTotal}</p>
<p>Heap Limit: {formatted.heapLimit}</p>
<p>Severity: {severity}</p>
</div>
);
}Browser Support
Full Support (performance.memory API)
- ✅ Chrome (all versions)
- ✅ Edge (Chromium-based)
Limited Support (DOM-only metrics)
- ⚠️ Firefox - DOM tracking only, no heap metrics
- ⚠️ Safari - DOM tracking only, no heap metrics
- ⚠️ Other browsers - Fallback strategies available
Browser Detection
The hook automatically detects browser capabilities and adjusts functionality:
const { isSupported, getUnsupportedInfo, getBrowserSupport } = useMemoryMonitor();
if (!isSupported) {
const info = getUnsupportedInfo();
console.log("Reason:", info.reason); // 'no-api' | 'browser-restriction' | etc.
console.log("Browser:", info.browser);
console.log("Fallbacks:", info.availableFallbacks);
}
const support = getBrowserSupport();
console.log("Support level:", support.level); // 'full' | 'partial' | 'none'
console.log("Available metrics:", support.availableMetrics);API Reference
useMemoryMonitor(options?)
A hook that monitors browser memory usage in real-time with leak detection and threshold alerts.
Parameters
| Parameter | Type | Default | Description |
| --------- | ----------------------------- | ------- | ---------------------------------- |
| options | UseMemoryMonitorOptions | {} | Configuration options (see below) |
Options (UseMemoryMonitorOptions)
| Option | Type | Default | Description |
| -------------------- | ----------------------- | -------- | ------------------------------------------------ |
| interval | number | 1000 | Polling interval in milliseconds |
| autoStart | boolean | true | Start monitoring automatically on mount |
| enabled | boolean | true | Enable/disable the hook |
| enableHistory | boolean | true | Track memory history |
| historySize | number | 50 | Maximum number of history entries |
| thresholds | ThresholdOptions | See below | Memory usage thresholds |
| leakDetection | LeakDetectionOptions | See below | Leak detection configuration |
| pauseWhenHidden | boolean | true | Pause monitoring when tab is hidden |
| onThresholdChange | (severity) => void | - | Callback when severity level changes |
| onMemoryUpdate | (memory) => void | - | Callback on each memory update |
| onLeakDetected | (analysis) => void | - | Callback when memory leak is detected |
| onUnsupported | (info) => void | - | Callback when memory API is not supported |
Default Thresholds
{
medium: 60, // Yellow alert at 60% usage
high: 80, // Orange alert at 80% usage
critical: 90, // Red alert at 90% usage
}Default Leak Detection
{
enabled: true,
sensitivity: "medium", // 'low' | 'medium' | 'high'
sampleSize: 10,
minDuration: 30000, // 30 seconds
}Returns UseMemoryMonitorReturn
| Property | Type | Description |
| ---------------------- | ------------------------------------ | -------------------------------------------- |
| memory | MemoryInfo \| null | Current memory information |
| heapUsed | number \| null | Used heap size in bytes |
| heapTotal | number \| null | Total heap size in bytes |
| heapLimit | number \| null | Heap size limit in bytes |
| isSupported | boolean | Whether memory monitoring is supported |
| isMonitoring | boolean | Whether currently monitoring |
| isLeakDetected | boolean | Whether memory leak is detected |
| severity | Severity | Current severity level |
| history | MemoryInfo[] | Historical memory data |
| trend | Trend | Memory usage trend |
| formatted | FormattedMemory | Human-readable formatted values |
| start | () => void | Start monitoring |
| stop | () => void | Stop monitoring |
| takeSnapshot | (id: string) => MemorySnapshot \| null | Take a memory snapshot |
| compareSnapshots | (id1, id2) => MemoryDifference \| null | Compare two snapshots |
| clearSnapshots | () => void | Clear all snapshots |
| getAllSnapshots | () => MemorySnapshot[] | Get all snapshots |
| clearHistory | () => void | Clear history buffer |
| requestGC | () => void | Request garbage collection (hint only) |
| getLeakAnalysis | () => LeakAnalysis \| null | Get current leak analysis |
| getBrowserSupport | () => BrowserSupport | Get browser support information |
| getUnsupportedInfo | () => UnsupportedInfo | Get info about why monitoring is unsupported |
Types
Severity
type Severity = "low" | "medium" | "high" | "critical";Trend
type Trend = "increasing" | "stable" | "decreasing";MemoryInfo
interface MemoryInfo {
heapUsed: number | null;
heapTotal: number | null;
heapLimit: number | null;
timestamp: number;
domNodes?: number | null;
eventListeners?: number | null;
}FormattedMemory
interface FormattedMemory {
heapUsed: string;
heapTotal: string;
heapLimit: string;
domNodes?: string;
eventListeners?: string;
}Examples
Basic Memory Monitoring
import { useMemoryMonitor } from "@usefy/use-memory-monitor";
function BasicMonitor() {
const { formatted, severity, isMonitoring, start, stop } = useMemoryMonitor({
interval: 1000,
autoStart: true,
});
return (
<div>
<h2>Memory Usage</h2>
<p>Used: {formatted.heapUsed}</p>
<p>Total: {formatted.heapTotal}</p>
<p>Severity: {severity}</p>
<button onClick={isMonitoring ? stop : start}>
{isMonitoring ? "Stop" : "Start"}
</button>
</div>
);
}Memory Leak Detection
import { useMemoryMonitor } from "@usefy/use-memory-monitor";
function LeakDetector() {
const { isLeakDetected, getLeakAnalysis, formatted } = useMemoryMonitor({
interval: 1000,
leakDetection: {
enabled: true,
sensitivity: "high",
sampleSize: 15,
minDuration: 20000, // 20 seconds
},
onLeakDetected: (analysis) => {
console.warn("Memory leak detected!", {
probability: analysis.probability,
confidence: analysis.confidence,
slope: analysis.slope,
});
},
});
const analysis = getLeakAnalysis();
return (
<div>
<h2>Leak Detection</h2>
{isLeakDetected && (
<div className="alert">
⚠️ Memory leak detected!
<p>Probability: {(analysis?.probability ?? 0).toFixed(2)}%</p>
<p>Confidence: {analysis?.confidence}</p>
</div>
)}
<p>Current Usage: {formatted.heapUsed}</p>
</div>
);
}How Leak Detection Works
The hook uses an Enhanced Multi-Factor Analysis algorithm to detect memory leaks accurately while minimizing false positives:
Algorithm Overview
- Data Collection: Requires minimum 10 samples over 30+ seconds of observation
- GC Event Detection: Identifies garbage collection events by detecting significant memory drops (≥10%)
- Baseline Calculation: Calculates baseline from post-GC memory values
- Baseline Trend Analysis: Monitors if post-GC baselines are increasing over time
- Weighted Probability Calculation: Combines multiple factors for accurate detection
Probability Factors (Weighted Scoring)
| Factor | Max Points | Description | |--------|------------|-------------| | Slope | 30 | How fast memory is growing | | R² Fit | 20 | How consistent the growth pattern is | | GC Ineffectiveness | 25 | Whether GC fails to reclaim memory | | Observation Time | 15 | Longer observation = more confidence | | Baseline Growth | 10 | Whether post-GC baseline is rising |
Total: 100 points maximum
Key Principles
- GC-Aware: True leaks persist even after garbage collection cycles
- Time-Based: Requires sufficient observation time before making judgment
- Baseline Tracking: Monitors if the memory "floor" after GC is rising
- False Positive Reduction: If GC is effective and baseline is stable, probability is reduced
Leak Detection Flow:
1. Collect samples (min 10, min 30 seconds)
↓
2. Detect GC events (memory drops ≥10%)
↓
3. Calculate baseline (average post-GC memory)
↓
4. Analyze baseline trend (is it rising?)
↓
5. Calculate weighted probability
↓
6. Apply corrections (GC effectiveness, trend)
↓
7. Final determination (≥70% = leak detected)Sensitivity Levels
| Sensitivity | Min Slope | Min R² | Min GC Cycles | Min Observation |
|-------------|-----------|--------|---------------|-----------------|
| low | 100KB/sample | 0.8 | 3 | 60 seconds |
| medium | 50KB/sample | 0.7 | 2 | 30 seconds |
| high | 10KB/sample | 0.6 | 1 | 15 seconds |
LeakAnalysis Response
The LeakAnalysis object now includes detailed analysis information:
interface LeakAnalysis {
isLeaking: boolean; // Whether a leak is detected (probability ≥ 70%)
probability: number; // Leak probability (0-100)
confidence: number; // Analysis confidence (0-100)
trend: Trend; // 'increasing' | 'stable' | 'decreasing'
averageGrowth: number; // Bytes per sample
rSquared: number; // Regression fit quality
observationTime?: number; // Total observation time in ms
gcAnalysis?: {
gcEventCount: number; // Number of GC events detected
avgRecoveryRatio: number; // Average memory recovered per GC
isGCEffective: boolean; // Whether GC is reclaiming memory
};
baselineAnalysis?: {
baselineHeap: number; // Average post-GC memory
currentHeap: number; // Current heap usage
growthRatio: number; // Growth from baseline
isSignificantGrowth: boolean;
};
factors?: {
slopeContribution: number;
rSquaredContribution: number;
gcContribution: number;
timeContribution: number;
baselineContribution: number;
};
recommendation?: string; // Human-readable recommendation
}Threshold Alerts
import { useMemoryMonitor } from "@usefy/use-memory-monitor";
function ThresholdMonitor() {
const { severity, heapUsed, heapLimit, formatted } = useMemoryMonitor({
interval: 1000,
thresholds: {
medium: 50,
high: 75,
critical: 90,
},
onThresholdChange: (newSeverity) => {
if (newSeverity === "critical") {
alert("Critical memory usage!");
}
},
});
const usagePercent = heapUsed && heapLimit
? (heapUsed / heapLimit) * 100
: 0;
return (
<div>
<h2>Memory Usage: {usagePercent.toFixed(1)}%</h2>
<div className={`alert alert-${severity}`}>
Severity: {severity}
</div>
<p>{formatted.heapUsed} / {formatted.heapLimit}</p>
</div>
);
}Memory Snapshots
import { useMemoryMonitor } from "@usefy/use-memory-monitor";
import { useState } from "react";
function SnapshotComparison() {
const {
takeSnapshot,
compareSnapshots,
getAllSnapshots,
clearSnapshots,
formatted,
} = useMemoryMonitor({ interval: 1000 });
const [snapshots, setSnapshots] = useState<string[]>([]);
const handleSnapshot = () => {
const snapshot = takeSnapshot(`snapshot-${Date.now()}`);
if (snapshot) {
setSnapshots([...snapshots, snapshot.id]);
}
};
const handleCompare = () => {
if (snapshots.length >= 2) {
const diff = compareSnapshots(snapshots[0], snapshots[snapshots.length - 1]);
if (diff) {
console.log("Memory difference:", {
heapUsed: (diff.heapUsed / 1024 / 1024).toFixed(2) + " MB",
heapTotal: (diff.heapTotal / 1024 / 1024).toFixed(2) + " MB",
duration: (diff.duration / 1000).toFixed(1) + " seconds",
});
}
}
};
return (
<div>
<h2>Snapshots ({snapshots.length})</h2>
<p>Current: {formatted.heapUsed}</p>
<button onClick={handleSnapshot}>Take Snapshot</button>
<button onClick={handleCompare} disabled={snapshots.length < 2}>
Compare First & Last
</button>
<button onClick={clearSnapshots}>Clear All</button>
<ul>
{getAllSnapshots().map((snapshot) => (
<li key={snapshot.id}>
{snapshot.id}: {(snapshot.data.heapUsed || 0) / 1024 / 1024} MB
</li>
))}
</ul>
</div>
);
}History & Trend Analysis
import { useMemoryMonitor } from "@usefy/use-memory-monitor";
function HistoryTracker() {
const { history, trend, formatted, clearHistory } = useMemoryMonitor({
interval: 500,
enableHistory: true,
historySize: 30,
});
const maxHeapUsed = Math.max(...history.map((h) => h.heapUsed || 0), 1);
return (
<div>
<h2>Memory History</h2>
<p>Current: {formatted.heapUsed}</p>
<p>
Trend: {trend === "increasing" ? "↗" : trend === "decreasing" ? "↘" : "→"} {trend}
</p>
<p>History Points: {history.length}</p>
<button onClick={clearHistory}>Clear History</button>
{/* Simple bar chart */}
<div style={{ display: "flex", alignItems: "flex-end", height: 100 }}>
{history.map((point, i) => {
const heightPercent = ((point.heapUsed || 0) / maxHeapUsed) * 100;
return (
<div
key={i}
style={{
flex: 1,
height: `${heightPercent}%`,
backgroundColor: "blue",
margin: "0 1px",
}}
title={`${((point.heapUsed || 0) / 1024 / 1024).toFixed(2)} MB`}
/>
);
})}
</div>
</div>
);
}Browser Support Detection
import { useMemoryMonitor } from "@usefy/use-memory-monitor";
function SupportDetection() {
const { isSupported, getUnsupportedInfo, getBrowserSupport } = useMemoryMonitor();
if (!isSupported) {
const info = getUnsupportedInfo();
return (
<div>
<h2>Limited Support</h2>
<p>Reason: {info.reason}</p>
<p>Browser: {info.browser || "Unknown"}</p>
<p>Available fallbacks: {info.availableFallbacks.join(", ")}</p>
</div>
);
}
const support = getBrowserSupport();
return (
<div>
<h2>Browser Support</h2>
<p>Level: {support.level}</p>
<p>Available metrics: {support.availableMetrics.join(", ")}</p>
<p>Secure context: {support.isSecureContext ? "Yes" : "No"}</p>
<p>Cross-origin isolated: {support.isCrossOriginIsolated ? "Yes" : "No"}</p>
{support.limitations.length > 0 && (
<ul>
{support.limitations.map((limitation, i) => (
<li key={i}>{limitation}</li>
))}
</ul>
)}
</div>
);
}Garbage Collection Request
The requestGC function provides a way to request garbage collection. Note that this is a hint only and is not guaranteed to trigger GC in all environments.
import { useMemoryMonitor } from "@usefy/use-memory-monitor";
function GCExample() {
const { requestGC, formatted } = useMemoryMonitor({
devMode: true,
logToConsole: true, // Enable logging to see GC status
});
return (
<div>
<p>Heap Used: {formatted.heapUsed}</p>
<button onClick={requestGC}>Request GC</button>
</div>
);
}How it works:
- If
globalThis.gc()is available (Chrome with--expose-gcflag or Node.js with--expose-gc), it will directly trigger garbage collection - Otherwise, it creates temporary memory pressure as a hint to the JS engine
- With
devModeandlogToConsoleenabled, you'll see console logs indicating whether GC was triggered or just hinted
To enable direct GC in Chrome:
# Windows
chrome.exe --js-flags="--expose-gc"
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --js-flags="--expose-gc"
# Linux
google-chrome --js-flags="--expose-gc"Performance
Optimizations
- Tab Visibility: Automatically pauses monitoring when tab is hidden (configurable with
pauseWhenHidden) - Circular Buffer: Uses efficient circular buffer for history storage (O(1) operations)
- Stable References: All callback functions are memoized with
useCallback - useSyncExternalStore: React 18+ concurrent mode safe state management
- Minimal Re-renders: Only triggers re-renders when actual memory data changes
Bundle Size
The hook is lightweight and tree-shakeable:
- Main hook: ~8KB minified
- With all utilities: ~15KB minified
- Gzipped: ~5KB
TypeScript
This hook is written in TypeScript and exports comprehensive type definitions:
import {
useMemoryMonitor,
type UseMemoryMonitorOptions,
type UseMemoryMonitorReturn,
type MemoryInfo,
type MemorySnapshot,
type LeakAnalysis,
type BrowserSupport,
type Severity,
type Trend,
} from "@usefy/use-memory-monitor";
// Full type safety
const options: UseMemoryMonitorOptions = {
interval: 1000,
autoStart: true,
};
const monitor: UseMemoryMonitorReturn = useMemoryMonitor(options);Testing
This package maintains comprehensive test coverage to ensure reliability and stability.
Test Coverage
📊 View Detailed Coverage Report (GitHub Pages)
Test Categories
225 tests passed covering:
- Initialization & Lifecycle: Mount, unmount, start/stop behavior
- Memory Tracking: Polling, history, trend analysis
- Leak Detection: Linear regression, sensitivity levels, analysis
- Thresholds: Severity calculation, callbacks
- Snapshots: Create, compare, clear operations
- Browser Detection: API availability, fallback strategies
- Edge Cases: SSR, unsupported browsers, invalid inputs
- Store Management: State updates, subscribers, batch operations
- Memory APIs: Performance.memory, DOM nodes, event listeners
License
MIT © mirunamu
This package is part of the usefy monorepo.
