mirror of
https://github.com/Retropex/custom-ocean.xyz-dashboard.git
synced 2025-05-12 19:20:45 +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}")
|
logging.error(f"Error during forced GC: {e}")
|
||||||
return jsonify({"status": "error", "message": str(e)}), 500
|
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")
|
@app.route("/api/notifications")
|
||||||
def api_notifications():
|
def api_notifications():
|
||||||
"""API endpoint for notification data."""
|
"""API endpoint for notification data."""
|
||||||
|
@ -10,9 +10,10 @@ services:
|
|||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
interval: 10s
|
interval: 1s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
start_period: 3s
|
||||||
|
|
||||||
dashboard:
|
dashboard:
|
||||||
build: .
|
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