Move Historical Analysis button to individual pod cards with pod-specific Prometheus queries

This commit is contained in:
2025-09-26 10:01:51 -03:00
parent 6c01527b5e
commit 0132a90387
3 changed files with 269 additions and 2 deletions

View File

@@ -449,6 +449,34 @@ async def get_namespace_historical_analysis(
logger.error(f"Error getting historical analysis for namespace {namespace}: {e}")
raise HTTPException(status_code=500, detail=str(e))
@api_router.get("/namespace/{namespace}/pod/{pod_name}/historical-analysis")
async def get_pod_historical_analysis(
namespace: str,
pod_name: str,
time_range: str = "24h",
prometheus_client=Depends(get_prometheus_client)
):
"""Get historical analysis for a specific pod"""
try:
historical_service = HistoricalAnalysisService()
# Get historical analysis for the pod
analysis = await historical_service.get_pod_historical_analysis(
namespace, pod_name, time_range, prometheus_client
)
return {
"namespace": namespace,
"pod_name": pod_name,
"time_range": time_range,
"analysis": analysis,
"timestamp": datetime.now().isoformat()
}
except Exception as e:
logger.error(f"Error getting historical analysis for pod {pod_name} in namespace {namespace}: {e}")
raise HTTPException(status_code=500, detail=str(e))
@api_router.get("/health")
async def health_check():
"""API health check"""

View File

@@ -569,3 +569,140 @@ class HistoricalAnalysisService:
'error': str(e),
'recommendations': []
}
async def get_pod_historical_analysis(self, namespace: str, pod_name: str, time_range: str, prometheus_client):
"""Get historical analysis for a specific pod"""
try:
logger.info(f"Getting historical analysis for pod: {pod_name} in namespace: {namespace}")
# Query for CPU usage by pod
cpu_query = f'''
sum(rate(container_cpu_usage_seconds_total{{
namespace="{namespace}",
pod="{pod_name}",
container!="POD",
container!=""
}}[{time_range}]))
'''
# Query for memory usage by pod
memory_query = f'''
sum(container_memory_working_set_bytes{{
namespace="{namespace}",
pod="{pod_name}",
container!="POD",
container!=""
}})
'''
# Query for CPU requests by pod
cpu_requests_query = f'''
sum(kube_pod_container_resource_requests{{
namespace="{namespace}",
pod="{pod_name}",
resource="cpu"
}})
'''
# Query for memory requests by pod
memory_requests_query = f'''
sum(kube_pod_container_resource_requests{{
namespace="{namespace}",
pod="{pod_name}",
resource="memory"
}})
'''
# Query for container count by pod
container_count_query = f'''
count(container_memory_working_set_bytes{{
namespace="{namespace}",
pod="{pod_name}",
container!="POD",
container!=""
}})
'''
# Execute queries
cpu_usage = await self._query_prometheus(cpu_query,
datetime.now() - timedelta(seconds=self.time_ranges[time_range]),
datetime.now(), prometheus_client)
memory_usage = await self._query_prometheus(memory_query,
datetime.now() - timedelta(seconds=self.time_ranges[time_range]),
datetime.now(), prometheus_client)
cpu_requests = await self._query_prometheus(cpu_requests_query,
datetime.now() - timedelta(seconds=self.time_ranges[time_range]),
datetime.now(), prometheus_client)
memory_requests = await self._query_prometheus(memory_requests_query,
datetime.now() - timedelta(seconds=self.time_ranges[time_range]),
datetime.now(), prometheus_client)
container_count = await self._query_prometheus(container_count_query,
datetime.now() - timedelta(seconds=self.time_ranges[time_range]),
datetime.now(), prometheus_client)
# Calculate utilization percentages
cpu_utilization = 0
memory_utilization = 0
if cpu_usage and cpu_requests and cpu_requests[0][1] != '0':
cpu_utilization = (float(cpu_usage[0][1]) / float(cpu_requests[0][1])) * 100
if memory_usage and memory_requests and memory_requests[0][1] != '0':
memory_utilization = (float(memory_usage[0][1]) / float(memory_requests[0][1])) * 100
# Generate recommendations based on utilization
recommendations = []
if cpu_utilization > 80:
recommendations.append({
"type": "cpu_high_utilization",
"severity": "warning",
"message": f"High CPU utilization: {cpu_utilization:.1f}%",
"recommendation": "Consider increasing CPU requests or optimizing application performance"
})
elif cpu_utilization < 20:
recommendations.append({
"type": "cpu_low_utilization",
"severity": "info",
"message": f"Low CPU utilization: {cpu_utilization:.1f}%",
"recommendation": "Consider reducing CPU requests to optimize resource allocation"
})
if memory_utilization > 80:
recommendations.append({
"type": "memory_high_utilization",
"severity": "warning",
"message": f"High memory utilization: {memory_utilization:.1f}%",
"recommendation": "Consider increasing memory requests or optimizing memory usage"
})
elif memory_utilization < 20:
recommendations.append({
"type": "memory_low_utilization",
"severity": "info",
"message": f"Low memory utilization: {memory_utilization:.1f}%",
"recommendation": "Consider reducing memory requests to optimize resource allocation"
})
return {
'namespace': namespace,
'pod_name': pod_name,
'time_range': time_range,
'cpu_usage': float(cpu_usage[0][1]) if cpu_usage else 0,
'memory_usage': float(memory_usage[0][1]) if memory_usage else 0,
'cpu_requests': float(cpu_requests[0][1]) if cpu_requests else 0,
'memory_requests': float(memory_requests[0][1]) if memory_requests else 0,
'cpu_utilization': cpu_utilization,
'memory_utilization': memory_utilization,
'container_count': int(container_count[0][1]) if container_count else 0,
'recommendations': recommendations
}
except Exception as e:
logger.error(f"Error getting historical analysis for pod {pod_name} in namespace {namespace}: {e}")
return {
'namespace': namespace,
'pod_name': pod_name,
'time_range': time_range,
'error': str(e),
'recommendations': []
}

View File

@@ -1042,7 +1042,6 @@
<div class="accordion-stat">${totalValidations} analysis</div>
<div class="accordion-stat" style="color: #dc3545;">${errorCount} errors</div>
<div class="accordion-stat" style="color: #ffc107;">${warningCount} warnings</div>
<button class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.8rem; margin-left: 0.5rem;" onclick="event.stopPropagation(); loadNamespaceHistoricalAnalysis('${namespace.namespace}')">Historical Analysis</button>
</div>
<div class="accordion-arrow">▶</div>
</div>
@@ -1056,7 +1055,10 @@
<div class="pod-item">
<div class="pod-header">
<div class="pod-name">${pod.pod_name}</div>
<div class="pod-validations-count">${pod.validations.length} validations</div>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<div class="pod-validations-count">${pod.validations.length} validations</div>
<button class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.8rem;" onclick="loadPodHistoricalAnalysis('${namespace.namespace}', '${pod.pod_name}')">Historical Analysis</button>
</div>
</div>
<div class="validation-list">
`;
@@ -1356,6 +1358,28 @@
}
// Historical Analysis Modal Functions
async function loadPodHistoricalAnalysis(namespace, podName) {
showLoading();
try {
const timeRange = '24h'; // Default time range
const response = await fetch(`/api/v1/namespace/${namespace}/pod/${podName}/historical-analysis?time_range=${timeRange}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
displayPodHistoricalAnalysis(data);
showHistoricalModal(`${namespace}/${podName}`);
} catch (error) {
showError('Error loading historical analysis: ' + error.message);
} finally {
hideLoading();
}
}
async function loadNamespaceHistoricalAnalysis(namespace) {
showLoading();
@@ -1378,6 +1402,84 @@
}
}
function displayPodHistoricalAnalysis(data) {
const container = document.getElementById('historicalModalBody');
if (data.analysis.error) {
container.innerHTML = `
<div class="error">
<h3>Error loading historical data</h3>
<p>${data.analysis.error}</p>
</div>
`;
return;
}
const analysis = data.analysis;
const recommendations = analysis.recommendations || [];
let html = `
<div class="historical-summary">
<h3>Pod: ${analysis.pod_name}</h3>
<p><strong>Namespace:</strong> ${analysis.namespace}</p>
<p><strong>Time Range:</strong> ${analysis.time_range}</p>
<p><strong>Containers:</strong> ${analysis.container_count}</p>
</div>
<div class="historical-stats">
<div class="historical-stat">
<h4>CPU Usage</h4>
<div class="value">${analysis.cpu_usage.toFixed(3)} cores</div>
</div>
<div class="historical-stat">
<h4>Memory Usage</h4>
<div class="value">${(analysis.memory_usage / (1024*1024*1024)).toFixed(2)} GiB</div>
</div>
<div class="historical-stat">
<h4>CPU Utilization</h4>
<div class="value" style="color: ${analysis.cpu_utilization > 80 ? '#dc3545' : analysis.cpu_utilization < 20 ? '#28a745' : '#007bff'}">${analysis.cpu_utilization.toFixed(1)}%</div>
</div>
<div class="historical-stat">
<h4>Memory Utilization</h4>
<div class="value" style="color: ${analysis.memory_utilization > 80 ? '#dc3545' : analysis.memory_utilization < 20 ? '#28a745' : '#007bff'}">${analysis.memory_utilization.toFixed(1)}%</div>
</div>
</div>
`;
if (recommendations.length > 0) {
html += `
<div class="historical-summary">
<h3>Recommendations</h3>
`;
recommendations.forEach(rec => {
const severityClass = rec.severity;
html += `
<div class="historical-validation ${severityClass}">
<div class="validation-header">
<span class="severity-badge severity-${severityClass}">${severityClass}</span>
${rec.message}
</div>
<div class="validation-recommendation">
<strong>Recommendation:</strong> ${rec.recommendation}
</div>
</div>
`;
});
html += `</div>`;
} else {
html += `
<div class="historical-summary">
<h3>Recommendations</h3>
<p>No specific recommendations at this time. Resource utilization appears to be within normal ranges.</p>
</div>
`;
}
container.innerHTML = html;
}
function displayNamespaceHistoricalAnalysis(data) {
const container = document.getElementById('historicalModalBody');