mirror of
https://github.com/Retropex/custom-ocean.xyz-dashboard.git
synced 2025-05-12 11:10:44 +02:00
Add memory dashboard and improve Redis health checks
- Implemented a new `/memory-dashboard` route in `App.py` to display memory usage metrics, including current usage, historical data, and garbage collection stats. - Updated `docker-compose.yml` to change Redis health check interval from 10s to 1s and added a start period of 3s. - Created `memory_dashboard.html` with a dashboard layout, styles, and scripts for dynamic memory metrics display and actions.
This commit is contained in:
parent
af263b2fe4
commit
2799ff15ea
36
App.py
36
App.py
@ -1061,6 +1061,42 @@ def force_gc():
|
||||
logging.error(f"Error during forced GC: {e}")
|
||||
return jsonify({"status": "error", "message": str(e)}), 500
|
||||
|
||||
@app.route("/memory-dashboard")
|
||||
def memory_dashboard():
|
||||
"""Serve the memory monitoring dashboard page."""
|
||||
try:
|
||||
process = psutil.Process(os.getpid())
|
||||
mem_info = process.memory_info()
|
||||
|
||||
memory_data = {
|
||||
"current_usage": {
|
||||
"rss_mb": mem_info.rss / 1024 / 1024,
|
||||
"percent": process.memory_percent()
|
||||
},
|
||||
"history": memory_usage_history[-20:] if memory_usage_history else [],
|
||||
"data_structures": {
|
||||
"arrow_history_entries": sum(len(v) for v in arrow_history.values() if isinstance(v, list)),
|
||||
"arrow_history_keys": list(arrow_history.keys()),
|
||||
"metrics_log_entries": len(metrics_log),
|
||||
"sse_connections": active_sse_connections
|
||||
},
|
||||
"gc_stats": {
|
||||
"counts": gc.get_count(),
|
||||
"threshold": gc.get_threshold(),
|
||||
"enabled": gc.isenabled()
|
||||
}
|
||||
}
|
||||
|
||||
current_time = datetime.now(ZoneInfo(get_timezone())).strftime("%Y-%m-%d %H:%M:%S %p")
|
||||
return render_template(
|
||||
"memory_dashboard.html",
|
||||
memory=memory_data,
|
||||
current_time=current_time
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"Error rendering memory dashboard: {e}")
|
||||
return render_template("error.html", message="Error loading memory dashboard."), 500
|
||||
|
||||
@app.route("/api/notifications")
|
||||
def api_notifications():
|
||||
"""API endpoint for notification data."""
|
||||
|
@ -10,9 +10,10 @@ services:
|
||||
- "6379:6379"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 3s
|
||||
|
||||
dashboard:
|
||||
build: .
|
||||
|
339
templates/memory_dashboard.html
Normal file
339
templates/memory_dashboard.html
Normal file
@ -0,0 +1,339 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Memory Dashboard{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
.memory-dashboard {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.memory-card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.memory-metric {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.memory-metric:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.metric-name {
|
||||
font-weight: bold;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.critical {
|
||||
color: #ff5252;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #ffab40;
|
||||
}
|
||||
|
||||
.healthy {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background-color: var(--accent-color);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#memoryChart {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.data-table th, .data-table td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background-color: var(--header-bg);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="memory-dashboard">
|
||||
<h1>Memory Management Dashboard</h1>
|
||||
<p>Current time: {{ current_time }}</p>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="action-button" onclick="forceGC(2)">Force Full GC</button>
|
||||
<button class="action-button" onclick="forceGC(1)">Force Intermediate GC</button>
|
||||
<button class="action-button" onclick="refreshData()">Refresh Data</button>
|
||||
</div>
|
||||
|
||||
<div class="memory-card">
|
||||
<h2>Current Memory Usage</h2>
|
||||
<div class="memory-metric">
|
||||
<span class="metric-name">RSS Memory</span>
|
||||
<span class="metric-value {% if memory.current_usage.percent > 80 %}critical{% elif memory.current_usage.percent > 60 %}warning{% else %}healthy{% endif %}">
|
||||
{{ "%.2f"|format(memory.current_usage.rss_mb) }} MB
|
||||
</span>
|
||||
</div>
|
||||
<div class="memory-metric">
|
||||
<span class="metric-name">Memory Percentage</span>
|
||||
<span class="metric-value {% if memory.current_usage.percent > 80 %}critical{% elif memory.current_usage.percent > 60 %}warning{% else %}healthy{% endif %}">
|
||||
{{ "%.2f"|format(memory.current_usage.percent) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="memory-card">
|
||||
<h2>Data Structure Sizes</h2>
|
||||
<div class="memory-metric">
|
||||
<span class="metric-name">Arrow History Entries</span>
|
||||
<span class="metric-value">{{ memory.data_structures.arrow_history_entries }}</span>
|
||||
</div>
|
||||
<div class="memory-metric">
|
||||
<span class="metric-name">Metrics Log Entries</span>
|
||||
<span class="metric-value">{{ memory.data_structures.metrics_log_entries }}</span>
|
||||
</div>
|
||||
<div class="memory-metric">
|
||||
<span class="metric-name">Active SSE Connections</span>
|
||||
<span class="metric-value">{{ memory.data_structures.sse_connections }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="memory-card">
|
||||
<h2>Garbage Collection</h2>
|
||||
<div class="memory-metric">
|
||||
<span class="metric-name">Counts</span>
|
||||
<span class="metric-value">{{ memory.gc_stats.counts }}</span>
|
||||
</div>
|
||||
<div class="memory-metric">
|
||||
<span class="metric-name">Threshold</span>
|
||||
<span class="metric-value">{{ memory.gc_stats.threshold }}</span>
|
||||
</div>
|
||||
<div class="memory-metric">
|
||||
<span class="metric-name">Enabled</span>
|
||||
<span class="metric-value">{{ memory.gc_stats.enabled }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Memory History</h2>
|
||||
<div id="memoryChart"></div>
|
||||
|
||||
<h2>Memory History Data</h2>
|
||||
{% if memory.history %}
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Memory (MB)</th>
|
||||
<th>Memory %</th>
|
||||
<th>Arrow History</th>
|
||||
<th>Metrics Log</th>
|
||||
<th>SSE Connections</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in memory.history %}
|
||||
<tr>
|
||||
<td>{{ entry.timestamp.split('T')[1].split('.')[0] }}</td>
|
||||
<td>{{ "%.2f"|format(entry.rss_mb) }}</td>
|
||||
<td>{{ "%.2f"|format(entry.percent) }}%</td>
|
||||
<td>{{ entry.arrow_history_entries }}</td>
|
||||
<td>{{ entry.metrics_log_entries }}</td>
|
||||
<td>{{ entry.sse_connections }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No memory history data available yet. Memory metrics are collected every 5 minutes.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>// Global variables
|
||||
let memoryChart = null;
|
||||
|
||||
// Load memory history when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
refreshData();
|
||||
setInterval(refreshData, 60000); // Refresh every minute
|
||||
});
|
||||
|
||||
// Force garbage collection
|
||||
function forceGC(generation) {
|
||||
fetch('/api/force-gc', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ generation: generation })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('GC result:', data);
|
||||
alert(`Garbage collection complete. Collected ${data.collected} objects in ${data.duration_seconds.toFixed(2)}s`);
|
||||
refreshData();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error forcing GC:', error);
|
||||
alert('Error forcing garbage collection. See console for details.');
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh data
|
||||
function refreshData() {
|
||||
fetch('/api/memory-history')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
updateMemoryChart(data.history);
|
||||
updateCurrentMemory(data.current);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching memory history:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Update current memory display
|
||||
function updateCurrentMemory(current) {
|
||||
const rssMb = document.querySelector('.memory-metric:nth-child(1) .metric-value');
|
||||
const percent = document.querySelector('.memory-metric:nth-child(2) .metric-value');
|
||||
|
||||
if (rssMb && percent) {
|
||||
rssMb.textContent = `${current.rss_mb.toFixed(2)} MB`;
|
||||
percent.textContent = `${current.percent.toFixed(2)}%`;
|
||||
|
||||
// Update classes
|
||||
rssMb.className = 'metric-value ' + getMemoryClass(current.percent);
|
||||
percent.className = 'metric-value ' + getMemoryClass(current.percent);
|
||||
}
|
||||
}
|
||||
|
||||
// Get CSS class based on memory usage
|
||||
function getMemoryClass(percent) {
|
||||
if (percent > 80) return 'critical';
|
||||
if (percent > 60) return 'warning';
|
||||
return 'healthy';
|
||||
}
|
||||
|
||||
// Update memory chart
|
||||
function updateMemoryChart(history) {
|
||||
if (!history || history.length === 0) return;
|
||||
|
||||
const ctx = document.getElementById('memoryChart').getContext('2d');
|
||||
|
||||
// Extract data
|
||||
const labels = history.map(entry => {
|
||||
const timestamp = entry.timestamp.split('T')[1].split('.')[0]; // Just the time part
|
||||
return timestamp;
|
||||
});
|
||||
|
||||
const memoryData = history.map(entry => entry.rss_mb);
|
||||
const percentData = history.map(entry => entry.percent);
|
||||
|
||||
// Create or update chart
|
||||
if (memoryChart) {
|
||||
memoryChart.data.labels = labels;
|
||||
memoryChart.data.datasets[0].data = memoryData;
|
||||
memoryChart.data.datasets[1].data = percentData;
|
||||
memoryChart.update();
|
||||
} else {
|
||||
memoryChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Memory (MB)',
|
||||
data: memoryData,
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||
yAxisID: 'y',
|
||||
tension: 0.1
|
||||
},
|
||||
{
|
||||
label: 'Memory %',
|
||||
data: percentData,
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
yAxisID: 'y1',
|
||||
tension: 0.1
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Memory (MB)'
|
||||
}
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'right',
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Memory %'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}</script>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user