@bernierllc/retry-metrics
v0.2.0
Published
Atomic retry metrics collection and analysis utilities
Readme
@bernierllc/retry-metrics
Atomic retry metrics and analytics utilities for the BernierLLC package suite.
Overview
@bernierllc/retry-metrics provides comprehensive metrics collection, analytics, and performance monitoring for retry operations. It's designed to be a lightweight, atomic package that can be used independently or as part of larger retry systems.
Features
- Real-time Metrics Collection: Track retry attempts, success rates, and performance metrics
- Performance Scoring: Calculate performance scores with customizable weights and grades
- Analytics & Reporting: Generate comprehensive reports with distributions and trends
- Alert System: Configurable alerts for performance thresholds
- Storage Adapters: In-memory and Redis storage options
- Event System: Subscribe to metrics events for real-time monitoring
- Time-based Aggregation: Aggregate metrics by minute, hour, day, week, or month
- Custom Metrics: Add application-specific metrics alongside standard ones
Installation
npm install @bernierllc/retry-metricsQuick Start
import { MetricsCollector, createMetricsStorage } from '@bernierllc/retry-metrics';
// Create a metrics collector
const collector = new MetricsCollector({
enabled: true,
maxStoredAttempts: 1000
});
// Record retry attempts
collector.recordAttemptStart('operation-1', 1);
// ... perform retry operation ...
collector.recordAttemptEnd('operation-1', 1, true, 150);
// Get current metrics
const metrics = collector.getMetrics();
console.log(`Success rate: ${(metrics.successfulRetries / metrics.totalRetries * 100).toFixed(1)}%`);
// Calculate performance score
const score = collector.calculatePerformanceScore();
console.log(`Performance grade: ${score.grade} (${(score.score * 100).toFixed(1)}%)`);API Reference
MetricsCollector
The main class for collecting and managing retry metrics.
Constructor
new MetricsCollector(config: MetricsCollectorConfig)Configuration Options:
enabled: Whether metrics collection is enabled (default:true)storage: Optional storage adapter for persistenceperformanceWeights: Custom weights for performance scoringmaxStoredAttempts: Maximum attempts to store in memory (default:1000)cleanupInterval: Auto-cleanup interval in milliseconds (default:60000)
Methods
recordAttemptStart(id: string, attempt: number, metadata?: Record<string, unknown>): void
Record the start of a retry attempt.
collector.recordAttemptStart('api-call-1', 1, { endpoint: '/users' });recordAttemptEnd(id: string, attempt: number, success: boolean, duration: number, error?: string): void
Record the completion of a retry attempt.
collector.recordAttemptEnd('api-call-1', 1, true, 150);
// or for failed attempts
collector.recordAttemptEnd('api-call-1', 1, false, 200, 'Network timeout');getMetrics(): RetryMetrics
Get current metrics snapshot.
const metrics = collector.getMetrics();
console.log(`Total retries: ${metrics.totalRetries}`);
console.log(`Success rate: ${(metrics.successfulRetries / metrics.totalRetries * 100).toFixed(1)}%`);calculatePerformanceScore(weights?: Partial<PerformanceWeights>): PerformanceScore
Calculate overall performance score.
const score = collector.calculatePerformanceScore();
console.log(`Grade: ${score.grade}, Score: ${(score.score * 100).toFixed(1)}%`);
console.log('Recommendations:', score.recommendations);on(eventType: string, listener: MetricsEventListener): void
Subscribe to metrics events.
collector.on('attempt_completed', (event) => {
console.log(`Attempt ${event.data.id} completed: ${event.data.success}`);
});
collector.on('metrics_updated', (event) => {
console.log('Metrics updated:', event.data.metrics);
});updateMemoryUsage(usage: number): void
Update current memory usage.
collector.updateMemoryUsage(process.memoryUsage().heapUsed);addCustomMetric(key: string, value: number): void
Add custom application-specific metrics.
collector.addCustomMetric('api_latency_p95', 250);
collector.addCustomMetric('cache_hit_rate', 0.85);Storage Adapters
Memory Storage
import { MemoryMetricsStorage } from '@bernierllc/retry-metrics';
const storage = new MemoryMetricsStorage(1000); // Max 1000 attemptsRedis Storage
import { RedisMetricsStorage } from '@bernierllc/retry-metrics';
import Redis from 'ioredis';
const redis = new Redis();
const storage = new RedisMetricsStorage(redis, 'retry:metrics:', 86400);Factory Function
import { createMetricsStorage } from '@bernierllc/retry-metrics';
// Memory storage
const memoryStorage = createMetricsStorage('memory', { maxAttempts: 1000 });
// Redis storage
const redisStorage = createMetricsStorage('redis', {
redisClient: redis,
prefix: 'retry:metrics:',
ttl: 86400
});Analytics Utilities
Basic Calculations
import {
calculateSuccessRate,
calculateFailureRate,
calculateThroughput,
calculateMedianRetryTime,
calculate95thPercentileRetryTime
} from '@bernierllc/retry-metrics';
const successRate = calculateSuccessRate(metrics);
const failureRate = calculateFailureRate(metrics);
const throughput = calculateThroughput(attempts, 60000); // ops per minute
const medianTime = calculateMedianRetryTime(attempts);
const p95Time = calculate95thPercentileRetryTime(attempts);Distributions
import {
calculateErrorDistribution,
calculateHourlyDistribution,
calculateDailyDistribution
} from '@bernierllc/retry-metrics';
const errorDist = calculateErrorDistribution(attempts);
const hourlyDist = calculateHourlyDistribution(attempts);
const dailyDist = calculateDailyDistribution(attempts);Aggregation
import { aggregateMetrics } from '@bernierllc/retry-metrics';
const hourlyMetrics = await aggregateMetrics(
attempts,
startTime,
endTime,
'hour'
);Performance Scoring
import { calculatePerformanceScore } from '@bernierllc/retry-metrics';
const score = calculatePerformanceScore(metrics, {
successRate: 0.4,
averageTime: 0.3,
memoryUsage: 0.2,
throughput: 0.1
});Report Generation
import { generateMetricsReport } from '@bernierllc/retry-metrics';
const report = generateMetricsReport(metrics, attempts, {
start: new Date('2025-01-01T00:00:00Z'),
end: new Date('2025-01-01T23:59:59Z')
});
console.log('Summary:', report.summary);
console.log('Performance:', report.performance);
console.log('Distributions:', report.distribution);
console.log('Trends:', report.trends);Types
RetryMetrics
interface RetryMetrics {
totalRetries: number;
successfulRetries: number;
failedRetries: number;
averageRetryTime: number;
memoryUsage: number;
lastUpdated: Date;
customMetrics?: Record<string, number>;
}RetryAttempt
interface RetryAttempt {
id: string;
attempt: number;
success: boolean;
duration: number;
error?: string;
startTime: Date;
endTime: Date;
metadata?: Record<string, unknown>;
}PerformanceScore
interface PerformanceScore {
score: number; // 0-1
components: {
successRate: number;
averageTime: number;
memoryUsage: number;
throughput: number;
};
grade: 'A' | 'B' | 'C' | 'D' | 'F';
recommendations: string[];
}Alert
interface Alert {
type: 'success_rate' | 'average_time' | 'memory_usage' | 'performance_score';
severity: 'warning' | 'critical';
message: string;
currentValue: number;
thresholdValue: number;
timestamp: Date;
context?: Record<string, unknown>;
}Examples
Basic Usage with Alerts
import { MetricsCollector } from '@bernierllc/retry-metrics';
const collector = new MetricsCollector({
enabled: true,
onAlert: (alert) => {
console.log(`[${alert.severity.toUpperCase()}] ${alert.message}`);
// Send to monitoring system, Slack, etc.
}
});
// Configure alert thresholds
collector.configureAlerts({
enabled: true,
thresholds: {
minSuccessRate: 0.8,
maxAverageTime: 5000,
maxMemoryUsage: 100 * 1024 * 1024, // 100MB
minPerformanceScore: 0.7
}
});Integration with Retry System
import { MetricsCollector } from '@bernierllc/retry-metrics';
class RetryManager {
private metrics: MetricsCollector;
constructor() {
this.metrics = new MetricsCollector({
enabled: true,
maxStoredAttempts: 10000
});
}
async executeWithRetry<T>(id: string, fn: () => Promise<T>, maxRetries: number = 3): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const startTime = Date.now();
this.metrics.recordAttemptStart(id, attempt);
try {
const result = await fn();
const duration = Date.now() - startTime;
this.metrics.recordAttemptEnd(id, attempt, true, duration);
return result;
} catch (error) {
const duration = Date.now() - startTime;
this.metrics.recordAttemptEnd(id, attempt, false, duration, error.message);
if (attempt === maxRetries) {
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}
getMetrics() {
return this.metrics.getMetrics();
}
getPerformanceScore() {
return this.metrics.calculatePerformanceScore();
}
}Redis Storage with Persistence
import { MetricsCollector, createMetricsStorage } from '@bernierllc/retry-metrics';
import Redis from 'ioredis';
const redis = new Redis();
const storage = createMetricsStorage('redis', {
redisClient: redis,
prefix: 'myapp:retry:',
ttl: 86400 * 7 // 7 days
});
const collector = new MetricsCollector({
enabled: true,
storage,
maxStoredAttempts: 5000
});
// Metrics will be automatically persisted to Redis
collector.recordAttemptStart('api-call', 1);
collector.recordAttemptEnd('api-call', 1, true, 150);
// Retrieve historical metrics
const historicalMetrics = await storage.getMetrics(
new Date('2025-01-01T00:00:00Z'),
new Date('2025-01-01T23:59:59Z')
);Custom Metrics and Monitoring
import { MetricsCollector } from '@bernierllc/retry-metrics';
const collector = new MetricsCollector({
enabled: true
});
// Add custom metrics
collector.addCustomMetric('api_latency_p95', 250);
collector.addCustomMetric('cache_hit_rate', 0.85);
collector.addCustomMetric('database_connections', 12);
// Subscribe to events for monitoring
collector.on('metrics_updated', (event) => {
const metrics = event.data.metrics;
// Send to monitoring system
monitoringSystem.record('retry.success_rate', metrics.successfulRetries / metrics.totalRetries);
monitoringSystem.record('retry.average_time', metrics.averageRetryTime);
monitoringSystem.record('retry.memory_usage', metrics.memoryUsage);
// Log custom metrics
Object.entries(metrics.customMetrics || {}).forEach(([key, value]) => {
monitoringSystem.record(`retry.custom.${key}`, value);
});
});
collector.on('performance_score_calculated', (event) => {
const score = event.data.performanceScore;
if (score.grade === 'F') {
// Send critical alert
alertSystem.sendCritical('Retry performance is critically low', score);
}
});Performance Considerations
- Memory Usage: The in-memory storage keeps attempts in memory. Use
maxStoredAttemptsto limit memory usage. - Redis Performance: Redis storage uses sorted sets for time-based queries. Consider Redis memory limits and TTL settings.
- Event Listeners: Remove event listeners when no longer needed to prevent memory leaks.
- Cleanup: Use
clearOldMetrics()to remove old data and free memory.
Testing
import { MetricsCollector } from '@bernierllc/retry-metrics';
describe('Retry Metrics', () => {
let collector: MetricsCollector;
beforeEach(() => {
collector = new MetricsCollector({ enabled: true });
});
afterEach(() => {
collector.destroy();
});
it('should track retry attempts', () => {
collector.recordAttemptStart('test-1', 1);
collector.recordAttemptEnd('test-1', 1, true, 100);
const metrics = collector.getMetrics();
expect(metrics.totalRetries).toBe(1);
expect(metrics.successfulRetries).toBe(1);
});
});License
MIT License - see LICENSE file for details.
Contributing
This package is part of the BernierLLC tools suite. Please refer to the main project documentation for contribution guidelines.
