@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
469 lines (420 loc) • 14.5 kB
text/typescript
import { Application, Request, Response } from 'express';
import { PerformanceMonitor } from '../../utils/performance-monitor.js';
export function setupMetricsAPI(
app: Application,
performanceMonitor: PerformanceMonitor,
exportEnabled: boolean = true
): void {
// Metrics API endpoints setup
// System overview metrics
app.get('/api/metrics/overview', async (req: Request, res: Response) => {
try {
const metrics = await performanceMonitor.getSystemMetrics();
res.json({
success: true,
data: metrics,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error fetching system metrics:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch system metrics',
message: (error as Error).message
});
}
});
// Tool-specific metrics
app.get('/api/metrics/tools', async (req: Request, res: Response) => {
try {
const { toolName, timeRange = '24h' } = req.query;
if (toolName && typeof toolName === 'string') {
// Get metrics for specific tool
const metrics = await performanceMonitor.getToolMetrics(toolName);
res.json({
success: true,
data: {
toolName,
metrics,
timeRange
},
timestamp: new Date().toISOString()
});
} else {
// Get metrics for all tools
const systemMetrics = performanceMonitor.getSystemMetrics();
// Build tool metrics from the available data
const toolMetrics: Record<string, any> = {};
// Extract data from top performing tools
systemMetrics.topPerformingTools.forEach(tool => {
toolMetrics[tool.tool] = {
calls: tool.calls,
successRate: tool.successRate
};
});
// Add data from slowest tools
systemMetrics.slowestTools.forEach(tool => {
if (!toolMetrics[tool.tool]) {
toolMetrics[tool.tool] = {};
}
toolMetrics[tool.tool].avgTime = tool.avgTime;
toolMetrics[tool.tool].calls = tool.calls;
});
// Add data from error-prone tools
systemMetrics.mostErrorProneTools.forEach(tool => {
if (!toolMetrics[tool.tool]) {
toolMetrics[tool.tool] = {};
}
toolMetrics[tool.tool].errorRate = tool.errorRate;
toolMetrics[tool.tool].calls = tool.calls;
});
res.json({
success: true,
data: {
tools: toolMetrics,
systemMetrics,
timeRange
},
timestamp: new Date().toISOString()
});
}
} catch (error) {
console.error('📊 Error fetching tool metrics:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch tool metrics',
message: (error as Error).message
});
}
});
// Performance trends over time
app.get('/api/metrics/trends', async (req: Request, res: Response) => {
try {
const { timeRange = '7d', metric = 'performance' } = req.query;
// This would need to be implemented in PerformanceMonitor
// For now, return sample trend data
const trends = {
timeRange,
metric,
dataPoints: [
{ timestamp: new Date(Date.now() - 6 * 24 * 60 * 60 * 1000).toISOString(), value: 95.2 },
{ timestamp: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), value: 97.1 },
{ timestamp: new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString(), value: 94.8 },
{ timestamp: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), value: 96.5 },
{ timestamp: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), value: 98.2 },
{ timestamp: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), value: 97.9 },
{ timestamp: new Date().toISOString(), value: 96.8 }
]
};
res.json({
success: true,
data: trends,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error fetching performance trends:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch performance trends',
message: (error as Error).message
});
}
});
// Quality metrics
app.get('/api/metrics/quality', async (req: Request, res: Response) => {
try {
const { toolName, timeRange = '7d' } = req.query;
const qualityMetrics = await performanceMonitor.getQualityMetrics(
typeof toolName === 'string' ? toolName : undefined
);
res.json({
success: true,
data: {
toolName: toolName || 'all',
timeRange,
qualityMetrics
},
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error fetching quality metrics:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch quality metrics',
message: (error as Error).message
});
}
});
// Performance alerts
app.get('/api/metrics/alerts', async (req: Request, res: Response) => {
try {
const alerts = await performanceMonitor.generateAlerts();
res.json({
success: true,
data: {
alerts,
count: alerts.length
},
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error fetching performance alerts:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch performance alerts',
message: (error as Error).message
});
}
});
// Record quality evaluation
app.post('/api/metrics/quality/evaluate', async (req: Request, res: Response) => {
try {
const { toolName, quality, feedback, executionId } = req.body;
if (!toolName || quality === undefined) {
return res.status(400).json({
success: false,
error: 'Missing required fields: toolName and quality are required'
});
}
if (quality < 1 || quality > 10) {
return res.status(400).json({
success: false,
error: 'Quality rating must be between 1 and 10'
});
}
performanceMonitor.recordQualityEvaluation({
toolName,
outputQuality: quality,
appropriatenessScore: quality,
completenessScore: quality,
accuracyScore: quality,
comments: feedback,
evaluationCriteria: ['user_feedback']
});
res.json({
success: true,
message: 'Quality evaluation recorded successfully',
data: {
toolName,
quality,
feedback
},
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error recording quality evaluation:', error);
res.status(500).json({
success: false,
error: 'Failed to record quality evaluation',
message: (error as Error).message
});
}
});
// Export metrics data
if (exportEnabled) {
app.get('/api/metrics/export', async (req: Request, res: Response) => {
try {
const { format = 'csv', timeRange = '24h', includeQuality = 'true' } = req.query;
const exportData = await performanceMonitor.exportMetrics(format as 'json' | 'csv');
const exportPath = `.atlas/exports/metrics-${new Date().toISOString().split('T')[0]}.${format}`;
// Save to file
const fs = await import('fs/promises');
const path = await import('path');
await fs.mkdir(path.dirname(exportPath), { recursive: true });
await fs.writeFile(exportPath, typeof exportData === 'string' ? exportData : JSON.stringify(exportData));
res.json({
success: true,
data: {
exportPath,
format,
timeRange,
includeQuality: includeQuality === 'true'
},
message: 'Metrics exported successfully',
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error exporting metrics:', error);
res.status(500).json({
success: false,
error: 'Failed to export metrics',
message: (error as Error).message
});
}
});
app.get('/api/metrics/download/:filename', async (req: Request, res: Response) => {
try {
const { filename } = req.params;
// Basic security check
if (!filename.match(/^[a-zA-Z0-9_-]+\.(csv|json)$/)) {
return res.status(400).json({
success: false,
error: 'Invalid filename format'
});
}
// This would need to serve files from the export directory
// For now, return a placeholder response
res.json({
success: false,
error: 'File download not implemented yet',
message: 'Use the export endpoint to generate files'
});
} catch (error) {
console.error('📊 Error downloading metrics file:', error);
res.status(500).json({
success: false,
error: 'Failed to download metrics file',
message: (error as Error).message
});
}
});
}
// Record tool execution
app.post('/api/metrics/record', async (req: Request, res: Response) => {
try {
const { tool, success, duration, metadata } = req.body;
if (!tool || success === undefined) {
return res.status(400).json({
success: false,
error: 'Tool name and success status are required'
});
}
performanceMonitor.recordToolExecution(tool, duration || 0, success);
res.json({
success: true,
message: 'Tool execution recorded',
data: { tool, success, duration },
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error recording tool execution:', error);
res.status(500).json({
success: false,
error: 'Failed to record tool execution',
message: (error as Error).message
});
}
});
// Get module-specific metrics
app.get('/api/metrics/modules', async (req: Request, res: Response) => {
try {
const { moduleName } = req.query;
const moduleMetrics = await performanceMonitor.getModuleMetrics(
typeof moduleName === 'string' ? moduleName : undefined
);
res.json({
success: true,
data: moduleMetrics,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error fetching module metrics:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch module metrics',
message: (error as Error).message
});
}
});
// Analyze performance bottlenecks
app.get('/api/metrics/bottlenecks', async (req: Request, res: Response) => {
try {
const options = {
threshold: Number(req.query.threshold) || 500
};
const bottlenecks = await performanceMonitor.analyzeBottlenecks(options);
res.json({
success: true,
data: bottlenecks,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error analyzing bottlenecks:', error);
res.status(500).json({
success: false,
error: 'Failed to analyze bottlenecks',
message: (error as Error).message
});
}
});
// Generate performance report
app.get('/api/metrics/report', async (req: Request, res: Response) => {
try {
const options = {
format: req.query.format as string || 'json',
includeRecommendations: req.query.includeRecommendations === 'true'
};
const report = await performanceMonitor.generatePerformanceReport(options);
res.json({
success: true,
data: report,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error generating performance report:', error);
res.status(500).json({
success: false,
error: 'Failed to generate performance report',
message: (error as Error).message
});
}
});
// Clear metrics
app.delete('/api/metrics/clear', async (req: Request, res: Response) => {
try {
const { confirm, before } = req.query;
if (confirm !== 'yes') {
return res.status(400).json({
success: false,
error: 'Confirmation required to clear metrics'
});
}
const options = before ? { before: before as string } : undefined;
await performanceMonitor.clearMetrics(options);
res.json({
success: true,
message: 'Metrics cleared successfully',
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Error clearing metrics:', error);
res.status(500).json({
success: false,
error: 'Failed to clear metrics',
message: (error as Error).message
});
}
});
// Health check for metrics service
app.get('/api/metrics/health', async (req: Request, res: Response) => {
try {
// Test if performance monitor is working
const testMetrics = await performanceMonitor.getSystemMetrics();
res.json({
success: true,
status: 'healthy',
data: {
metricsCollected: testMetrics ? true : false,
exportEnabled,
features: {
systemMetrics: true,
toolMetrics: true,
qualityMetrics: true,
alerts: true,
export: exportEnabled
}
},
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('📊 Metrics health check failed:', error);
res.status(503).json({
success: false,
status: 'unhealthy',
error: 'Metrics service is not functioning properly',
message: (error as Error).message,
timestamp: new Date().toISOString()
});
}
});
}