UNPKG

azureai-optimizer

Version:

AI-Powered Azure Infrastructure Optimization via Model Context Protocol

335 lines 14.3 kB
/** * Right-Sizing Tool * AI-powered Azure resource optimization recommendations */ // import { ComputeManagementClient } from '@azure/arm-compute'; // const ComputeManagementClient = {} as any; // Stub for now // import { MonitorClient } from '@azure/arm-monitor'; import { Logger } from '../utils/logger.js'; import { MCPError, ErrorCode } from '../utils/errors.js'; export class RightSizingTool { name = 'right_size_resources'; description = 'AI-powered resource optimization and right-sizing recommendations'; inputSchema = { type: 'object', properties: { subscription_id: { type: 'string', description: 'Azure subscription ID to analyze' }, resource_group: { type: 'string', description: 'Optional: Specific resource group to analyze' }, resource_types: { type: 'array', items: { type: 'string', enum: ['VirtualMachines', 'SqlDatabases', 'AppServicePlans', 'Storage'] }, description: 'Resource types to analyze (default: all supported types)' }, utilization_threshold: { type: 'number', description: 'CPU utilization threshold for optimization (default: 70%)', default: 70, minimum: 10, maximum: 95 }, analysis_days: { type: 'number', description: 'Number of days to analyze metrics (default: 30)', default: 30, minimum: 7, maximum: 90 } }, required: ['subscription_id'] }; logger; constructor(_config) { this.logger = new Logger('RightSizingTool'); } async execute(args, _context) { const startTime = Date.now(); let apiCalls = 0; try { this.logger.info(`📏 Starting right-sizing analysis for subscription: ${args.subscription_id}`); // Initialize Azure clients // this.computeClient = new ComputeManagementClient(context.credential, args.subscription_id); // this._monitorClient = new MonitorClient(context.credential, args.subscription_id); // Set defaults const resourceTypes = args.resource_types || ['VirtualMachines', 'SqlDatabases', 'AppServicePlans', 'Storage']; const utilizationThreshold = args.utilization_threshold || 70; const analysisDays = args.analysis_days || 30; let vmRecommendations = []; let databaseRecommendations = []; let storageRecommendations = []; // Analyze Virtual Machines if (resourceTypes.includes('VirtualMachines')) { this.logger.info('🖥️ Analyzing Virtual Machines...'); vmRecommendations = await this.analyzeVirtualMachines(args, utilizationThreshold, analysisDays); apiCalls += 2; // VM list + metrics } // Analyze SQL Databases if (resourceTypes.includes('SqlDatabases')) { this.logger.info('🗄️ Analyzing SQL Databases...'); databaseRecommendations = await this.analyzeSqlDatabases(args, analysisDays); apiCalls += 2; // Database list + metrics } // Analyze Storage Accounts if (resourceTypes.includes('Storage')) { this.logger.info('💾 Analyzing Storage Accounts...'); storageRecommendations = await this.analyzeStorageAccounts(args, analysisDays); apiCalls += 2; // Storage list + metrics } // Calculate summary const summary = this.calculateSummary(vmRecommendations, databaseRecommendations, storageRecommendations); const result = { success: true, data: { summary, vm_recommendations: vmRecommendations, database_recommendations: databaseRecommendations, storage_recommendations: storageRecommendations }, metadata: { execution_time: Date.now() - startTime, api_calls: apiCalls, analysis_period: `${analysisDays} days`, confidence_threshold: utilizationThreshold } }; this.logger.info(`✅ Right-sizing analysis completed`); this.logger.info(`💰 Potential monthly savings: $${summary.potential_monthly_savings.toFixed(2)}`); this.logger.info(`📊 Found ${summary.optimization_opportunities} optimization opportunities`); return result; } catch (error) { this.logger.error('❌ Right-sizing analysis failed:', error); if (error instanceof MCPError) { throw error; } throw new MCPError(ErrorCode.INTERNAL_ERROR, `Right-sizing analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async analyzeVirtualMachines(args, threshold, days) { try { // Get VMs // const vms = await this.computeClient.virtualMachines.listAll(); const vms = []; const recommendations = []; for (const vm of vms) { if (args.resource_group && !vm.id?.includes(`/resourceGroups/${args.resource_group}/`)) { continue; } // Get VM metrics const metrics = await this.getVMMetrics(vm.id, days); // Analyze utilization const recommendation = this.analyzeVMUtilization(vm, metrics, threshold); if (recommendation) { recommendations.push(recommendation); } } return recommendations; } catch (error) { this.logger.warn('⚠️ Failed to analyze VMs:', error); return []; } } async getVMMetrics(resourceId, days) { try { const endTime = new Date(); const startTime = new Date(); startTime.setDate(endTime.getDate() - days); // Stub implementation - actual implementation would use proper Azure Monitor API const metricsResponse = { value: [], timespan: `${startTime.toISOString()}/${endTime.toISOString()}`, interval: 'PT1H' }; return this.processMetrics(metricsResponse); } catch (error) { this.logger.warn(`⚠️ Failed to get metrics for ${resourceId}:`, error); return { cpu_average: 50, memory_average: 50, network_average: 50 }; // Default values } } processMetrics(metricsResponse) { const metrics = { cpu_average: 0, memory_average: 0, network_average: 0 }; if (metricsResponse.value) { metricsResponse.value.forEach((metric) => { const metricName = metric.name?.value?.toLowerCase(); const timeseries = metric.timeseries?.[0]; if (timeseries?.data) { const values = timeseries.data .filter((point) => point.average !== undefined) .map((point) => point.average); if (values.length > 0) { const average = values.reduce((sum, val) => sum + val, 0) / values.length; if (metricName?.includes('cpu')) { metrics.cpu_average = average; } else if (metricName?.includes('memory')) { metrics.memory_average = average; } else if (metricName?.includes('network')) { metrics.network_average = average; } } } }); } return metrics; } analyzeVMUtilization(vm, metrics, threshold) { const currentSku = vm.hardwareProfile?.vmSize || 'Unknown'; const cpuUtilization = metrics.cpu_average; // Only recommend if CPU utilization is below threshold if (cpuUtilization >= threshold) { return null; } // Get recommended SKU based on utilization const recommendedSku = this.getRecommendedVMSku(currentSku, cpuUtilization); if (!recommendedSku || recommendedSku === currentSku) { return null; } // Calculate cost savings (simplified) const currentCost = this.getVMCost(currentSku); const projectedCost = this.getVMCost(recommendedSku); const monthlySavings = currentCost - projectedCost; if (monthlySavings <= 0) { return null; } return { resource_id: vm.id, resource_name: vm.name, current_sku: currentSku, recommended_sku: recommendedSku, current_cost: currentCost, projected_cost: projectedCost, monthly_savings: monthlySavings, savings_percentage: (monthlySavings / currentCost) * 100, utilization_metrics: metrics, confidence_level: this.getConfidenceLevel(cpuUtilization, threshold), implementation_complexity: this.getImplementationComplexity(currentSku, recommendedSku), risk_assessment: this.getRiskAssessment(cpuUtilization, metrics) }; } getRecommendedVMSku(currentSku, cpuUtilization) { // Simplified SKU recommendation logic const skuMap = { 'Standard_D4s_v3': ['Standard_D2s_v3', 'Standard_D1s_v3'], 'Standard_D8s_v3': ['Standard_D4s_v3', 'Standard_D2s_v3'], 'Standard_D16s_v3': ['Standard_D8s_v3', 'Standard_D4s_v3'], 'Standard_F4s_v2': ['Standard_F2s_v2'], 'Standard_F8s_v2': ['Standard_F4s_v2', 'Standard_F2s_v2'] }; const alternatives = skuMap[currentSku]; if (!alternatives) { return currentSku; } // Choose based on utilization if (cpuUtilization < 20) { return alternatives[alternatives.length - 1]; // Smallest alternative } else if (cpuUtilization < 40) { return alternatives[Math.floor(alternatives.length / 2)]; // Middle alternative } else { return alternatives[0]; // One step down } } getVMCost(sku) { // Simplified cost mapping (in production, use Azure Pricing API) const costMap = { 'Standard_D1s_v3': 50, 'Standard_D2s_v3': 100, 'Standard_D4s_v3': 200, 'Standard_D8s_v3': 400, 'Standard_D16s_v3': 800, 'Standard_F2s_v2': 80, 'Standard_F4s_v2': 160, 'Standard_F8s_v2': 320 }; return costMap[sku] || 100; // Default cost } getConfidenceLevel(cpuUtilization, threshold) { const utilizationGap = threshold - cpuUtilization; if (utilizationGap > 40) return 'High'; if (utilizationGap > 20) return 'Medium'; return 'Low'; } getImplementationComplexity(currentSku, recommendedSku) { // Simplified complexity assessment const currentTier = this.getSkuTier(currentSku); const recommendedTier = this.getSkuTier(recommendedSku); if (currentTier === recommendedTier) return 'Low'; if (Math.abs(currentTier - recommendedTier) === 1) return 'Medium'; return 'High'; } getSkuTier(sku) { // Extract tier from SKU name (simplified) if (sku.includes('D1') || sku.includes('F2')) return 1; if (sku.includes('D2') || sku.includes('F4')) return 2; if (sku.includes('D4') || sku.includes('F8')) return 3; if (sku.includes('D8')) return 4; if (sku.includes('D16')) return 5; return 2; // Default } getRiskAssessment(cpuUtilization, _metrics) { if (cpuUtilization < 10) { return 'Low risk - Resource is significantly underutilized'; } else if (cpuUtilization < 30) { return 'Medium risk - Monitor performance after downsizing'; } else { return 'Higher risk - Careful monitoring required during peak loads'; } } async analyzeSqlDatabases(_args, _days) { // Simplified implementation - in production, would use SQL Database APIs this.logger.info('🗄️ SQL Database analysis not yet implemented'); return []; } async analyzeStorageAccounts(_args, _days) { // Simplified implementation - in production, would use Storage APIs this.logger.info('💾 Storage analysis not yet implemented'); return []; } calculateSummary(vmRecs, dbRecs, storageRecs) { const totalResources = vmRecs.length + dbRecs.length + storageRecs.length; const totalSavings = [ ...vmRecs.map(r => r.monthly_savings), ...dbRecs.map(r => r.monthly_savings || 0), ...storageRecs.map(r => r.monthly_savings || 0) ].reduce((sum, savings) => sum + savings, 0); const totalCurrentCost = [ ...vmRecs.map(r => r.current_cost), ...dbRecs.map(r => r.current_cost || 0), ...storageRecs.map(r => r.current_cost || 0) ].reduce((sum, cost) => sum + cost, 0); return { total_resources_analyzed: totalResources, optimization_opportunities: vmRecs.length + dbRecs.length + storageRecs.length, potential_monthly_savings: totalSavings, total_savings_percentage: totalCurrentCost > 0 ? (totalSavings / totalCurrentCost) * 100 : 0 }; } } //# sourceMappingURL=right-sizing.js.map