node-runtime-guardian 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +423 -0
- package/dist/core/EventLoopMonitor.d.ts +32 -0
- package/dist/core/EventLoopMonitor.d.ts.map +1 -0
- package/dist/core/EventLoopMonitor.js +131 -0
- package/dist/core/EventLoopMonitor.js.map +1 -0
- package/dist/core/GCMonitor.d.ts +32 -0
- package/dist/core/GCMonitor.d.ts.map +1 -0
- package/dist/core/GCMonitor.js +151 -0
- package/dist/core/GCMonitor.js.map +1 -0
- package/dist/core/Guardian.d.ts +76 -0
- package/dist/core/Guardian.d.ts.map +1 -0
- package/dist/core/Guardian.js +196 -0
- package/dist/core/Guardian.js.map +1 -0
- package/dist/core/HeuristicEngine.d.ts +27 -0
- package/dist/core/HeuristicEngine.d.ts.map +1 -0
- package/dist/core/HeuristicEngine.js +150 -0
- package/dist/core/HeuristicEngine.js.map +1 -0
- package/dist/core/MemoryMonitor.d.ts +33 -0
- package/dist/core/MemoryMonitor.d.ts.map +1 -0
- package/dist/core/MemoryMonitor.js +193 -0
- package/dist/core/MemoryMonitor.js.map +1 -0
- package/dist/core/ProtectionLayer.d.ts +42 -0
- package/dist/core/ProtectionLayer.d.ts.map +1 -0
- package/dist/core/ProtectionLayer.js +105 -0
- package/dist/core/ProtectionLayer.js.map +1 -0
- package/dist/core/ThreadPoolMonitor.d.ts +29 -0
- package/dist/core/ThreadPoolMonitor.d.ts.map +1 -0
- package/dist/core/ThreadPoolMonitor.js +144 -0
- package/dist/core/ThreadPoolMonitor.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/Plugin.d.ts +53 -0
- package/dist/plugins/Plugin.d.ts.map +1 -0
- package/dist/plugins/Plugin.js +74 -0
- package/dist/plugins/Plugin.js.map +1 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +18 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/server/MetricsServer.d.ts +26 -0
- package/dist/server/MetricsServer.d.ts.map +1 -0
- package/dist/server/MetricsServer.js +86 -0
- package/dist/server/MetricsServer.js.map +1 -0
- package/dist/types/index.d.ts +104 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/metrics.d.ts +67 -0
- package/dist/types/metrics.d.ts.map +1 -0
- package/dist/types/metrics.js +3 -0
- package/dist/types/metrics.js.map +1 -0
- package/dist/worker/WorkerPool.d.ts +44 -0
- package/dist/worker/WorkerPool.d.ts.map +1 -0
- package/dist/worker/WorkerPool.js +214 -0
- package/dist/worker/WorkerPool.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Node Runtime Guardian Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>Node Runtime Guardian</h1>
|
|
3
|
+
<img src="assets/node-runtime-guardian.gif" alt="Node Runtime Guardian Logo" width="600">
|
|
4
|
+
<p><strong>Runtime visibility and self-protection for Node.js applications</strong></p>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Node Runtime Guardian is a lightweight, dependency-free runtime diagnostics and protection layer for Node.js. It runs inside your process to observe event loop health, memory behavior, garbage collection pressure, and thread pool saturation — and can optionally shed load when your application is under stress.
|
|
10
|
+
|
|
11
|
+
This project is intentionally calm, explicit, and production-focused. It is built for backend developers who want to understand how the Node.js runtime behaves under real load, not just what dashboards report after the fact.
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Why This Exists](#why-this-exists)
|
|
16
|
+
- [When Should You Use This?](#when-should-you-use-this)
|
|
17
|
+
- [Features](#features)
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Quick Start](#quick-start)
|
|
20
|
+
- [Documentation](#documentation)
|
|
21
|
+
- [API Reference](#api-documentation)
|
|
22
|
+
- [Usage Examples](#usage-examples)
|
|
23
|
+
- [Metrics](#metrics)
|
|
24
|
+
- [Development](#development)
|
|
25
|
+
- [Contributing](#contributing)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
28
|
+
## Why This Exists
|
|
29
|
+
|
|
30
|
+
Most Node.js production issues don't show up as errors.
|
|
31
|
+
|
|
32
|
+
They show up as:
|
|
33
|
+
|
|
34
|
+
- **Latency spikes** with no obvious cause
|
|
35
|
+
- **Memory usage** slowly creeping upward
|
|
36
|
+
- **GC pauses** causing jittery response times
|
|
37
|
+
- **Pods restarting** due to OOM without clear signals
|
|
38
|
+
- **Services degrading** under burst traffic
|
|
39
|
+
|
|
40
|
+
By the time traditional monitoring alerts fire, the system is often already unhealthy.
|
|
41
|
+
|
|
42
|
+
Node Runtime Guardian was built to observe these problems from inside the Node.js runtime itself — where the event loop, garbage collector, and thread pool actually live.
|
|
43
|
+
|
|
44
|
+
## When Should You Use This?
|
|
45
|
+
|
|
46
|
+
Node Runtime Guardian is useful if:
|
|
47
|
+
|
|
48
|
+
- You suspect event loop blocking or synchronous CPU work
|
|
49
|
+
- Your service slows down under load without throwing errors
|
|
50
|
+
- Memory usage grows steadily and heap snapshots aren't obvious
|
|
51
|
+
- You want runtime insight without a full APM stack
|
|
52
|
+
- You want a last-line defense against cascading failures
|
|
53
|
+
|
|
54
|
+
It is especially well-suited for:
|
|
55
|
+
|
|
56
|
+
- High-traffic API servers
|
|
57
|
+
- Background workers and job processors
|
|
58
|
+
- Memory-constrained containers
|
|
59
|
+
- High-throughput Node.js services
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- **Event Loop Monitoring**: Track event loop delay using `perf_hooks`
|
|
64
|
+
- **Memory Monitoring**: Detect memory leaks and drift patterns
|
|
65
|
+
- **GC Pressure Estimation**: Monitor garbage collection behavior
|
|
66
|
+
- **Thread Pool Saturation Detection**: Infer thread pool queue buildup
|
|
67
|
+
- **Load Shedding**: Automatic request rejection when thresholds are exceeded
|
|
68
|
+
- **Metrics Endpoint**: HTTP endpoint for runtime metrics
|
|
69
|
+
- **Plugin System**: Extensible architecture for custom integrations
|
|
70
|
+
- **Worker Thread Pool**: Managed worker pool utility
|
|
71
|
+
- **Zero Dependencies**: Uses only Node.js built-in modules
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install node-runtime-guardian
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { Guardian } from 'node-runtime-guardian';
|
|
83
|
+
|
|
84
|
+
// Initialize Guardian
|
|
85
|
+
const guardian = new Guardian({
|
|
86
|
+
eventLoop: {
|
|
87
|
+
thresholdMs: 100,
|
|
88
|
+
},
|
|
89
|
+
memory: {
|
|
90
|
+
rssLimit: 512 * 1024 * 1024, // 512MB
|
|
91
|
+
driftDetectionEnabled: true,
|
|
92
|
+
},
|
|
93
|
+
protection: {
|
|
94
|
+
enabled: true,
|
|
95
|
+
loadShedding: {
|
|
96
|
+
enabled: true,
|
|
97
|
+
eventLoopThreshold: 200,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
metricsServer: {
|
|
101
|
+
enabled: true,
|
|
102
|
+
port: 9090,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Start monitoring
|
|
107
|
+
guardian.start();
|
|
108
|
+
|
|
109
|
+
// Listen for warnings
|
|
110
|
+
guardian.on('warning', (warning) => {
|
|
111
|
+
console.warn('Guardian warning:', warning);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Get current metrics
|
|
115
|
+
const metrics = guardian.getMetrics();
|
|
116
|
+
console.log('Health score:', guardian.getHealthScore());
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
> 💡 **Tip**: For more detailed examples and configuration options, see the [Usage Documentation](docs/USAGE.md).
|
|
120
|
+
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
📖 **For comprehensive usage guides, configuration options, integration patterns, and best practices, see the [Usage Documentation](docs/USAGE.md).**
|
|
124
|
+
|
|
125
|
+
The [Usage Documentation](docs/USAGE.md) provides detailed information on:
|
|
126
|
+
|
|
127
|
+
- **Configuration Examples**: Development, production, memory-sensitive, and CPU-intensive setups
|
|
128
|
+
- **Integration Patterns**: Express.js, Fastify, Koa.js middleware integration
|
|
129
|
+
- **Usage Patterns**: Background job monitoring, microservices health reporting
|
|
130
|
+
- **Best Practices**: Configuration tuning, event handling, request tracking
|
|
131
|
+
- **Troubleshooting**: Common issues and solutions
|
|
132
|
+
- **Advanced Topics**: Custom health scoring, APM integration, metrics export
|
|
133
|
+
|
|
134
|
+
## API Documentation
|
|
135
|
+
|
|
136
|
+
### Guardian Class
|
|
137
|
+
|
|
138
|
+
Main class that orchestrates all monitoring and protection.
|
|
139
|
+
|
|
140
|
+
#### Constructor
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
new Guardian(config?: GuardianConfig)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### Methods
|
|
147
|
+
|
|
148
|
+
- `init(config?: GuardianConfig): void` - Initialize and start monitoring
|
|
149
|
+
- `start(): void` - Start all monitors
|
|
150
|
+
- `stop(): void` - Stop all monitors
|
|
151
|
+
- `getMetrics(): RuntimeMetrics` - Get current aggregated metrics
|
|
152
|
+
- `getHealthScore(): number` - Get health score (0-1, where 1 is healthy)
|
|
153
|
+
- `isProtectionActive(): boolean` - Check if protection is active
|
|
154
|
+
- `shouldRejectRequest(): boolean` - Check if a request should be rejected
|
|
155
|
+
- `getRejectionResponse(): { statusCode: number; message: string }` - Get rejection response
|
|
156
|
+
- `trackRequest(): void` - Track incoming request
|
|
157
|
+
- `trackRequestComplete(): void` - Track completed request
|
|
158
|
+
|
|
159
|
+
#### Events
|
|
160
|
+
|
|
161
|
+
- `metric` - Emitted when metrics are collected
|
|
162
|
+
- `warning` - Emitted when thresholds are exceeded
|
|
163
|
+
- `eventLoopBlocked` - Emitted when event loop delay exceeds threshold
|
|
164
|
+
- `memoryDrift` - Emitted when memory drift is detected
|
|
165
|
+
- `gcPressure` - Emitted when GC pressure is high
|
|
166
|
+
- `threadPoolSaturated` - Emitted when thread pool is saturated
|
|
167
|
+
- `protectionActivated` - Emitted when protection is activated
|
|
168
|
+
- `protectionDeactivated` - Emitted when protection is deactivated
|
|
169
|
+
|
|
170
|
+
### Configuration
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface GuardianConfig {
|
|
174
|
+
eventLoop?: {
|
|
175
|
+
enabled?: boolean;
|
|
176
|
+
thresholdMs?: number; // Default: 100
|
|
177
|
+
intervalMs?: number; // Default: 1000
|
|
178
|
+
};
|
|
179
|
+
memory?: {
|
|
180
|
+
enabled?: boolean;
|
|
181
|
+
rssLimit?: number;
|
|
182
|
+
heapLimit?: number;
|
|
183
|
+
externalLimit?: number;
|
|
184
|
+
driftDetectionEnabled?: boolean; // Default: true
|
|
185
|
+
driftThreshold?: number; // Default: 0.1
|
|
186
|
+
};
|
|
187
|
+
gc?: {
|
|
188
|
+
enabled?: boolean;
|
|
189
|
+
pressureThreshold?: number; // Default: 0.7
|
|
190
|
+
};
|
|
191
|
+
threadPool?: {
|
|
192
|
+
enabled?: boolean;
|
|
193
|
+
saturationThreshold?: number; // Default: 0.8
|
|
194
|
+
};
|
|
195
|
+
protection?: {
|
|
196
|
+
enabled?: boolean;
|
|
197
|
+
loadShedding?: {
|
|
198
|
+
enabled?: boolean;
|
|
199
|
+
eventLoopThreshold?: number; // Default: 200
|
|
200
|
+
memoryThreshold?: number; // Default: 0.9
|
|
201
|
+
responseCode?: number; // Default: 503
|
|
202
|
+
responseMessage?: string;
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
metricsServer?: {
|
|
206
|
+
enabled?: boolean;
|
|
207
|
+
port?: number; // Default: 9090
|
|
208
|
+
path?: string; // Default: '/guardian/metrics'
|
|
209
|
+
};
|
|
210
|
+
workerPool?: {
|
|
211
|
+
enabled?: boolean;
|
|
212
|
+
poolSize?: number; // Default: 4
|
|
213
|
+
maxQueueSize?: number; // Default: 100
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Usage Examples
|
|
219
|
+
|
|
220
|
+
> 📚 **See [Usage Documentation](docs/USAGE.md) for more comprehensive examples including Express, Fastify, Koa.js integrations, and advanced patterns.**
|
|
221
|
+
|
|
222
|
+
### Basic Monitoring
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { Guardian } from 'node-runtime-guardian';
|
|
226
|
+
|
|
227
|
+
const guardian = new Guardian({
|
|
228
|
+
eventLoop: { thresholdMs: 100 },
|
|
229
|
+
memory: { driftDetectionEnabled: true },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
guardian.start();
|
|
233
|
+
|
|
234
|
+
guardian.on('warning', (warning) => {
|
|
235
|
+
console.error('Warning:', warning.message);
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Load Shedding Integration
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import express from 'express';
|
|
243
|
+
import { Guardian } from 'node-runtime-guardian';
|
|
244
|
+
|
|
245
|
+
const app = express();
|
|
246
|
+
const guardian = new Guardian({
|
|
247
|
+
protection: {
|
|
248
|
+
enabled: true,
|
|
249
|
+
loadShedding: {
|
|
250
|
+
enabled: true,
|
|
251
|
+
eventLoopThreshold: 200,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
guardian.start();
|
|
257
|
+
|
|
258
|
+
// Middleware to check protection
|
|
259
|
+
app.use((req, res, next) => {
|
|
260
|
+
guardian.trackRequest();
|
|
261
|
+
|
|
262
|
+
if (guardian.shouldRejectRequest()) {
|
|
263
|
+
const response = guardian.getRejectionResponse();
|
|
264
|
+
guardian.trackRequestComplete();
|
|
265
|
+
return res.status(response.statusCode).json({ error: response.message });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
res.on('finish', () => {
|
|
269
|
+
guardian.trackRequestComplete();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
next();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
app.get('/api/data', (req, res) => {
|
|
276
|
+
res.json({ data: 'Hello World' });
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Metrics Endpoint
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { Guardian, MetricsServer } from 'node-runtime-guardian';
|
|
284
|
+
|
|
285
|
+
const guardian = new Guardian();
|
|
286
|
+
guardian.start();
|
|
287
|
+
|
|
288
|
+
const metricsServer = new MetricsServer(guardian, {
|
|
289
|
+
port: 9090,
|
|
290
|
+
path: '/guardian/metrics',
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
metricsServer.start();
|
|
294
|
+
|
|
295
|
+
// Access metrics at http://localhost:9090/guardian/metrics
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Plugin System
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
import {
|
|
302
|
+
Guardian,
|
|
303
|
+
GuardianPlugin,
|
|
304
|
+
RuntimeMetrics,
|
|
305
|
+
} from 'node-runtime-guardian';
|
|
306
|
+
|
|
307
|
+
class CustomLoggerPlugin implements GuardianPlugin {
|
|
308
|
+
name = 'custom-logger';
|
|
309
|
+
|
|
310
|
+
onMetricUpdate(data: RuntimeMetrics): void {
|
|
311
|
+
console.log('Metrics:', {
|
|
312
|
+
eventLoopDelay: data.eventLoopDelay.mean,
|
|
313
|
+
memory: data.memory.heapUsed,
|
|
314
|
+
health: data.healthScore,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const guardian = new Guardian();
|
|
320
|
+
guardian.start();
|
|
321
|
+
|
|
322
|
+
// Register plugin (would need plugin registry integration)
|
|
323
|
+
// This is a conceptual example
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Worker Pool
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { WorkerPool } from 'node-runtime-guardian';
|
|
330
|
+
|
|
331
|
+
const pool = new WorkerPool('./worker.js', {
|
|
332
|
+
poolSize: 4,
|
|
333
|
+
maxQueueSize: 100,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Run a task
|
|
337
|
+
const result = await pool.run({ task: 'process-data' });
|
|
338
|
+
|
|
339
|
+
// Get pool statistics
|
|
340
|
+
const stats = pool.getStats();
|
|
341
|
+
console.log('Active tasks:', stats.activeTasks);
|
|
342
|
+
console.log('Queue size:', stats.queueSize);
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Metrics
|
|
346
|
+
|
|
347
|
+
The metrics endpoint returns the following data:
|
|
348
|
+
|
|
349
|
+
```json
|
|
350
|
+
{
|
|
351
|
+
"eventLoopDelay": {
|
|
352
|
+
"mean": 2.5,
|
|
353
|
+
"max": 10.2,
|
|
354
|
+
"min": 0.1,
|
|
355
|
+
"p50": 2.0,
|
|
356
|
+
"p95": 8.5,
|
|
357
|
+
"p99": 9.8
|
|
358
|
+
},
|
|
359
|
+
"memory": {
|
|
360
|
+
"heapUsed": 52428800,
|
|
361
|
+
"heapTotal": 67108864,
|
|
362
|
+
"rss": 134217728,
|
|
363
|
+
"external": 1024000,
|
|
364
|
+
"arrayBuffers": 512000
|
|
365
|
+
},
|
|
366
|
+
"gc": {
|
|
367
|
+
"estimatedCycles": 15,
|
|
368
|
+
"minorGCCount": 12,
|
|
369
|
+
"majorGCCount": 3,
|
|
370
|
+
"gcPressure": 0.45
|
|
371
|
+
},
|
|
372
|
+
"threadPool": {
|
|
373
|
+
"saturationLevel": 0.2,
|
|
374
|
+
"estimatedQueueSize": 0.8,
|
|
375
|
+
"avgLatency": 0.5
|
|
376
|
+
},
|
|
377
|
+
"cpu": {
|
|
378
|
+
"user": 125.5,
|
|
379
|
+
"system": 45.2
|
|
380
|
+
},
|
|
381
|
+
"activeRequests": 5,
|
|
382
|
+
"healthScore": 0.85,
|
|
383
|
+
"isProtectionActive": false,
|
|
384
|
+
"timestamp": 1704067200000
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Development
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
# Install dependencies
|
|
392
|
+
npm install
|
|
393
|
+
|
|
394
|
+
# Build
|
|
395
|
+
npm run build
|
|
396
|
+
|
|
397
|
+
# Run tests
|
|
398
|
+
npm test
|
|
399
|
+
|
|
400
|
+
# Lint
|
|
401
|
+
npm run lint
|
|
402
|
+
|
|
403
|
+
# Format code
|
|
404
|
+
npm run format
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Contributing
|
|
408
|
+
|
|
409
|
+
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
410
|
+
|
|
411
|
+
## License
|
|
412
|
+
|
|
413
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
414
|
+
|
|
415
|
+
## Acknowledgments
|
|
416
|
+
|
|
417
|
+
- Built with TypeScript
|
|
418
|
+
- Uses Node.js built-in modules only
|
|
419
|
+
- Inspired by production monitoring needs
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
**If this project helped you or sparked an idea, consider dropping a star or a kind word. It quietly keeps me motivated.**
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { EventLoopConfig, EventLoopMetrics } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Monitors event loop delay using perf_hooks
|
|
6
|
+
*/
|
|
7
|
+
export declare class EventLoopMonitor extends EventEmitter {
|
|
8
|
+
private monitor;
|
|
9
|
+
private interval;
|
|
10
|
+
private config;
|
|
11
|
+
private isRunning;
|
|
12
|
+
constructor(config?: EventLoopConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Start monitoring event loop delay
|
|
15
|
+
*/
|
|
16
|
+
start(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Stop monitoring
|
|
19
|
+
*/
|
|
20
|
+
stop(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Collect current metrics
|
|
23
|
+
*/
|
|
24
|
+
getMetrics(): EventLoopMetrics;
|
|
25
|
+
/**
|
|
26
|
+
* Reset monitor statistics
|
|
27
|
+
*/
|
|
28
|
+
reset(): void;
|
|
29
|
+
private collectMetrics;
|
|
30
|
+
private getEmptyMetrics;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=EventLoopMonitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventLoopMonitor.d.ts","sourceRoot":"","sources":["../../src/core/EventLoopMonitor.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACL,eAAe,EACf,gBAAgB,EAMjB,MAAM,UAAU,CAAC;AAElB;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,OAAO,CAAC,OAAO,CAAyD;IACxE,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,GAAE,eAAoB;IASxC;;OAEG;IACH,KAAK,IAAI,IAAI;IAeb;;OAEG;IACH,IAAI,IAAI,IAAI;IAcZ;;OAEG;IACH,UAAU,IAAI,gBAAgB;IAgB9B;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,cAAc;IAsDtB,OAAO,CAAC,eAAe;CAUxB"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventLoopMonitor = void 0;
|
|
4
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
5
|
+
const events_1 = require("events");
|
|
6
|
+
/**
|
|
7
|
+
* Monitors event loop delay using perf_hooks
|
|
8
|
+
*/
|
|
9
|
+
class EventLoopMonitor extends events_1.EventEmitter {
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
super();
|
|
12
|
+
this.monitor = null;
|
|
13
|
+
this.interval = null;
|
|
14
|
+
this.isRunning = false;
|
|
15
|
+
this.config = {
|
|
16
|
+
enabled: config.enabled ?? true,
|
|
17
|
+
thresholdMs: config.thresholdMs ?? 100,
|
|
18
|
+
intervalMs: config.intervalMs ?? 1000,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Start monitoring event loop delay
|
|
23
|
+
*/
|
|
24
|
+
start() {
|
|
25
|
+
if (!this.config.enabled || this.isRunning) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this.monitor = (0, perf_hooks_1.monitorEventLoopDelay)({ resolution: 10 });
|
|
29
|
+
this.monitor.enable();
|
|
30
|
+
this.interval = setInterval(() => {
|
|
31
|
+
this.collectMetrics();
|
|
32
|
+
}, this.config.intervalMs);
|
|
33
|
+
this.isRunning = true;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Stop monitoring
|
|
37
|
+
*/
|
|
38
|
+
stop() {
|
|
39
|
+
if (this.interval) {
|
|
40
|
+
clearInterval(this.interval);
|
|
41
|
+
this.interval = null;
|
|
42
|
+
}
|
|
43
|
+
if (this.monitor) {
|
|
44
|
+
this.monitor.disable();
|
|
45
|
+
this.monitor = null;
|
|
46
|
+
}
|
|
47
|
+
this.isRunning = false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Collect current metrics
|
|
51
|
+
*/
|
|
52
|
+
getMetrics() {
|
|
53
|
+
if (!this.monitor) {
|
|
54
|
+
return this.getEmptyMetrics();
|
|
55
|
+
}
|
|
56
|
+
const stats = this.monitor;
|
|
57
|
+
return {
|
|
58
|
+
mean: stats.mean / 1e6, // Convert nanoseconds to milliseconds
|
|
59
|
+
max: stats.max / 1e6,
|
|
60
|
+
min: stats.min / 1e6,
|
|
61
|
+
p50: stats.percentile(50) / 1e6,
|
|
62
|
+
p95: stats.percentile(95) / 1e6,
|
|
63
|
+
p99: stats.percentile(99) / 1e6,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Reset monitor statistics
|
|
68
|
+
*/
|
|
69
|
+
reset() {
|
|
70
|
+
if (this.monitor) {
|
|
71
|
+
this.monitor.reset();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
collectMetrics() {
|
|
75
|
+
const metrics = this.getMetrics();
|
|
76
|
+
if (metrics.mean > this.config.thresholdMs) {
|
|
77
|
+
const emptyMemory = {
|
|
78
|
+
heapUsed: 0,
|
|
79
|
+
heapTotal: 0,
|
|
80
|
+
rss: 0,
|
|
81
|
+
external: 0,
|
|
82
|
+
arrayBuffers: 0,
|
|
83
|
+
};
|
|
84
|
+
const emptyGC = {
|
|
85
|
+
estimatedCycles: 0,
|
|
86
|
+
minorGCCount: 0,
|
|
87
|
+
majorGCCount: 0,
|
|
88
|
+
gcPressure: 0,
|
|
89
|
+
};
|
|
90
|
+
const emptyThreadPool = {
|
|
91
|
+
saturationLevel: 0,
|
|
92
|
+
estimatedQueueSize: 0,
|
|
93
|
+
avgLatency: 0,
|
|
94
|
+
};
|
|
95
|
+
const emptyCPU = {
|
|
96
|
+
user: 0,
|
|
97
|
+
system: 0,
|
|
98
|
+
};
|
|
99
|
+
const warning = {
|
|
100
|
+
type: 'eventLoopBlocked',
|
|
101
|
+
message: `Event loop delay exceeded threshold: ${metrics.mean.toFixed(2)}ms > ${this.config.thresholdMs}ms`,
|
|
102
|
+
severity: metrics.mean > this.config.thresholdMs * 2 ? 'high' : 'medium',
|
|
103
|
+
metrics: {
|
|
104
|
+
eventLoopDelay: metrics,
|
|
105
|
+
memory: emptyMemory,
|
|
106
|
+
gc: emptyGC,
|
|
107
|
+
threadPool: emptyThreadPool,
|
|
108
|
+
cpu: emptyCPU,
|
|
109
|
+
activeRequests: 0,
|
|
110
|
+
timestamp: Date.now(),
|
|
111
|
+
},
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
};
|
|
114
|
+
this.emit('warning', warning);
|
|
115
|
+
this.emit('eventLoopBlocked', metrics);
|
|
116
|
+
}
|
|
117
|
+
this.emit('metric', metrics);
|
|
118
|
+
}
|
|
119
|
+
getEmptyMetrics() {
|
|
120
|
+
return {
|
|
121
|
+
mean: 0,
|
|
122
|
+
max: 0,
|
|
123
|
+
min: 0,
|
|
124
|
+
p50: 0,
|
|
125
|
+
p95: 0,
|
|
126
|
+
p99: 0,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.EventLoopMonitor = EventLoopMonitor;
|
|
131
|
+
//# sourceMappingURL=EventLoopMonitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventLoopMonitor.js","sourceRoot":"","sources":["../../src/core/EventLoopMonitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { GCConfig, GCMetrics } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Monitors GC pressure by observing heap drop patterns
|
|
6
|
+
*/
|
|
7
|
+
export declare class GCMonitor extends EventEmitter {
|
|
8
|
+
private interval;
|
|
9
|
+
private config;
|
|
10
|
+
private isRunning;
|
|
11
|
+
private heapHistory;
|
|
12
|
+
private readonly maxHistorySize;
|
|
13
|
+
private minorGCCount;
|
|
14
|
+
private majorGCCount;
|
|
15
|
+
private lastHeapSize;
|
|
16
|
+
constructor(config?: GCConfig);
|
|
17
|
+
/**
|
|
18
|
+
* Start monitoring GC
|
|
19
|
+
*/
|
|
20
|
+
start(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Stop monitoring
|
|
23
|
+
*/
|
|
24
|
+
stop(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Get current GC metrics
|
|
27
|
+
*/
|
|
28
|
+
getMetrics(): GCMetrics;
|
|
29
|
+
private collectMetrics;
|
|
30
|
+
private calculatePressure;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=GCMonitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GCMonitor.d.ts","sourceRoot":"","sources":["../../src/core/GCMonitor.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACL,QAAQ,EACR,SAAS,EAMV,MAAM,UAAU,CAAC;AAElB;;GAEG;AACH,qBAAa,SAAU,SAAQ,YAAY;IACzC,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAO;IACtC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,YAAY,CAAK;gBAEb,MAAM,GAAE,QAAa;IAQjC;;OAEG;IACH,KAAK,IAAI,IAAI;IAcb;;OAEG;IACH,IAAI,IAAI,IAAI;IAYZ;;OAEG;IACH,UAAU,IAAI,SAAS;IAiBvB,OAAO,CAAC,cAAc;IA+EtB,OAAO,CAAC,iBAAiB;CAuB1B"}
|