Feature: Add real Prometheus metrics visualization for historical analysis
This commit is contained in:
@@ -488,6 +488,65 @@ async def get_historical_validations(
|
|||||||
logger.error(f"Error getting historical validations: {e}")
|
logger.error(f"Error getting historical validations: {e}")
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@api_router.get("/workloads/{namespace}/{workload}/metrics")
|
||||||
|
async def get_workload_historical_metrics(
|
||||||
|
namespace: str,
|
||||||
|
workload: str,
|
||||||
|
time_range: str = "24h"
|
||||||
|
):
|
||||||
|
"""Get historical metrics for a specific workload (deployment/daemonset)"""
|
||||||
|
try:
|
||||||
|
prometheus_client = PrometheusClient()
|
||||||
|
|
||||||
|
# Get CPU and Memory usage metrics for the workload
|
||||||
|
cpu_usage = await prometheus_client.query_range(
|
||||||
|
f'rate(container_cpu_usage_seconds_total{{namespace="{namespace}",pod=~"{workload}-.*"}}[5m])',
|
||||||
|
time_range
|
||||||
|
)
|
||||||
|
|
||||||
|
memory_usage = await prometheus_client.query_range(
|
||||||
|
f'container_memory_working_set_bytes{{namespace="{namespace}",pod=~"{workload}-.*"}}',
|
||||||
|
time_range
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get resource requests and limits
|
||||||
|
cpu_requests = await prometheus_client.query_range(
|
||||||
|
f'kube_pod_container_resource_requests{{namespace="{namespace}",pod=~"{workload}-.*",resource="cpu"}}',
|
||||||
|
time_range
|
||||||
|
)
|
||||||
|
|
||||||
|
memory_requests = await prometheus_client.query_range(
|
||||||
|
f'kube_pod_container_resource_requests{{namespace="{namespace}",pod=~"{workload}-.*",resource="memory"}}',
|
||||||
|
time_range
|
||||||
|
)
|
||||||
|
|
||||||
|
cpu_limits = await prometheus_client.query_range(
|
||||||
|
f'kube_pod_container_resource_limits{{namespace="{namespace}",pod=~"{workload}-.*",resource="cpu"}}',
|
||||||
|
time_range
|
||||||
|
)
|
||||||
|
|
||||||
|
memory_limits = await prometheus_client.query_range(
|
||||||
|
f'kube_pod_container_resource_limits{{namespace="{namespace}",pod=~"{workload}-.*",resource="memory"}}',
|
||||||
|
time_range
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"workload": workload,
|
||||||
|
"namespace": namespace,
|
||||||
|
"time_range": time_range,
|
||||||
|
"metrics": {
|
||||||
|
"cpu_usage": cpu_usage,
|
||||||
|
"memory_usage": memory_usage,
|
||||||
|
"cpu_requests": cpu_requests,
|
||||||
|
"memory_requests": memory_requests,
|
||||||
|
"cpu_limits": cpu_limits,
|
||||||
|
"memory_limits": memory_limits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting workload metrics for {namespace}/{workload}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
@api_router.get("/cluster/historical-summary")
|
@api_router.get("/cluster/historical-summary")
|
||||||
async def get_cluster_historical_summary(
|
async def get_cluster_historical_summary(
|
||||||
time_range: str = "24h"
|
time_range: str = "24h"
|
||||||
|
|||||||
@@ -384,6 +384,34 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workload-selector {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workload-selector select {
|
||||||
|
margin: 0 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container canvas {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Problem Summary Table */
|
/* Problem Summary Table */
|
||||||
.problem-summary {
|
.problem-summary {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
@@ -1334,7 +1362,211 @@
|
|||||||
// Action functions - these are defined above in the main functions
|
// Action functions - these are defined above in the main functions
|
||||||
|
|
||||||
function showHistoricalAnalysis() {
|
function showHistoricalAnalysis() {
|
||||||
showSection('historical-analysis');
|
// Create modal for historical analysis with real Prometheus data
|
||||||
|
let modal = document.getElementById('historicalModal');
|
||||||
|
if (!modal) {
|
||||||
|
modal = document.createElement('div');
|
||||||
|
modal.id = 'historicalModal';
|
||||||
|
modal.className = 'modal';
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="modal-content" style="width: 90%; max-width: 1200px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>📈 Historical Analysis - Real Prometheus Metrics</h2>
|
||||||
|
<span class="close">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="historicalModalBody">
|
||||||
|
<div class="workload-selector">
|
||||||
|
<h3>Select Workload to Analyze:</h3>
|
||||||
|
<select id="workloadSelect" onchange="loadWorkloadMetrics()">
|
||||||
|
<option value="">Choose a workload...</option>
|
||||||
|
</select>
|
||||||
|
<select id="timeRangeSelect" onchange="loadWorkloadMetrics()">
|
||||||
|
<option value="1h">Last 1 hour</option>
|
||||||
|
<option value="6h">Last 6 hours</option>
|
||||||
|
<option value="24h" selected>Last 24 hours</option>
|
||||||
|
<option value="7d">Last 7 days</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="metricsCharts" style="display: none;">
|
||||||
|
<div class="chart-container">
|
||||||
|
<h3>CPU Usage vs Requests/Limits</h3>
|
||||||
|
<canvas id="cpuChart" width="800" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="chart-container">
|
||||||
|
<h3>Memory Usage vs Requests/Limits</h3>
|
||||||
|
<canvas id="memoryChart" width="800" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
// Add close functionality
|
||||||
|
modal.querySelector('.close').onclick = () => modal.style.display = 'none';
|
||||||
|
modal.onclick = (e) => {
|
||||||
|
if (e.target === modal) modal.style.display = 'none';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate workload selector
|
||||||
|
populateWorkloadSelector();
|
||||||
|
modal.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateWorkloadSelector() {
|
||||||
|
if (!currentData || !currentData.namespaces) return;
|
||||||
|
|
||||||
|
const select = document.getElementById('workloadSelect');
|
||||||
|
select.innerHTML = '<option value="">Choose a workload...</option>';
|
||||||
|
|
||||||
|
currentData.namespaces.forEach(namespace => {
|
||||||
|
Object.values(namespace.pods || {}).forEach(pod => {
|
||||||
|
// Extract workload name from pod name (remove random suffix)
|
||||||
|
const workloadName = pod.pod_name.replace(/-\w{10}-\w{5}$/, '');
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = `${namespace.namespace}/${workloadName}`;
|
||||||
|
option.textContent = `${namespace.namespace}/${workloadName}`;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWorkloadMetrics() {
|
||||||
|
const workloadSelect = document.getElementById('workloadSelect');
|
||||||
|
const timeRangeSelect = document.getElementById('timeRangeSelect');
|
||||||
|
const chartsDiv = document.getElementById('metricsCharts');
|
||||||
|
|
||||||
|
if (!workloadSelect.value) {
|
||||||
|
chartsDiv.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [namespace, workload] = workloadSelect.value.split('/');
|
||||||
|
const timeRange = timeRangeSelect.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
chartsDiv.style.display = 'block';
|
||||||
|
chartsDiv.innerHTML = '<p>Loading metrics from Prometheus...</p>';
|
||||||
|
|
||||||
|
const response = await fetch(`/api/v1/workloads/${namespace}/${workload}/metrics?time_range=${timeRange}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.metrics) {
|
||||||
|
renderMetricsCharts(data.metrics, timeRange);
|
||||||
|
} else {
|
||||||
|
chartsDiv.innerHTML = '<p>No metrics data available for this workload.</p>';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading metrics:', error);
|
||||||
|
chartsDiv.innerHTML = '<p>Error loading metrics. Please try again.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMetricsCharts(metrics, timeRange) {
|
||||||
|
const chartsDiv = document.getElementById('metricsCharts');
|
||||||
|
chartsDiv.innerHTML = `
|
||||||
|
<div class="chart-container">
|
||||||
|
<h3>CPU Usage vs Requests/Limits (${timeRange})</h3>
|
||||||
|
<canvas id="cpuChart" width="800" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="chart-container">
|
||||||
|
<h3>Memory Usage vs Requests/Limits (${timeRange})</h3>
|
||||||
|
<canvas id="memoryChart" width="800" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Simple chart rendering (you can replace with Chart.js or similar)
|
||||||
|
renderSimpleChart('cpuChart', metrics.cpu_usage, metrics.cpu_requests, metrics.cpu_limits, 'CPU (cores)');
|
||||||
|
renderSimpleChart('memoryChart', metrics.memory_usage, metrics.memory_requests, metrics.memory_limits, 'Memory (bytes)');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSimpleChart(canvasId, usage, requests, limits, unit) {
|
||||||
|
const canvas = document.getElementById(canvasId);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const width = canvas.width;
|
||||||
|
const height = canvas.height;
|
||||||
|
|
||||||
|
// Clear canvas
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// Draw axes
|
||||||
|
ctx.strokeStyle = '#333';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(50, height - 50);
|
||||||
|
ctx.lineTo(width - 50, height - 50);
|
||||||
|
ctx.moveTo(50, 50);
|
||||||
|
ctx.lineTo(50, height - 50);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Draw usage line
|
||||||
|
if (usage && usage.length > 0) {
|
||||||
|
ctx.strokeStyle = '#007bff';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
usage.forEach((point, index) => {
|
||||||
|
const x = 50 + (index * (width - 100) / usage.length);
|
||||||
|
const y = height - 50 - (point[1] * (height - 100) / Math.max(...usage.map(p => p[1])));
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw requests line
|
||||||
|
if (requests && requests.length > 0) {
|
||||||
|
ctx.strokeStyle = '#28a745';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.setLineDash([5, 5]);
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
requests.forEach((point, index) => {
|
||||||
|
const x = 50 + (index * (width - 100) / requests.length);
|
||||||
|
const y = height - 50 - (point[1] * (height - 100) / Math.max(...requests.map(p => p[1])));
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw limits line
|
||||||
|
if (limits && limits.length > 0) {
|
||||||
|
ctx.strokeStyle = '#dc3545';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.setLineDash([5, 5]);
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
limits.forEach((point, index) => {
|
||||||
|
const x = 50 + (index * (width - 100) / limits.length);
|
||||||
|
const y = height - 50 - (point[1] * (height - 100) / Math.max(...limits.map(p => p[1])));
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset line dash
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
|
||||||
|
// Add labels
|
||||||
|
ctx.fillStyle = '#333';
|
||||||
|
ctx.font = '12px Arial';
|
||||||
|
ctx.fillText(unit, 10, height / 2);
|
||||||
|
ctx.fillText('Time', width / 2, height - 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportComplianceReport() {
|
function exportComplianceReport() {
|
||||||
|
|||||||
Reference in New Issue
Block a user