memory-watch
Version:
Advanced Node.js memory monitoring with stack trace analysis, user code detection, and memory leak identification
549 lines (439 loc) • 15.3 kB
Markdown
# Memory Watch 🚨
**Advanced Node.js Memory Monitoring with Stack Trace Analysis**
A powerful Node.js library that not only monitors memory usage but also **identifies exactly which functions and files are causing memory issues**. Unlike basic memory monitors, Memory Watch provides detailed stack traces, user code detection, and actionable insights for fixing memory leaks.
## 🚀 Key Features
- 📊 **Real-time memory monitoring** with customizable thresholds
- 🎯 **User code detection** - identifies YOUR functions causing memory issues (not just Node.js internals)
- 🔍 **Advanced stack trace analysis** - shows exact file paths and line numbers
- � **Automatic memory leak detection** with smart recommendations
- 📈 **Comprehensive diagnostics** - heap breakdown, active handles, CPU usage
- 💾 **Manual context capture** for better tracking with `captureContext()`
- 🎬 **Multiple action callbacks** for alerts, logging, notifications
- 📋 **Detailed diagnostic reports** with actionable insights
## What Makes It Different
❌ **Basic memory monitors tell you:**
- "Memory usage is high: 85%"
✅ **Memory Watch tells you:**
- "Memory spike in `processUserData()` function"
- "File: `src/services/userService.js:123`"
- "Cause: Creating too many objects in loop"
- "Recommendation: Implement data streaming"
## Installation
```bash
npm install memory-watch
```
## Quick Start
### Basic Memory Monitoring
```javascript
const { MemoryWatch } = require("memory-watch");
const watch = new MemoryWatch({
threshold: 0.8, // 80% of max heap
interval: 30000, // Check every 30 seconds
actions: [
(data) => {
console.log("🚨 Memory threshold reached!");
console.log(`Usage: ${(data.percentage * 100).toFixed(2)}%`);
// See which function caused the issue
if (data.context?.stackTrace?.[0]) {
const trace = data.context.stackTrace[0];
console.log(`Problem in: ${trace.functionName}`);
console.log(`File: ${trace.fileName}:${trace.lineNumber}`);
}
},
],
});
watch.start();
```
### Advanced User Code Tracking
```javascript
const { MemoryWatch } = require("memory-watch");
const watch = new MemoryWatch({
threshold: 0.7,
interval: 10000,
actions: [
(data) => {
// Get detailed diagnostic report
const report = generateDiagnosticReport(data);
console.log(report);
// Send alert with specific function details
sendSlackAlert({
message: `Memory leak detected in ${data.context?.stackTrace?.[0]?.functionName}`,
file: data.context?.stackTrace?.[0]?.fileName,
line: data.context?.stackTrace?.[0]?.lineNumber,
});
},
],
});
// Manually track important functions
function processLargeDataset() {
watch.captureContext("processLargeDataset", __filename, 45);
// Your memory-intensive code here
}
watch.start();
```
## API Reference
### Constructor Options
```typescript
interface MemoryWatchOptions {
threshold: number; // Memory threshold (0-1)
interval: number; // Check interval in milliseconds
actions: Array<(data: MemoryData) => void | Promise<void>>;
continuous?: boolean; // Continue monitoring after threshold (default: true)
customMemoryCheck?: () => { used: number; total: number };
}
```
### MemoryData Object
```typescript
interface MemoryData {
used: number; // Current memory usage
total: number; // Total available memory
percentage: number; // Usage percentage (0-1)
usedBytes: number; // Memory usage in bytes
totalBytes: number; // Total memory in bytes
timestamp: Date; // Measurement timestamp
breakdown: {
rss: number; // Resident Set Size (physical memory)
heapUsed: number; // Heap memory used
heapTotal: number; // Total heap allocated
external: number; // External memory (C++ objects)
arrayBuffers: number; // ArrayBuffer memory
};
context?: {
triggerSource?: string; // What triggered the measurement
pid: number; // Process ID
nodeVersion: string; // Node.js version
platform: string; // Platform information
stackTrace?: Array<{
functionName: string;
fileName?: string;
lineNumber?: number;
columnNumber?: number;
}>;
activeHandles?: number; // Active timers, servers, etc.
activeRequests?: number; // Active HTTP requests
uptime: number; // Process uptime in seconds
cpuUsage?: {
user: number; // CPU user time
system: number; // CPU system time
};
};
}
```
### Diagnostic Utilities
```typescript
// Generate detailed diagnostic report
import {
generateDiagnosticReport,
getMemoryLeakIndicators,
} from "memory-watch";
const data = watch.getCurrentMemory();
const report = generateDiagnosticReport(data);
console.log(report);
// Check for memory leak indicators
const leakIndicators = getMemoryLeakIndicators(data);
if (leakIndicators.length > 0) {
console.log("Potential memory leaks detected:", leakIndicators);
}
```
### Methods
- `start()` - Start monitoring
- `stop()` - Stop monitoring
- `getCurrentMemory()` - Get current memory status
- `isRunning()` - Check if monitoring is active
- `captureContext(functionName, fileName?, lineNumber?)` - **NEW!** Manually capture user context for better tracking
- `MemoryWatch.checkOnce(threshold)` - One-time memory check (static method)
## Usage Examples
### 1. Basic Memory Monitoring with User Code Detection
```javascript
const { MemoryWatch, generateDiagnosticReport } = require("memory-watch");
const watch = new MemoryWatch({
threshold: 0.8,
interval: 30000,
actions: [
(data) => {
// Show which user function caused the issue
if (data.context?.stackTrace?.[0]) {
const trace = data.context.stackTrace[0];
console.log(`🎯 Problem in function: ${trace.functionName}`);
console.log(`📁 File: ${trace.fileName}:${trace.lineNumber}`);
}
// Send detailed alert
sendSlackNotification({
message: `Memory threshold reached: ${(data.percentage * 100).toFixed(
1
)}%`,
function: data.context?.stackTrace?.[0]?.functionName,
file: data.context?.stackTrace?.[0]?.fileName,
line: data.context?.stackTrace?.[0]?.lineNumber,
});
},
],
});
watch.start();
```
### 2. Manual Context Tracking for Better Analysis
```javascript
const { MemoryWatch } = require("memory-watch");
const watch = new MemoryWatch({
threshold: 0.7,
interval: 15000,
actions: [
(data) => {
const report = generateDiagnosticReport(data);
console.log(report);
// Get memory leak indicators
const leakIndicators = getMemoryLeakIndicators(data);
if (leakIndicators.length > 0) {
console.log('🔴 Memory leak detected:', leakIndicators);
}
}
]
});
// In your application functions, add manual tracking:
function processLargeDataset(data) {
// Track this function for better memory analysis
watch.captureContext('processLargeDataset', __filename, 25);
// Your processing logic here...
const result = data.map(item => /* heavy processing */);
return result;
}
function handleAPIRequest(req, res) {
watch.captureContext('handleAPIRequest', __filename, 35);
// Your API logic here...
}
watch.start();
```
### Advanced Diagnostic Monitoring
```javascript
const {
MemoryWatch,
generateDiagnosticReport,
getMemoryLeakIndicators,
} = require("memory-watch");
const watch = new MemoryWatch({
threshold: 0.7,
interval: 10000,
actions: [
(data) => {
// Generate comprehensive diagnostic report
const report = generateDiagnosticReport(data);
console.log(report);
// Check for memory leak indicators
const leakIndicators = getMemoryLeakIndicators(data);
if (leakIndicators.length > 0) {
console.log("🔴 Memory leak indicators:", leakIndicators);
// Send alert with specific details
sendAlert({
type: "memory_leak",
indicators: leakIndicators,
stackTrace: data.context?.stackTrace,
file: data.context?.stackTrace?.[0]?.fileName,
function: data.context?.stackTrace?.[0]?.functionName,
});
}
// Log problematic source files
if (data.context?.stackTrace) {
const sourceFiles = data.context.stackTrace
.filter((trace) => trace.fileName)
.map((trace) => `${trace.fileName}:${trace.lineNumber}`)
.slice(0, 3);
console.log("🎯 Check these files for memory issues:", sourceFiles);
}
},
],
});
```
### Production Server Monitoring
```javascript
const watch = new MemoryWatch({
threshold: 0.85,
interval: 60000, // Check every minute
actions: [
async (data) => {
// Send to monitoring service with detailed context
await sendToDatadog({
metric: "memory.usage.high",
value: data.percentage,
tags: [
`pid:${data.context?.pid}`,
`platform:${data.context?.platform}`,
`trigger:${data.context?.triggerSource}`,
`active_handles:${data.context?.activeHandles}`,
`active_requests:${data.context?.activeRequests}`,
],
stackTrace: data.context?.stackTrace,
});
// Log detailed breakdown for ops team
console.log("Memory breakdown:", {
heap: `${(data.breakdown.heapUsed / 1024 / 1024).toFixed(2)}MB`,
rss: `${(data.breakdown.rss / 1024 / 1024).toFixed(2)}MB`,
external: `${(data.breakdown.external / 1024 / 1024).toFixed(2)}MB`,
activeHandles: data.context?.activeHandles,
topFunction: data.context?.stackTrace?.[0]?.functionName,
});
},
],
});
```
### One-time Memory Check
```javascript
// Check if memory usage is above 50%
const result = await MemoryWatch.checkOnce(0.5);
if (result) {
console.log("Memory usage is high:", result);
}
```
## Real-world Use Cases
## Real-world Use Cases
### 1. API Memory Leak Detection
Identify which API endpoints are causing memory leaks:
```javascript
const watch = new MemoryWatch({
threshold: 0.8,
interval: 30000,
actions: [
(data) => {
const apiEndpoint = data.context?.stackTrace?.find(
(trace) =>
trace.fileName?.includes("routes") ||
trace.fileName?.includes("controllers")
);
if (apiEndpoint) {
console.log(
`🚨 Memory issue in API: ${apiEndpoint.fileName}:${apiEndpoint.lineNumber}`
);
console.log(` Function: ${apiEndpoint.functionName}`);
console.log(` Memory: ${(data.percentage * 100).toFixed(1)}%`);
}
},
],
});
```
### 2. Database Connection Monitoring
Monitor for unclosed database connections:
```javascript
const watch = new MemoryWatch({
threshold: 0.7,
interval: 15000,
actions: [
(data) => {
if (data.context?.activeHandles > 50) {
console.log(`⚠️ High active handles: ${data.context.activeHandles}`);
console.log("Possible unclosed database connections or timers");
// Check stack trace for database-related functions
const dbTrace = data.context?.stackTrace?.find(
(trace) =>
trace.functionName?.includes("query") ||
trace.functionName?.includes("connection") ||
trace.fileName?.includes("database")
);
if (dbTrace) {
console.log(
`🔍 Check database code: ${dbTrace.fileName}:${dbTrace.lineNumber}`
);
}
}
},
],
});
```
### 3. Development Memory Profiling
Use during development to catch memory issues early:
```javascript
const watch = new MemoryWatch({
threshold: 0.6,
interval: 5000,
continuous: false, // Stop after first alert
actions: [
(data) => {
const report = generateDiagnosticReport(data);
console.log(report);
// Save detailed report to file for analysis
require("fs").writeFileSync(`memory-report-${Date.now()}.txt`, report);
console.log(
"💡 Tip: Check the stack trace above for the problematic code"
);
},
],
});
```
## What Makes This Different
Unlike basic memory monitoring tools, Memory Watch provides:
- **🎯 Exact source identification**: Tells you which file and function is causing memory issues
- **📊 Detailed breakdown**: Shows heap, RSS, external memory separately
- **🔍 Root cause analysis**: Identifies patterns like unclosed handles or large buffers
- **🚨 Smart leak detection**: Automatically detects common memory leak patterns
- **📈 Process insights**: Tracks active handles, requests, and CPU usage
- **💡 Actionable recommendations**: Provides specific suggestions for fixing issues
## Examples Output
When memory threshold is reached, you'll see detailed reports like:
```
🔍 MEMORY DIAGNOSTIC REPORT
================================
📊 Overall Usage: 78.5% (245MB / 312MB)
⏰ Timestamp: 2025-09-10T11:30:15.123Z
📈 MEMORY BREAKDOWN:
• Heap Used: 178MB
• Heap Total: 245MB
• RSS (Physical): 312MB
• External: 45MB
• Array Buffers: 12MB
🖥️ PROCESS INFO:
• PID: 12345
• Node.js: v18.20.4
• Platform: linux
• Uptime: 2h 15m 30s
• Active Handles: 15
• Active Requests: 3
🎯 POTENTIAL SOURCES (Stack Trace):
1. processLargeDataset (/app/src/data-processor.js:45:12)
2. handleApiRequest (/app/src/routes/api.js:123:8)
3. middleware (/app/src/middleware/auth.js:67:15)
💡 RECOMMENDATIONS:
⚠️ Heap usage is very high - possible memory leak
⚠️ Check the stack trace above for problematic functions
```
## Available Scripts
Test the library with included examples:
```bash
# Simple memory monitoring
npm run example
# Basic usage with multiple actions
npm run example-basic
# Advanced diagnostics with detailed analysis
npm run example-advanced
# User code tracking demonstration
npm run example-tracking
# Build the project
npm run build
# Development mode with watch
npm run dev
```
## Development & Testing
To test the library locally:
1. Clone the repository
2. Install dependencies: `npm install`
3. Build the project: `npm run build`
4. Run examples: `npm run example-tracking`
## NPM Package Information
- **Package Name**: `memory-watch`
- **Version**: 1.0.0
- **Node.js Support**: >=14.0.0
- **TypeScript**: Full TypeScript support with type definitions
- **License**: MIT
- **Bundle Size**: Lightweight (~50KB)
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
### Development Setup
```bash
git clone https://github.com/muhcen/memory-watch.git
cd memory-watch
npm install
npm run build
npm run example-tracking
```
## License
MIT
Copyright (c) 2025 Mohsen Moradi
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.