diff --git a/app/api/routes.py b/app/api/routes.py index f325795..a26e621 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -423,6 +423,32 @@ async def get_cluster_historical_summary( logger.error(f"Error getting historical summary: {e}") raise HTTPException(status_code=500, detail=str(e)) +@api_router.get("/namespace/{namespace}/historical-analysis") +async def get_namespace_historical_analysis( + namespace: str, + time_range: str = "24h", + prometheus_client=Depends(get_prometheus_client) +): + """Get historical analysis for a specific namespace""" + try: + historical_service = HistoricalAnalysisService() + + # Get historical analysis for the namespace + analysis = await historical_service.get_namespace_historical_analysis( + namespace, time_range, prometheus_client + ) + + return { + "namespace": namespace, + "time_range": time_range, + "analysis": analysis, + "timestamp": datetime.now().isoformat() + } + + except Exception as e: + logger.error(f"Error getting historical analysis for 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 6df141c..3f063ba 100644 --- a/app/services/historical_analysis.py +++ b/app/services/historical_analysis.py @@ -443,3 +443,129 @@ class HistoricalAnalysisService: except Exception as e: logger.error(f"Error getting historical summary: {e}") return {} + + async def get_namespace_historical_analysis(self, namespace: str, time_range: str, prometheus_client): + """Get historical analysis for a specific namespace""" + try: + logger.info(f"Getting historical analysis for namespace: {namespace}") + + # Query for CPU usage by namespace + cpu_query = f''' + sum(rate(container_cpu_usage_seconds_total{{ + namespace="{namespace}", + container!="POD", + container!="" + }}[{time_range}])) + ''' + + # Query for memory usage by namespace + memory_query = f''' + sum(container_memory_working_set_bytes{{ + namespace="{namespace}", + container!="POD", + container!="" + }}) + ''' + + # Query for CPU requests by namespace + cpu_requests_query = f''' + sum(kube_pod_container_resource_requests{{ + namespace="{namespace}", + resource="cpu" + }}) + ''' + + # Query for memory requests by namespace + memory_requests_query = f''' + sum(kube_pod_container_resource_requests{{ + namespace="{namespace}", + resource="memory" + }}) + ''' + + # Query for pod count by namespace + pod_count_query = f''' + count(kube_pod_info{{namespace="{namespace}"}}) + ''' + + # 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) + pod_count = await self._query_prometheus(pod_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, + '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, + 'pod_count': int(pod_count[0][1]) if pod_count else 0, + 'recommendations': recommendations + } + + except Exception as e: + logger.error(f"Error getting historical analysis for namespace {namespace}: {e}") + return { + 'namespace': namespace, + 'time_range': time_range, + 'error': str(e), + 'recommendations': [] + } diff --git a/app/static/index.html b/app/static/index.html index c1b1bad..55f6eb0 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -497,6 +497,66 @@ font-size: 0.9rem; } + /* Modal Styles */ + .modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + } + + .modal-content { + background: white; + border-radius: 8px; + max-width: 800px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + } + + .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.5rem; + border-bottom: 1px solid #dee2e6; + background: #f8f9fa; + } + + .modal-header h2 { + margin: 0; + color: #cc0000; + } + + .modal-close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: #6c757d; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + } + + .modal-close:hover { + color: #dc3545; + } + + .modal-body { + padding: 1.5rem; + } + /* Filters */ .filters { display: flex; @@ -602,7 +662,6 @@
-
@@ -713,6 +772,19 @@ + + +