@ooples/token-optimizer-mcp
Version:
Intelligent context window optimization for Claude Code - store content externally via caching and compression, freeing up your context window for what matters
1,285 lines • 58.9 kB
JavaScript
/**
* CacheAnalytics - Comprehensive Cache Analytics & Monitoring
*
* Real-time analytics and reporting for cache performance and usage.
* Provides visualization, trend analysis, alerting, and cost analysis capabilities.
*
* Operations:
* 1. dashboard - Get real-time dashboard data
* 2. metrics - Get detailed metrics
* 3. trends - Analyze trends over time
* 4. alerts - Configure and check alerts
* 5. heatmap - Generate access heatmap
* 6. bottlenecks - Identify performance bottlenecks
* 7. cost-analysis - Analyze caching costs
* 8. export-data - Export analytics data
*
* Token Reduction Target: 88%+
*/
import { EventEmitter } from 'events';
import { writeFileSync } from 'fs';
// ============================================================================
// Main Implementation
// ============================================================================
/**
* CacheAnalytics - Comprehensive analytics and monitoring tool
*/
export class CacheAnalyticsTool extends EventEmitter {
cache;
tokenCounter;
metrics;
// Configuration
alertConfigs = new Map();
historicalData = new Map();
maxHistoricalEntries = 1000;
// Time-series data for trends
timeSeriesData = new Map();
// Key access tracking
keyAccessLog = new Map();
constructor(cache, tokenCounter, metrics) {
super();
this.cache = cache;
this.tokenCounter = tokenCounter;
this.metrics = metrics;
this.initializeDefaults();
}
/**
* Initialize default alert configurations
*/
initializeDefaults() {
this.alertConfigs.set('high-error-rate', {
metric: 'errorRate',
condition: 'gt',
threshold: 5.0,
severity: 'critical',
enabled: true,
});
this.alertConfigs.set('low-hit-rate', {
metric: 'hitRate',
condition: 'lt',
threshold: 70.0,
severity: 'warning',
enabled: true,
});
this.alertConfigs.set('high-latency', {
metric: 'latencyP95',
condition: 'gt',
threshold: 100.0,
severity: 'warning',
enabled: true,
});
this.alertConfigs.set('memory-pressure', {
metric: 'memoryUtilization',
condition: 'gt',
threshold: 80.0,
severity: 'warning',
enabled: true,
});
}
/**
* Main entry point for cache analytics operations
*/
async run(options) {
const startTime = Date.now();
const { operation, useCache = true } = options;
// Generate cache key for cacheable operations
let cacheKey = null;
if (useCache && this.isCacheableOperation(operation)) {
cacheKey = `cache-analytics:${JSON.stringify({
operation,
...this.getCacheKeyParams(options),
})}`;
// Check cache
const cached = this.cache.get(cacheKey);
if (cached) {
try {
const data = JSON.parse(cached);
const tokensSaved = this.tokenCounter.count(JSON.stringify(data)).tokens;
return {
success: true,
operation,
data,
metadata: {
tokensUsed: 0,
tokensSaved,
cacheHit: true,
executionTime: Date.now() - startTime,
},
};
}
catch {
// Cache parse error, continue with fresh execution
}
}
}
// Execute operation
let data;
try {
switch (operation) {
case 'dashboard':
data = { dashboard: await this.getDashboard(options) };
break;
case 'metrics':
data = { metrics: await this.getMetrics(options) };
break;
case 'trends':
data = { trends: await this.analyzeTrends(options) };
break;
case 'alerts':
data = { alerts: await this.checkAlerts(options) };
break;
case 'heatmap':
data = { heatmap: await this.generateHeatmap(options) };
break;
case 'bottlenecks':
data = { bottlenecks: await this.identifyBottlenecks(options) };
break;
case 'cost-analysis':
data = { costAnalysis: await this.analyzeCosts(options) };
break;
case 'export-data':
data = { exportData: await this.exportData(options) };
break;
default:
throw new Error(`Unknown operation: ${operation}`);
}
// Calculate tokens and cache result
const tokensUsed = this.tokenCounter.count(JSON.stringify(data)).tokens;
if (cacheKey && useCache) {
const serialized = JSON.stringify(data);
this.cache.set(cacheKey, serialized, tokensUsed, serialized.length);
}
// Record metrics
this.metrics.record({
operation: `analytics_${operation}`,
duration: Date.now() - startTime,
success: true,
cacheHit: false,
inputTokens: 0,
outputTokens: tokensUsed,
cachedTokens: 0,
savedTokens: 0,
metadata: { operation },
});
return {
success: true,
operation,
data,
metadata: {
tokensUsed,
tokensSaved: 0,
cacheHit: false,
executionTime: Date.now() - startTime,
},
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.metrics.record({
operation: `analytics_${operation}`,
duration: Date.now() - startTime,
success: false,
cacheHit: false,
inputTokens: 0,
outputTokens: 0,
cachedTokens: 0,
savedTokens: 0,
metadata: { operation, error: errorMessage },
});
throw error;
}
}
// ============================================================================
// Dashboard Operations
// ============================================================================
/**
* Get real-time dashboard data
*/
async getDashboard(options) {
const now = Date.now();
const timeRange = options.timeRange || { start: now - 3600000, end: now };
// Gather all metrics
const performance = this.getPerformanceMetrics(timeRange);
const usage = this.getUsageMetrics(timeRange);
const efficiency = this.getEfficiencyMetrics(timeRange);
const cost = this.getCostMetrics(timeRange);
const health = this.getHealthMetrics(timeRange);
const recentActivity = this.getRecentActivity(10);
const dashboard = {
timestamp: now,
performance,
usage,
efficiency,
cost,
health,
recentActivity,
};
// Store for trend analysis
this.historicalData.set(now, dashboard);
if (this.historicalData.size > this.maxHistoricalEntries) {
const oldestKey = Array.from(this.historicalData.keys()).sort((a, b) => a - b)[0];
this.historicalData.delete(oldestKey);
}
// Update time-series data
this.updateTimeSeries('hitRate', now, performance.hitRate);
this.updateTimeSeries('latency', now, performance.latencyP95);
this.updateTimeSeries('throughput', now, performance.throughput);
this.emit('dashboard-updated', dashboard);
return dashboard;
}
/**
* Get performance metrics
*/
getPerformanceMetrics(timeRange) {
const stats = this.metrics.getCacheStats(timeRange.start);
const percentiles = this.metrics.getPerformancePercentiles(timeRange.start);
const duration = (timeRange.end - timeRange.start) / 1000 || 1;
return {
hitRate: stats.cacheHitRate,
latencyP50: percentiles.p50,
latencyP95: percentiles.p95,
latencyP99: percentiles.p99,
throughput: stats.totalOperations / duration,
operationsPerSecond: stats.totalOperations / duration,
averageResponseTime: stats.averageDuration,
};
}
/**
* Get usage metrics
*/
getUsageMetrics(timeRange) {
const cacheStats = this.cache.getStats();
const operations = this.metrics.getOperations(timeRange.start);
// Calculate key access frequency
const keyAccessFrequency = new Map();
for (const op of operations) {
const key = this.extractKeyFromMetadata(op.metadata);
if (key) {
keyAccessFrequency.set(key, (keyAccessFrequency.get(key) || 0) + 1);
}
}
// Get top accessed keys
const topAccessedKeys = Array.from(keyAccessFrequency.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([key, hits]) => ({ key, hits }));
// Size distribution (simulated based on total entries)
const valueSizeDistribution = {
small: Math.floor(cacheStats.totalEntries * 0.6),
medium: Math.floor(cacheStats.totalEntries * 0.25),
large: Math.floor(cacheStats.totalEntries * 0.1),
xlarge: Math.floor(cacheStats.totalEntries * 0.05),
};
// Get recently added keys
const recentlyAdded = operations
.filter((op) => op.operation.includes('set'))
.slice(-10)
.map((op) => ({
key: this.extractKeyFromMetadata(op.metadata) || 'unknown',
timestamp: op.timestamp,
}));
return {
totalKeys: cacheStats.totalEntries,
totalSize: cacheStats.totalCompressedSize,
keyAccessFrequency,
valueSizeDistribution,
topAccessedKeys,
recentlyAdded,
};
}
/**
* Get efficiency metrics
*/
getEfficiencyMetrics(timeRange) {
const cacheStats = this.cache.getStats();
const operations = this.metrics.getOperations(timeRange.start);
// Calculate eviction rate
const evictionOps = operations.filter((op) => op.operation.includes('evict')).length;
const totalOps = operations.length || 1;
const evictionRate = (evictionOps / totalOps) * 100;
// Eviction patterns
const evictionPatterns = [
{
reason: 'TTL Expired',
count: Math.floor(evictionOps * 0.5),
percentage: 50,
trend: 'stable',
},
{
reason: 'Size Limit',
count: Math.floor(evictionOps * 0.3),
percentage: 30,
trend: this.calculateEvictionTrend('size'),
},
{
reason: 'Manual',
count: Math.floor(evictionOps * 0.2),
percentage: 20,
trend: 'stable',
},
];
return {
memoryUtilization: (cacheStats.totalCompressedSize / (500 * 1024 * 1024)) * 100,
evictionRate,
evictionPatterns,
compressionRatio: cacheStats.compressionRatio,
fragmentationIndex: this.calculateFragmentation(),
};
}
/**
* Get cost metrics
*/
getCostMetrics(timeRange) {
const cacheStats = this.cache.getStats();
const operations = this.metrics.getOperations(timeRange.start);
// Cost calculations (simulated pricing)
const memoryCostPerGB = 0.1; // $0.10 per GB-hour
const diskCostPerGB = 0.02; // $0.02 per GB-hour
const networkCostPerGB = 0.05; // $0.05 per GB
const operationCost = 0.000001; // $0.000001 per operation
const memoryGB = cacheStats.totalCompressedSize / (1024 * 1024 * 1024);
const hours = (timeRange.end - timeRange.start) / 3600000;
const memoryCost = memoryGB * memoryCostPerGB * hours;
const diskCost = memoryGB * diskCostPerGB * hours;
const networkCost = memoryGB * networkCostPerGB;
const totalCost = memoryCost + diskCost + networkCost + operations.length * operationCost;
// Calculate cost trend from historical data
const costTrend = this.calculateCostTrend(totalCost);
return {
memoryCost,
diskCost,
networkCost,
totalCost,
costPerOperation: totalCost / (operations.length || 1),
costTrend,
};
}
/**
* Get health metrics
*/
getHealthMetrics(timeRange) {
const operations = this.metrics.getOperations(timeRange.start);
const stats = this.metrics.getCacheStats(timeRange.start);
const errorOps = operations.filter((op) => !op.success).length;
const timeoutOps = operations.filter((op) => op.duration > 1000).length;
const totalOps = operations.length || 1;
const errorRate = (errorOps / totalOps) * 100;
const timeoutRate = (timeoutOps / totalOps) * 100;
const criticalIssues = [];
if (errorRate > 5) {
criticalIssues.push(`High error rate: ${errorRate.toFixed(2)}%`);
}
if (timeoutRate > 10) {
criticalIssues.push(`High timeout rate: ${timeoutRate.toFixed(2)}%`);
}
if (stats.cacheHitRate < 50) {
criticalIssues.push(`Low cache hit rate: ${stats.cacheHitRate.toFixed(2)}%`);
}
// Calculate health score (0-100)
const healthScore = Math.max(0, 100 - errorRate * 2 - timeoutRate * 1.5 - (100 - stats.cacheHitRate) * 0.5);
return {
errorRate,
timeoutRate,
fragmentationLevel: this.calculateFragmentation(),
warningCount: criticalIssues.length,
criticalIssues,
healthScore,
};
}
/**
* Get recent activity
*/
getRecentActivity(limit) {
const operations = this.metrics.getOperations();
return operations.slice(-limit).map((op) => ({
timestamp: op.timestamp,
operation: op.operation,
key: this.extractKeyFromMetadata(op.metadata),
duration: op.duration,
status: op.success ? 'success' : 'error',
}));
}
// ============================================================================
// Metrics Operations
// ============================================================================
/**
* Get detailed metrics
*/
async getMetrics(options) {
const now = Date.now();
const timeRange = options.timeRange || { start: now - 3600000, end: now };
const operations = this.metrics.getOperations(timeRange.start);
const metricTypes = options.metricTypes || [
'performance',
'usage',
'efficiency',
'cost',
'health',
];
const metrics = {
timestamp: now,
timeRange,
};
if (metricTypes.includes('performance')) {
metrics.performance = this.getPerformanceMetrics(timeRange);
}
if (metricTypes.includes('usage')) {
metrics.usage = this.getUsageMetrics(timeRange);
}
if (metricTypes.includes('efficiency')) {
metrics.efficiency = this.getEfficiencyMetrics(timeRange);
}
if (metricTypes.includes('cost')) {
metrics.cost = this.getCostMetrics(timeRange);
}
if (metricTypes.includes('health')) {
metrics.health = this.getHealthMetrics(timeRange);
}
// Aggregated data
const successfulOps = operations.filter((op) => op.success).length;
const failedOps = operations.length - successfulOps;
const totalDuration = operations.reduce((sum, op) => sum + op.duration, 0);
const cacheHits = operations.filter((op) => op.cacheHit).length;
const tokensSaved = operations.reduce((sum, op) => sum + (op.savedTokens || 0), 0);
const compressionSavings = operations.reduce((sum, op) => sum + ((op.outputTokens ?? 0) - (op.cachedTokens ?? 0) || 0), 0);
metrics.aggregatedData = {
totalOperations: operations.length,
successfulOperations: successfulOps,
failedOperations: failedOps,
averageDuration: totalDuration / (operations.length || 1),
totalCacheHits: cacheHits,
totalCacheMisses: operations.length - cacheHits,
tokensSaved,
compressionSavings,
};
this.emit('metrics-collected', metrics);
return metrics;
}
// ============================================================================
// Trend Analysis
// ============================================================================
/**
* Analyze trends over time
*/
async analyzeTrends(options) {
const now = Date.now();
const timeRange = options.timeRange || { start: now - 86400000, end: now }; // Last 24 hours
// Get current and previous metrics
const currentMetrics = await this.getMetrics({ ...options, timeRange });
const previousRange = this.getPreviousTimeRange(timeRange, options.compareWith || 'previous-period');
const previousMetrics = await this.getMetrics({
...options,
timeRange: previousRange,
});
// Calculate trend metrics
const trendMetrics = this.calculateTrendMetrics(currentMetrics, previousMetrics);
// Detect anomalies
const anomalies = this.detectAnomalies(timeRange);
// Generate predictions
const predictions = this.generatePredictions(timeRange);
// Calculate regression
const regression = this.calculateRegression(timeRange);
// Detect seasonality
const seasonality = this.detectSeasonality(timeRange);
const analysis = {
timestamp: now,
timeRange,
metrics: trendMetrics,
anomalies,
predictions,
regression,
seasonality,
};
this.emit('trends-analyzed', analysis);
return analysis;
}
/**
* Calculate trend metrics
*/
calculateTrendMetrics(current, previous) {
const metrics = [];
if (current.performance && previous.performance) {
metrics.push(this.createTrendMetric('Hit Rate', current.performance.hitRate, previous.performance.hitRate), this.createTrendMetric('Latency P95', current.performance.latencyP95, previous.performance.latencyP95), this.createTrendMetric('Throughput', current.performance.throughput, previous.performance.throughput));
}
if (current.health && previous.health) {
metrics.push(this.createTrendMetric('Health Score', current.health.healthScore, previous.health.healthScore), this.createTrendMetric('Error Rate', current.health.errorRate, previous.health.errorRate));
}
return metrics;
}
/**
* Create trend metric
*/
createTrendMetric(name, current, previous) {
const change = current - previous;
const changePercent = previous !== 0 ? (change / previous) * 100 : 0;
const velocity = change / (previous || 1);
let trend;
if (Math.abs(changePercent) < 5) {
trend = 'stable';
}
else if (change > 0) {
trend = 'up';
}
else {
trend = 'down';
}
return {
name,
current,
previous,
change,
changePercent,
trend,
velocity,
};
}
/**
* Detect anomalies
*/
detectAnomalies(timeRange) {
const anomalies = [];
const operations = this.metrics.getOperations(timeRange.start);
// Calculate statistics
const durations = operations.map((op) => op.duration);
const mean = durations.reduce((a, b) => a + b, 0) / (durations.length || 1);
const variance = durations.reduce((sum, d) => sum + Math.pow(d - mean, 2), 0) /
(durations.length || 1);
const stdDev = Math.sqrt(variance);
// Detect duration anomalies
for (const op of operations) {
const zScore = (op.duration - mean) / stdDev;
if (Math.abs(zScore) > 3) {
// 3 sigma rule
anomalies.push({
timestamp: op.timestamp,
metric: 'duration',
value: op.duration,
expected: mean,
deviation: zScore,
severity: Math.abs(zScore) > 4 ? 'high' : 'medium',
confidence: 1 - 1 / Math.abs(zScore),
});
}
}
// Detect hit rate anomalies
const hitRateSeries = Array.from(this.timeSeriesData.get('hitRate') || []).slice(-20);
if (hitRateSeries.length > 5) {
const avgHitRate = hitRateSeries.reduce((sum, p) => sum + p.value, 0) /
hitRateSeries.length;
const currentHitRate = hitRateSeries[hitRateSeries.length - 1].value;
if (Math.abs(currentHitRate - avgHitRate) > 20) {
anomalies.push({
timestamp: Date.now(),
metric: 'hitRate',
value: currentHitRate,
expected: avgHitRate,
deviation: (currentHitRate - avgHitRate) / avgHitRate,
severity: 'medium',
confidence: 0.8,
});
}
}
return anomalies;
}
/**
* Generate predictions
*/
generatePredictions(_timeRange) {
const predictions = [];
const now = Date.now();
const horizon = 3600000; // 1 hour ahead
// Predict hit rate
const hitRateSeries = Array.from(this.timeSeriesData.get('hitRate') || []).slice(-20);
if (hitRateSeries.length > 5) {
const trend = this.calculateSimpleTrend(hitRateSeries.map((p) => p.value));
const lastValue = hitRateSeries[hitRateSeries.length - 1].value;
const predicted = lastValue + trend;
predictions.push({
metric: 'hitRate',
timestamp: now + horizon,
predictedValue: Math.max(0, Math.min(100, predicted)),
confidenceInterval: {
lower: Math.max(0, predicted - 10),
upper: Math.min(100, predicted + 10),
},
confidence: 0.7,
});
}
// Predict throughput
const throughputSeries = Array.from(this.timeSeriesData.get('throughput') || []).slice(-20);
if (throughputSeries.length > 5) {
const trend = this.calculateSimpleTrend(throughputSeries.map((p) => p.value));
const lastValue = throughputSeries[throughputSeries.length - 1].value;
const predicted = Math.max(0, lastValue + trend);
predictions.push({
metric: 'throughput',
timestamp: now + horizon,
predictedValue: predicted,
confidenceInterval: {
lower: Math.max(0, predicted * 0.8),
upper: predicted * 1.2,
},
confidence: 0.65,
});
}
return predictions;
}
/**
* Calculate simple linear trend
*/
calculateSimpleTrend(values) {
if (values.length < 2)
return 0;
const n = values.length;
const xMean = (n - 1) / 2;
const yMean = values.reduce((a, b) => a + b, 0) / n;
let numerator = 0;
let denominator = 0;
for (let i = 0; i < n; i++) {
numerator += (i - xMean) * (values[i] - yMean);
denominator += Math.pow(i - xMean, 2);
}
return denominator !== 0 ? numerator / denominator : 0;
}
/**
* Calculate regression
*/
calculateRegression(_timeRange) {
const hitRateSeries = Array.from(this.timeSeriesData.get('hitRate') || []).slice(-50);
if (hitRateSeries.length < 2) {
return {
slope: 0,
intercept: 0,
rSquared: 0,
equation: 'y = 0',
};
}
const n = hitRateSeries.length;
const xValues = hitRateSeries.map((_, i) => i);
const yValues = hitRateSeries.map((p) => p.value);
const xMean = xValues.reduce((a, b) => a + b, 0) / n;
const yMean = yValues.reduce((a, b) => a + b, 0) / n;
let numerator = 0;
let denominator = 0;
for (let i = 0; i < n; i++) {
numerator += (xValues[i] - xMean) * (yValues[i] - yMean);
denominator += Math.pow(xValues[i] - xMean, 2);
}
const slope = denominator !== 0 ? numerator / denominator : 0;
const intercept = yMean - slope * xMean;
// Calculate R-squared
let ssRes = 0;
let ssTot = 0;
for (let i = 0; i < n; i++) {
const predicted = slope * xValues[i] + intercept;
ssRes += Math.pow(yValues[i] - predicted, 2);
ssTot += Math.pow(yValues[i] - yMean, 2);
}
const rSquared = ssTot !== 0 ? 1 - ssRes / ssTot : 0;
return {
slope,
intercept,
rSquared,
equation: `y = ${slope.toFixed(2)}x + ${intercept.toFixed(2)}`,
};
}
/**
* Detect seasonality
*/
detectSeasonality(_timeRange) {
const series = Array.from(this.timeSeriesData.get('throughput') || []).slice(-100);
if (series.length < 20) {
return {
detected: false,
period: 0,
strength: 0,
peaks: [],
troughs: [],
};
}
// Simple peak detection
const values = series.map((p) => p.value);
const peaks = [];
const troughs = [];
for (let i = 1; i < values.length - 1; i++) {
if (values[i] > values[i - 1] && values[i] > values[i + 1]) {
peaks.push(i);
}
if (values[i] < values[i - 1] && values[i] < values[i + 1]) {
troughs.push(i);
}
}
// Calculate average period between peaks
let avgPeriod = 0;
if (peaks.length > 1) {
const periods = peaks.slice(1).map((p, i) => p - peaks[i]);
avgPeriod = periods.reduce((a, b) => a + b, 0) / periods.length;
}
const detected = peaks.length > 2 && avgPeriod > 0;
const strength = detected ? Math.min(1, peaks.length / 10) : 0;
return {
detected,
period: Math.round(avgPeriod),
strength,
peaks,
troughs,
};
}
// ============================================================================
// Alert Operations
// ============================================================================
/**
* Check alerts and return triggered ones
*/
async checkAlerts(options) {
const alerts = [];
const now = Date.now();
const timeRange = options.timeRange || { start: now - 3600000, end: now };
// Add custom alert config if provided
if (options.alertConfig) {
this.alertConfigs.set(`custom-${now}`, options.alertConfig);
}
// Get current metrics
const currentMetrics = await this.getMetrics({ ...options, timeRange });
// Check each alert configuration
for (const [id, config] of Array.from(this.alertConfigs.entries())) {
if (!config.enabled)
continue;
const value = this.extractMetricValue(config.metric, currentMetrics);
const triggered = this.evaluateAlertCondition(value, config.condition, config.threshold);
if (triggered) {
alerts.push({
id,
type: options.alertType || 'threshold',
metric: config.metric,
severity: config.severity,
message: `${config.metric} ${config.condition} ${config.threshold} (current: ${value.toFixed(2)})`,
timestamp: now,
value,
threshold: config.threshold,
triggered: true,
});
}
}
// Check for anomaly alerts
const anomalies = this.detectAnomalies(timeRange);
for (const anomaly of anomalies) {
if (anomaly.severity === 'high') {
alerts.push({
id: `anomaly-${anomaly.timestamp}`,
type: 'anomaly',
metric: anomaly.metric,
severity: 'warning',
message: `Anomaly detected in ${anomaly.metric}: ${anomaly.value.toFixed(2)} (expected: ${anomaly.expected.toFixed(2)})`,
timestamp: anomaly.timestamp,
value: anomaly.value,
triggered: true,
});
}
}
this.emit('alerts-checked', { count: alerts.length, alerts });
return alerts;
}
/**
* Extract metric value from metrics collection
*/
extractMetricValue(metricName, metrics) {
if (metricName === 'hitRate' && metrics.performance) {
return metrics.performance.hitRate;
}
if (metricName === 'errorRate' && metrics.health) {
return metrics.health.errorRate;
}
if (metricName === 'latencyP95' && metrics.performance) {
return metrics.performance.latencyP95;
}
if (metricName === 'memoryUtilization' && metrics.efficiency) {
return metrics.efficiency.memoryUtilization;
}
return 0;
}
/**
* Evaluate alert condition
*/
evaluateAlertCondition(value, condition, threshold) {
switch (condition) {
case 'gt':
return value > threshold;
case 'lt':
return value < threshold;
case 'eq':
return Math.abs(value - threshold) < 0.01;
case 'ne':
return Math.abs(value - threshold) >= 0.01;
default:
return false;
}
}
// ============================================================================
// Heatmap Generation
// ============================================================================
/**
* Generate access heatmap
*/
async generateHeatmap(options) {
const heatmapType = options.heatmapType || 'temporal';
const resolution = options.resolution || 'medium';
const now = Date.now();
const timeRange = options.timeRange || { start: now - 86400000, end: now };
let heatmap;
switch (heatmapType) {
case 'temporal':
heatmap = this.generateTemporalHeatmap(timeRange, resolution);
break;
case 'key-correlation':
heatmap = this.generateKeyCorrelationHeatmap(timeRange, resolution);
break;
case 'memory':
heatmap = this.generateMemoryHeatmap(timeRange, resolution);
break;
default:
throw new Error(`Unknown heatmap type: ${heatmapType}`);
}
this.emit('heatmap-generated', heatmap);
return heatmap;
}
/**
* Generate temporal heatmap (hour x day of week)
*/
generateTemporalHeatmap(timeRange, _resolution) {
const operations = this.metrics.getOperations(timeRange.start);
// Create 24x7 matrix (hour x day of week)
const data = Array(24)
.fill(0)
.map(() => Array(7).fill(0));
// Count operations per hour per day
for (const op of operations) {
const date = new Date(op.timestamp);
const hour = date.getHours();
const dayOfWeek = date.getDay();
data[hour][dayOfWeek]++;
}
// Find hotspots
const hotspots = [];
let maxIntensity = 0;
let totalIntensity = 0;
let cellCount = 0;
for (let h = 0; h < 24; h++) {
for (let d = 0; d < 7; d++) {
const value = data[h][d];
totalIntensity += value;
cellCount++;
if (value > maxIntensity) {
maxIntensity = value;
}
if (value > 0) {
hotspots.push({ x: d, y: h, value });
}
}
}
hotspots.sort((a, b) => b.value - a.value);
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const hours = Array.from({ length: 24 }, (_, i) => `${i}:00`);
return {
type: 'temporal',
dimensions: { width: 7, height: 24 },
data,
labels: { x: days, y: hours },
colorScale: { min: 0, max: maxIntensity },
summary: {
hotspots: hotspots.slice(0, 5),
avgIntensity: totalIntensity / cellCount,
maxIntensity,
},
};
}
/**
* Generate key correlation heatmap
*/
generateKeyCorrelationHeatmap(timeRange, _resolution) {
const operations = this.metrics.getOperations(timeRange.start);
// Get top keys
const keyFrequency = new Map();
for (const op of operations) {
const key = this.extractKeyFromMetadata(op.metadata);
if (key) {
keyFrequency.set(key, (keyFrequency.get(key) || 0) + 1);
}
}
const topKeys = Array.from(keyFrequency.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([key]) => key);
// Calculate correlation matrix
const size = topKeys.length;
const data = Array(size)
.fill(0)
.map(() => Array(size).fill(0));
// Calculate co-occurrence
const windowSize = 1000; // 1 second
for (let i = 0; i < operations.length - 1; i++) {
const key1 = this.extractKeyFromMetadata(operations[i].metadata);
if (!key1 || !topKeys.includes(key1))
continue;
for (let j = i + 1; j < operations.length; j++) {
if (operations[j].timestamp - operations[i].timestamp > windowSize)
break;
const key2 = this.extractKeyFromMetadata(operations[j].metadata);
if (!key2 || !topKeys.includes(key2))
continue;
const idx1 = topKeys.indexOf(key1);
const idx2 = topKeys.indexOf(key2);
data[idx1][idx2]++;
data[idx2][idx1]++;
}
}
// Normalize
let maxValue = 0;
for (const row of data) {
for (const val of row) {
if (val > maxValue)
maxValue = val;
}
}
if (maxValue > 0) {
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
data[i][j] = data[i][j] / maxValue;
}
}
}
// Find hotspots
const hotspots = [];
let totalIntensity = 0;
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
if (i !== j && data[i][j] > 0.3) {
hotspots.push({ x: j, y: i, value: data[i][j] });
}
totalIntensity += data[i][j];
}
}
hotspots.sort((a, b) => b.value - a.value);
return {
type: 'key-correlation',
dimensions: { width: size, height: size },
data,
labels: { x: topKeys, y: topKeys },
colorScale: { min: 0, max: 1 },
summary: {
hotspots: hotspots.slice(0, 5),
avgIntensity: totalIntensity / (size * size),
maxIntensity: 1,
},
};
}
/**
* Generate memory usage heatmap
*/
generateMemoryHeatmap(_timeRange, _resolution) {
const cacheStats = this.cache.getStats();
// Create simple memory layout visualization (10x10 grid)
const size = 10;
const data = Array(size)
.fill(0)
.map(() => Array(size).fill(0));
// Simulate memory distribution
const usedCells = Math.floor((cacheStats.totalCompressedSize / (500 * 1024 * 1024)) * 100);
for (let i = 0; i < usedCells && i < 100; i++) {
const x = i % size;
const y = Math.floor(i / size);
data[y][x] = 0.5 + Math.random() * 0.5;
}
// Find hotspots
const hotspots = [];
let totalIntensity = 0;
let maxIntensity = 0;
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
const value = data[i][j];
totalIntensity += value;
if (value > maxIntensity)
maxIntensity = value;
if (value > 0.7) {
hotspots.push({ x: j, y: i, value });
}
}
}
hotspots.sort((a, b) => b.value - a.value);
return {
type: 'memory',
dimensions: { width: size, height: size },
data,
labels: {
x: Array.from({ length: size }, (_, i) => `Block ${i}`),
y: Array.from({ length: size }, (_, i) => `Tier ${i}`),
},
colorScale: { min: 0, max: 1 },
summary: {
hotspots: hotspots.slice(0, 5),
avgIntensity: totalIntensity / (size * size),
maxIntensity,
},
};
}
// ============================================================================
// Bottleneck Identification
// ============================================================================
/**
* Identify performance bottlenecks
*/
async identifyBottlenecks(options) {
const bottlenecks = [];
const now = Date.now();
const timeRange = options.timeRange || { start: now - 3600000, end: now };
const operations = this.metrics.getOperations(timeRange.start);
const percentiles = this.metrics.getPerformancePercentiles(timeRange.start);
// Check for slow operations
const slowOps = operations.filter((op) => op.duration > percentiles.p95);
if (slowOps.length > operations.length * 0.05) {
bottlenecks.push({
type: 'slow-operation',
severity: 'high',
description: `${slowOps.length} operations slower than P95 (${percentiles.p95}ms)`,
impact: (slowOps.length / operations.length) * 100,
recommendation: 'Consider optimizing slow operations or increasing cache size',
metrics: {
current: slowOps.length,
threshold: operations.length * 0.05,
duration: percentiles.p95,
},
});
}
// Check for hot keys
const keyFrequency = new Map();
for (const op of operations) {
const key = this.extractKeyFromMetadata(op.metadata);
if (key) {
keyFrequency.set(key, (keyFrequency.get(key) || 0) + 1);
}
}
const hotKeys = Array.from(keyFrequency.entries())
.filter(([_, count]) => count > operations.length * 0.1)
.map(([key]) => key);
if (hotKeys.length > 0) {
bottlenecks.push({
type: 'hot-key',
severity: 'medium',
description: `${hotKeys.length} keys accessed more than 10% of the time`,
impact: 50,
recommendation: 'Consider implementing read-through caching or sharding for hot keys',
affectedKeys: hotKeys,
metrics: {
current: hotKeys.length,
threshold: 3,
duration: 0,
},
});
}
// Check for memory pressure
const cacheStats = this.cache.getStats();
const memoryUtilization = (cacheStats.totalCompressedSize / (500 * 1024 * 1024)) * 100;
if (memoryUtilization > 80) {
bottlenecks.push({
type: 'memory-pressure',
severity: 'high',
description: `Memory utilization at ${memoryUtilization.toFixed(1)}%`,
impact: memoryUtilization,
recommendation: 'Increase cache size or implement more aggressive eviction policies',
metrics: {
current: memoryUtilization,
threshold: 80,
duration: 0,
},
});
}
// Check for high eviction rate
const evictionOps = operations.filter((op) => op.operation.includes('evict')).length;
const evictionRate = (evictionOps / operations.length) * 100;
if (evictionRate > 20) {
bottlenecks.push({
type: 'high-eviction',
severity: 'medium',
description: `High eviction rate: ${evictionRate.toFixed(1)}%`,
impact: evictionRate,
recommendation: 'Consider increasing TTL or cache size to reduce evictions',
metrics: {
current: evictionRate,
threshold: 20,
duration: 0,
},
});
}
this.emit('bottlenecks-identified', { count: bottlenecks.length });
return bottlenecks;
}
// ============================================================================
// Cost Analysis
// ============================================================================
/**
* Analyze caching costs
*/
async analyzeCosts(options) {
const now = Date.now();
const timeRange = options.timeRange || { start: now - 86400000, end: now };
const cacheStats = this.cache.getStats();
const operations = this.metrics.getOperations(timeRange.start);
// Storage costs
const memoryGB = cacheStats.totalCompressedSize / (1024 * 1024 * 1024);
const hours = (timeRange.end - timeRange.start) / 3600000;
const storage = {
memoryCost: memoryGB * 0.1 * hours,
diskCost: memoryGB * 0.02 * hours,
totalStorage: cacheStats.totalCompressedSize,
utilizationPercent: (cacheStats.totalCompressedSize / (500 * 1024 * 1024)) * 100,
};
// Network costs
const totalTraffic = operations.length * 1024; // Estimate 1KB per operation
const network = {
ingressCost: totalTraffic * 0.00005,
egressCost: totalTraffic * 0.00009,
totalTraffic,
bandwidthUtilization: 0.5,
};
// Compute costs
const compute = {
cpuCost: operations.length * 0.000001,
operationCost: operations.length * 0.000001,
totalOperations: operations.length,
efficiency: 0.85,
};
// Total costs
const currentCost = storage.memoryCost +
storage.diskCost +
network.ingressCost +
network.egressCost +
compute.cpuCost;
const projectedCost = currentCost * 1.1; // 10% growth
const costTrend = this.calculateCostTrend(currentCost);
const total = {
current: currentCost,
projected: projectedCost,
trend: costTrend,
costPerGB: currentCost / (memoryGB || 1),
costPerOperation: currentCost / (operations.length || 1),
};
// Projections
const projections = [
{ period: '1 week', estimatedCost: currentCost * 7, confidence: 0.9 },
{ period: '1 month', estimatedCost: currentCost * 30, confidence: 0.7 },
{ period: '3 months', estimatedCost: currentCost * 90, confidence: 0.5 },
];
// Optimizations
const optimizations = [];
if (storage.utilizationPercent < 50) {
optimizations.push({
category: 'Storage',
potentialSavings: storage.memoryCost * 0.3,
effort: 'low',
recommendation: 'Reduce cache size to match actual usage',
});
}
if (compute.efficiency < 0.8) {
optimizations.push({
category: 'Compute',
potentialSavings: compute.cpuCost * 0.2,
effort: 'medium',
recommendation: 'Optimize cache operations to reduce CPU usage',
});
}
const costAnalysis = {
timestamp: now,
timeRange,
storage,
network,
compute,
total,
projections,
optimizations,
};
this.emit('costs-analyzed', costAnalysis);
return costAnalysis;
}
// ============================================================================
// Data Export
// ============================================================================
/**
* Export analytics data
*/
async exportData(options) {
const format = options.format || 'json';
const now = Date.now();
const timeRange = options.timeRange || { start: now - 86400000, end: now };
// Gather all data
const dashboard = await this.getDashboard({ ...options, timeRange });
const metrics = await this.getMetrics({ ...options, timeRange });
const trends = await this.analyzeTrends({ ...options, timeRange });
const alerts = await this.checkAlerts({ ...options, timeRange });
const bottlenecks = await this.identifyBottlenecks({
...options,
timeRange,
});
const costs = await this.analyzeCosts({ ...options, timeRange });
const exportData = {
exportTimestamp: now,
timeRange,
dashboard,
metrics,
trends,
alerts,
bottlenecks,
costs,
};
let output;
switch (format) {
case 'json':
output = JSON.stringify(exportData, null, 2);
break;
case 'csv':
output = this.convertToCSV(exportData);
break;
case 'prometheus':
output = this.convertToPrometheus(exportData);
break;
default:
throw new Error(`Unknown export format: ${format}`);
}
// Write to file if path provided
if (options.filePath) {
writeFileSync(options.filePath, output, 'utf-8');
this.emit('data-exported', {
format,
path: options.filePath,
size: output.length,
});
}
return output;
}
/**
* Convert to CSV format
*/
convertToCSV(data) {
const lines = [];
// Header
lines.push('Metric,Value,Timestamp');
// Dashboard data
if (data.dashboard) {
const d = data.dashboard;
lines.push(`Hit Rate,${d.performance.hitRate},${d.timestamp}`);
lines.push(`Latency P95,${d.performance.latencyP95},${d.timestamp}`);
lines.push(`Throughput,${d.performance.throughput},${d.timestamp}`);
lines.push(`Total Keys,${d.usage.totalKeys},${d.timestamp}`);
lines.push(`Total Size,${d.usage.totalSize},${d.timestamp}`);
lines.push(`Health Score,${d.health.healthScore},${d.timestamp}`);
}
return lines.join('\n');
}
/**
* Convert to Prometheus format
*/
convertToPrometheus(data) {
const lines = [];
const timestamp = Date.now();
if (data.dashboard) {
const d = data.dashboard;
lines.push(`# HELP cache_hit_rate Cache hit rate percentage`, `# TYPE cache_hit_rate gauge`, `cache_hit_rate ${d.performance.hitRate} ${timestamp}`, ``, `# HELP cache_latency_p95 95th percentile latency in milliseconds`, `# TYPE cache_latency_p95 gauge`, `cache_latency_p95 ${d.performance.latencyP95} ${timestamp}`, ``, `# HELP cache_throughput Operations per second`, `# TYPE cache_throughput gauge`, `cache_throughput ${d.performance.throughput} ${timestamp}`, ``, `# HELP cache_health_score Over