UNPKG

@esmj/monitor

Version:

Node.js performance measurement metrics (cpu, memory, event loop, gc)

566 lines (436 loc) 18.1 kB
# ESMJ-Monitor The `@esmj/monitor` is module for collecting node metrics from native node API. ## Features - Event Loop Utilization monitoring - Event Loop Delay monitoring - CPU Usage monitoring - Memory Usage monitoring - GC (Garbage Collection) metrics - HTTP Request monitoring - Load Average monitoring - Process information - Severity Analysis for system health - DoS and DDoS attack detection ## Requirements - Node 18+ ## Installation ```bash npm install @esmj/monitor ``` ## Basic Usage It works for both Javascript modules (ESM and CJS) and has full Typescript support. ```javascript // server.js import { createMonitoring } from '@esmj/monitor'; const { monitor, metricsHistory, severity, start, stop } = createMonitoring(); // Start collecting metrics start(); // Subscribe to metrics updates const { unsubscribe } = monitor.subscribe((metrics) => { console.log('Current metrics:', metrics); // { // timestamp: 1697049474653, // cpuUsage: { user: 1692, system: 925, percent: 0.26 }, // eventLoopDelay: { // min: 20.07, // max: 21.15, // mean: 20.78, // stddev: 0.38, // percentile80: 21.08 // }, // eventLoopUtilization: { // idle: 992.72, // active: 7.85, // utilization: 0.01 // }, // loadAverage: { // minute1: 3.38, // minute5: 8.28, // minute15: 9.15 // }, // memoryUsage: { // percent: 5.23, // rss: 54.2, // heapTotal: 20.2, // heapUsed: 17.74, // external: 0.9, // arrayBuffers: 0.07 // }, // gc: { entry: null }, // process: { // pid: 53509, // ppid: 53480, // platform: 'darwin', // uptime: 14.656514084, // version: 'v18.12.1' // }, // request: { // count: { // total: 45, // active: 3, // zombie: 0, // clientErrors: 2, // serverErrors: 0, // connections: 15 // }, // duration: { // 10: 1, // 10 requests completed in under 10ms // 25: 5, // 5 requests completed in 10-25ms // 50: 10, // 10 requests completed in 25-50ms // 100: 20, // 20 requests completed in 50-100ms // 200: 30, // 30 requests completed in 100-200ms // 500: 50, // 50 requests completed in 200-500ms // 1000: 60, // 60 requests completed in 500-1000ms // 2000: 70, // 70 requests completed in 1000-2000ms // 5000: 80, // 80 requests completed in 2000-5000ms // 'Infinity': 90, // 90 requests completed in above 5000ms // }, // } // } }); // Check system health using severity analysis setInterval(() => { const threats = severity.getThreats(); console.log(`Current severity: ${threats.level} (score: ${threats.score})`); if (threats.level === 'high' || threats.level === 'critical' || threats.level === 'fatal') { console.warn('ALERT: System under stress!', threats.records); } }, 5000); // For per-request threat assessment (e.g., in Express middleware) // app.use((req, res, next) => { // const threats = severity.getThreats(req); // // if (threats.level === 'critical' || threats.level === 'fatal') { // res.status(503).send('Service temporarily unavailable'); // return; // } // // next(); // }); // Access metrics history and create custom metrics setTimeout(() => { console.log(metricsHistory.size); // 15 console.log(metricsHistory.current); // return last captured metric structure // Custom metrics can be added and used metricsHistory.add('getCPUPercent', pipe( metricsHistory.from('cpuUsage.percent'), takeLast(5), avg() )); console.log(metricsHistory.custom.getCPUPercent()); // e.g., 0.26 }, 5000); setTimeout(() => { unsubscribe(); stop(); }, 15000); ``` ## Advanced Configuration ```javascript const { monitor, metricsHistory, severity, start, stop } = createMonitoring({ monitor: { interval: 1000 }, // Collect metrics every second metricsHistory: { limit: 60 }, // Keep 1 minutes of history shortMonitor: { interval: 10 }, // High-frequency collection (10ms) severity: { threshold: { denialOfService: 50, // Request threshold for DoS detection distributedDenialOfService: 200, // Request threshold for DDoS detection deadlock: 20, // Active request threshold for deadlock detection oldDataToFatalTime: 3000 // Time threshold (in ms) without collected metrics to escalate to fatal severity level }, experimental: { evaluateMemoryUsage: true } // Enable experimental evaluations of threats } }); ``` ## Severity Analysis The severity analysis component evaluates multiple metrics to determine the health of your system: - **Normal**: System functioning properly - **Low**: Minor issues detected, monitor the situation - **Medium**: Performance degradation detected - **High**: Significant issues affecting performance - **Critical**: System under severe stress, immediate action required - **Fatal**: System on the verge of failure, emergency intervention needed ```javascript // Example severity assessment { score: 80, // Severity score (0-100) level: 'critical', // One of: 'normal', 'low', 'medium', 'high', 'critical', 'fatal' records: [ { score: 80, metric: 'denialOfServiceDetected' } // Other factors contributing to the score ] } ``` ## Architecture ``` ┌────────────┐ ┌──────────────────┐ ┌────────────────┐ │ │ │ │ │ │ │ Monitor │────▶│ MetricsHistory │────▶│ Subscribers │ │ │ │ │ │ │ └────────────┘ └──────────────────┘ └────────────────┘ │ ▲ │ │ │ ┌──────────────┐ │ └──────────▶│ Severity │─────────────────┘ │ │ └──────────────┘ ``` ## API ### Functions #### createMonitoring(options?) Factory function that creates and wires up all monitoring components. **Returns**: `{ monitor, metricsHistory, shortMonitor, shortMetricsHistory, severity, start, stop }` **Parameters**: - `options?` (object): Configuration options for the monitoring system - `monitor?` (object): Options for the main Monitor instance. Default: `{ interval: 1000 }` - `metricsHistory?` (object): Options for the main MetricsHistory instance. Default: `{ limit: 60 }` - `shortMonitor?` (object): Options for the high-frequency Monitor instance. Default: `{ interval: 10 }` - `shortMetricsHistory?` (object): Options for the high-frequency MetricsHistory instance. Default: `{ limit: 100 }` - `severity?` (object): Options for the Severity instance, including threshold configuration **Methods**: - `start()`: Start collecting metrics on both monitor and shortMonitor - `stop()`: Stop collecting metrics on both monitor and shortMonitor #### pipe(...) Composes functions from left to right. **Parameters**: - `...functions` (Function): The functions to compose **Returns**: (Function): A function that is the composition of all input functions #### memo(fn) Creates a memoized (cached) version of a function. **Parameters**: - `fn` (Function): The function to memoize **Returns**: (Function): A memoized version of the input function with a `clear()` method #### linearRegression() Creates a function that calculates linear regression from an array of values. **Returns**: (Function): A function that accepts an array and returns regression analysis with `slope`, `yIntercept` and `predict` method #### percentile(number) Creates a function that calculates the specified percentile from an array of values. **Parameters**: - `number` (Number): The percentile to calculate (0-100). Default: `50` **Returns**: (Function): A function that accepts an array and returns the specified percentile value #### medianNoiseReduction(grouping) Creates a function that applies median-based noise reduction to an array of values. **Parameters**: - `grouping` (Number): The size of the grouping window. Default: `5` **Returns**: (Function): A function that accepts an array and returns a noise-reduced array #### takeLast(size) Creates a function that returns the last N items from an array. **Parameters**: - `size` (Number): Number of items to take from the end **Returns**: (Function): A function that accepts an array and returns the last N items #### first() Creates a function that returns the first item from an array. **Returns**: (Function): A function that accepts an array and returns its first item #### last() Creates a function that returns the last item from an array. **Returns**: (Function): A function that accepts an array and returns its last item #### avg() Creates a function that calculates the average of an array of numbers. **Returns**: (Function): A function that accepts an array and returns its average value #### sum() Creates a function that calculates the sum of an array of numbers. **Returns**: (Function): A function that accepts an array and returns the sum of its values ### Classes #### Monitor Manages the collection of metrics at regular intervals. ##### Constructor ```javascript new Monitor(options?) ``` **Parameters**: - `options?` (object): Configuration options - `interval?` (number): Measurement interval in milliseconds. Default: `1000` ##### Methods - `start()`: Begin collecting metrics - `stop()`: Stop collecting metrics - `add(metric)`: Add a metric collector - `metric` (Metric): The metric collector to add - **Returns**: (Function): Function to remove the added metric - `remove(metric)`: Remove a metric collector - `metric` (Metric): The metric collector to remove - `subscribe(listener)`: Subscribe to metric updates - `listener` (Function): Callback function that receives metrics - **Returns**: (object): Subscription with `unsubscribe` method #### MetricsHistory Stores metrics history and provides data access functions. ##### Constructor ```javascript new MetricsHistory(options?) ``` **Parameters**: - `options?` (object): Configuration options - `limit?` (number): Maximum number of metrics to store. Default: `60` ##### Properties - `size` (number): Current number of stored metrics - `current` (object): The most recently captured metrics with timestamp - `custom` (object): Container for custom metric functions. Typed with `CustomMetrics` interface that can be extended, see [Custom Metrics](#custom-metrics) ##### Methods - `next(metric)`: Add a new metric to the history - `metric` (object): The metric to add - `complete()`: Clear all stored metrics - `add(name, func)`: Add a custom calculation function - `name` (string): Name of the function - `func` (Function): The calculation function - `from(key)`: Create a function that retrieves values for a specific key - `key` (string): Path in metrics structure - **Returns**: (Function): Function that returns values for the key - `getValues(key)`: Get all values for a specific metric key - `key` (string): Path in metrics structure - **Returns**: (Array): Array of values #### Severity Analyzes metrics to determine system health and detect attacks. ##### Constructor ```javascript new Severity(monitor, metricsHistory, shortMonitor, shortMetricsHistory, requestMetric, shortRequestMetric, options?) ``` **Parameters**: - `monitor` (Monitor): Primary metrics monitor - `metricsHistory` (MetricsHistory): History of metrics - `shortMonitor` (Monitor): High-frequency metrics monitor - `shortMetricsHistory` (MetricsHistory): High-frequency metrics history - `requestMetric` (RequestMetric): HTTP request metrics - `shortRequestMetric` (RequestMetric): High-frequency HTTP request metrics - `options?` (object): Configuration options - `threshold?` (object): Detection thresholds - `denialOfService?` (number): Request threshold for DoS detection. Default: `10` - `distributedDenialOfService?` (number): Request threshold for DDoS detection. Default: `20` - `deadlock?` (number): Active request threshold for deadlock detection. Default: `10` - `oldDataToFatalTime?` (number): Time threshold (in ms) without new metrics to escalate to fatal. Default: `4000` - `experimental?` (object): Enable experimental (under development) evaluations of specific threats. - `evaluateMemoryUsage?` (boolean): Enable experimental memory usage evaluation. Default: `false` ##### Methods - `init()`: Initialize the severity analyzer - `getThreats(req?)`: Calculate and return the current system severity assessment - **Parameters**: - `req?` (Request): Optional HTTP request object. Used for caching to ensure each request is evaluated only once - **Returns**: (object): `{ score, level, records }` - `score` (number): Severity score (0-100) - `level` (string): One of: 'normal', 'low', 'medium', 'high', 'critical', 'fatal' - `records` (Array): Factors contributing to the severity score - **Behavior**: - If called with the same `req` object multiple times, returns the cached result immediately - Evaluates system metrics (CPU utilization, memory usage, event loop delay) - Checks for request-based threats (DoS, DDoS, deadlock) if severity score < 80 - Each unique request is evaluated only once to prevent duplicate threat detection #### Metric Base class for all metric collectors. ##### Methods - `start(options)`: Called when monitoring starts - `beforeMeasure(options)`: Called before each measurement - `measure(options)`: Performs the measurement - **Returns**: (object): The collected metrics - `afterMeasure(options)`: Called after each measurement - `stop(options)`: Called when monitoring stops ##### Helpers To determine if a system should be considered under stress, there is exported helper `isSverityLevelAtLeast()` and object `SEVERITY_LEVEL` that provides all the currently supported levels: Basic usage: ```typescript import { isSeverityLevelAtLeast, SEVERITY_LEVEL } from "@esmj/monitor"; // get threats from your instance of Severity const threats = severity.getThreats(); // pass threats and minimum severity level to check const underStress = isSeverityLevelAtLeast(threats, SEVERITY_LEVEL.HIGH); ``` ## Custom Metrics The MetricsHistory class allows you to create custom metrics by combining utility functions. Custom metrics are useful for calculating specific insights from your collected data without constantly writing the same code. Custom metrics are stored in the `metricsHistory.custom` object that is typed with `CustomMetrics` `interface`, with names of the function as keys and their return types as values. **You can extend this interface in your project to add types for your custom metrics by redeclaring the interface** ### Adding Custom Metrics ```typescript import { pipe, memo, takeLast, avg } from '@esmj/monitor'; // Add a custom metric function metricsHistory.add('averageCPULastMinute', pipe( metricsHistory.from('cpuUsage.percent'), takeLast(60), // Last 60 measurements (1 minute at 1000ms interval) avg() )); // Add a memoized custom metric for better performance metricsHistory.add('memoizedCPUAverage', memo(pipe( metricsHistory.from('cpuUsage.percent'), takeLast(60), avg() ))); // Use the custom metrics later const cpuAverage = metricsHistory.custom.averageCPULastMinute(); console.log(`Average CPU usage over the last minute: ${cpuAverage}%`); // Memoized version - calculates once until metrics history changes const memoizedAvg = metricsHistory.custom.memoizedCPUAverage(); ``` If you are using TypeScript, you can extend the `CustomMetrics` interface with your custom metrics functions: ```typescript import { type MemoizedFunction, type MetricsFunction } from "@esmj/monitor"; declare module '@esmj/monitor' { export interface CustomMetrics { averageCPULastMinute: MetricsFunction<number>; // If you used utility function memo(), you can import also MemoizedMetricsFunction type to help you with typing your custom metrics functions. memoizedCPUAverage: MemoizedMetricsFunction<MetricsFunction<number>>; } } ``` ### Common Custom Metric Examples ```javascript import { pipe, memo, linearRegression, percentile, takeLast, first, last, avg, sum } from '@esmj/monitor'; // Memory growth trend detection - with memoization for performance metricsHistory.add('memoryTrend', memo(pipe( metricsHistory.from('memoryUsage.rss'), takeLast(30), linearRegression() ))); // Then check if memory is growing too fast const { slope } = metricsHistory.custom.memoryTrend(); if (slope > 5) { console.warn('Memory usage is increasing rapidly!'); } // 95th percentile of event loop delay metricsHistory.add('p95EventLoopDelay', memo(pipe( metricsHistory.from('eventLoopDelay.max'), takeLast(60), percentile(95) ))); ``` ## Utility Functions These utility functions can be imported directly from the module: ```javascript import { pipe, memo, linearRegression, percentile, medianNoiseReduction, takeLast, first, last, avg, sum } from '@esmj/monitor'; ``` They can be combined to create powerful data transformations: ```javascript // Calculate average CPU usage from the last 10 measurements const getAvgCPU = pipe( metricsHistory.from('cpuUsage.percent'), takeLast(10), avg() ); // Get highest memory usage from the last minute const getMaxMemory = pipe( metricsHistory.from('memoryUsage.heapUsed'), takeLast(60), percentile(100) ); // Create a noise-reduced trend analysis of event loop delay const smoothedDelay = pipe( metricsHistory.from('eventLoopDelay.max'), medianNoiseReduction(5) ); ``` ## Contributing Issues and pull requests are welcome! Feel free to contribute. ## License MIT