azureai-optimizer
Version:
AI-Powered Azure Infrastructure Optimization via Model Context Protocol
335 lines • 14.3 kB
JavaScript
/**
* 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