From 0132a90387c28adb660cdf9a1f497ad53c1b6003 Mon Sep 17 00:00:00 2001 From: andersonid Date: Fri, 26 Sep 2025 10:01:51 -0300 Subject: [PATCH] Move Historical Analysis button to individual pod cards with pod-specific Prometheus queries --- app/api/routes.py | 28 ++++++ app/services/historical_analysis.py | 137 ++++++++++++++++++++++++++++ app/static/index.html | 106 ++++++++++++++++++++- 3 files changed, 269 insertions(+), 2 deletions(-) diff --git a/app/api/routes.py b/app/api/routes.py index a26e621..60485b6 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -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""" diff --git a/app/services/historical_analysis.py b/app/services/historical_analysis.py index 3f063ba..5403647 100644 --- a/app/services/historical_analysis.py +++ b/app/services/historical_analysis.py @@ -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': [] + } diff --git a/app/static/index.html b/app/static/index.html index fd62c21..99586dd 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -1042,7 +1042,6 @@
${totalValidations} analysis
${errorCount} errors
${warningCount} warnings
-
@@ -1056,7 +1055,10 @@
${pod.pod_name}
-
${pod.validations.length} validations
+
+
${pod.validations.length} validations
+ +
`; @@ -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 = ` +
+

Error loading historical data

+

${data.analysis.error}

+
+ `; + return; + } + + const analysis = data.analysis; + const recommendations = analysis.recommendations || []; + + let html = ` +
+

Pod: ${analysis.pod_name}

+

Namespace: ${analysis.namespace}

+

Time Range: ${analysis.time_range}

+

Containers: ${analysis.container_count}

+
+ +
+
+

CPU Usage

+
${analysis.cpu_usage.toFixed(3)} cores
+
+
+

Memory Usage

+
${(analysis.memory_usage / (1024*1024*1024)).toFixed(2)} GiB
+
+
+

CPU Utilization

+
${analysis.cpu_utilization.toFixed(1)}%
+
+
+

Memory Utilization

+
${analysis.memory_utilization.toFixed(1)}%
+
+
+ `; + + if (recommendations.length > 0) { + html += ` +
+

Recommendations

+ `; + + recommendations.forEach(rec => { + const severityClass = rec.severity; + html += ` +
+
+ ${severityClass} + ${rec.message} +
+
+ Recommendation: ${rec.recommendation} +
+
+ `; + }); + + html += `
`; + } else { + html += ` +
+

Recommendations

+

No specific recommendations at this time. Resource utilization appears to be within normal ranges.

+
+ `; + } + + container.innerHTML = html; + } + function displayNamespaceHistoricalAnalysis(data) { const container = document.getElementById('historicalModalBody');