Optimize Idle CPU/Dash
Release rolling / release (push) Successful in 8m21s

This commit is contained in:
Raven Scott
2026-08-04 21:04:55 -04:00
parent 450b54ca55
commit ab6cea96c7
9 changed files with 690 additions and 214 deletions
+126 -74
View File
@@ -107,11 +107,7 @@ function bindLivePeer(conn) {
if (conn.capability) entry.capability = conn.capability;
updateConnectionStatus(topicId, true);
updateConnectionDisplay(topicId);
// Restart per-peer health UI loop (clears itself if peer drops)
if (entry.healthCheckInterval) {
clearInterval(entry.healthCheckInterval);
entry.healthCheckInterval = null;
}
// Sync health UI from live peer (manager health loop owns pings — no second interval)
startHealthMonitoring(topicId);
}
@@ -407,14 +403,22 @@ const volumesStore = {
// Expose volumes store to window for use by other modules
window.volumesStore = volumesStore;
let lastStatsUpdate = Date.now();
/**
* Legacy no-ops: live container stats arrive via push:allStats from the server.
* Kept as named exports so call sites stay stable without a 500ms wake timer.
*/
function stopStatsInterval() {
if (statsInterval) {
clearInterval(statsInterval);
statsInterval = null;
console.log('[INFO] Stats interval stopped.');
}
}
function startStatsInterval() {
// Clear any leftover timer from older sessions; do not schedule a new one.
stopStatsInterval();
}
// Utility functions are now imported from uiUtils.js
@@ -428,30 +432,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
});
function startStatsInterval() {
// Guard: stop existing interval before starting a new one
stopStatsInterval();
// Only start if there's an active peer
if (!window.activePeer) {
console.warn('[WARN] No active peer; not starting stats interval.');
return;
}
// Increased interval to 500ms for better performance (was 100ms)
statsInterval = setInterval(() => {
if (window.activePeer) {
const now = Date.now();
if (now - lastStatsUpdate >= 500) { // Ensure at least 500ms between updates
lastStatsUpdate = now;
}
} else {
console.warn('[WARN] No active peer; skipping stats request.');
stopStatsInterval(); // Stop interval if peer is no longer active
}
}, 500); // Poll every 500ms for better performance (reduced from 100ms)
}
const smoothedStats = {}; // Container-specific smoothing storage
const historicalStats = {}; // Container-specific historical stats for charts
const MAX_HISTORY_POINTS = 900; // ~30m at 2s broadcast interval
@@ -2577,11 +2557,75 @@ document.addEventListener('DOMContentLoaded', () => {
// Expose to window for onclick handlers
window.navigateToView = navigateToView;
/**
* Dashboard RPC TTL cache avoids re-hitting the Docker socket on every
* navigation to the dashboard when nothing has changed.
* Invalidated on peer switch and on relevant list/event responses.
*/
const DASHBOARD_TTL = {
systemInfo: 30_000,
systemDf: 30_000,
containers: 10_000,
images: 20_000,
networks: 20_000,
volumes: 20_000,
};
/** @type {{ peerId: string, at: Record<string, number> }} */
const dashboardRpcCache = { peerId: '', at: {} };
function dashboardCachePeerId() {
return manager.active?.id || window.activePeer?.id || '';
}
function touchDashboardCache(key) {
const peerId = dashboardCachePeerId();
if (!peerId) return;
if (dashboardRpcCache.peerId !== peerId) {
dashboardRpcCache.peerId = peerId;
dashboardRpcCache.at = {};
}
dashboardRpcCache.at[key] = Date.now();
}
/**
* @param {string} key
* @param {number} [ttlMs]
*/
function isDashboardCacheFresh(key, ttlMs) {
const peerId = dashboardCachePeerId();
if (!peerId || dashboardRpcCache.peerId !== peerId) return false;
const at = dashboardRpcCache.at[key];
if (!at) return false;
const ttl = ttlMs ?? DASHBOARD_TTL[key] ?? 15_000;
return Date.now() - at < ttl;
}
/**
* @param {string|string[]|'all'} [keys]
*/
function invalidateDashboardCache(keys = 'all') {
if (keys === 'all') {
dashboardRpcCache.at = {};
return;
}
const list = Array.isArray(keys) ? keys : [keys];
for (const k of list) delete dashboardRpcCache.at[k];
}
window.invalidateDashboardCache = invalidateDashboardCache;
// Dashboard Functions
function loadDashboard() {
if (!hasActiveConnection() && !window.activePeer) {
return;
}
const peerId = dashboardCachePeerId();
if (peerId && dashboardRpcCache.peerId !== peerId) {
dashboardRpcCache.peerId = peerId;
dashboardRpcCache.at = {};
}
// Set up volumes subscription early to catch broadcasts
if (!volumesStoreSubscription) {
@@ -2627,22 +2671,27 @@ function loadDashboard() {
}
})
.catch(() => {});
// Load system info
sendCommand('getSystemInfo');
sendCommand('getSystemDf');
// Load container stats for counts
sendCommand('listContainers');
// Load images count
sendCommand('listImages');
// Load networks count
sendCommand('listNetworks');
// Load volumes
sendCommand('listVolumes');
// Only re-fetch slices that are stale — same data when fresh, less Docker socket load.
// When fresh, rely on event pushes + auto-refresh for updates.
if (!isDashboardCacheFresh('systemInfo')) {
sendCommand('getSystemInfo');
}
if (!isDashboardCacheFresh('systemDf')) {
sendCommand('getSystemDf');
}
if (!isDashboardCacheFresh('containers')) {
sendCommand('listContainers');
}
if (!isDashboardCacheFresh('images')) {
sendCommand('listImages');
}
if (!isDashboardCacheFresh('networks')) {
sendCommand('listNetworks');
}
if (!isDashboardCacheFresh('volumes')) {
sendCommand('listVolumes');
}
}
window.loadDashboard = loadDashboard;
@@ -2662,6 +2711,7 @@ function formatBytes(bytes) {
function refreshSystemDf() {
if (!hasActiveConnection()) return;
invalidateDashboardCache('systemDf');
sendCommand('getSystemDf');
}
window.refreshSystemDf = refreshSystemDf;
@@ -9773,6 +9823,7 @@ function handleRpcMessage(response, conn) {
: null;
if (rows) {
applyContainerSnapshot(rows, topicId, { source: 'rpc' });
touchDashboardCache('containers');
if (currentView === 'dashboard') {
updateDashboardStats(containerFilterState.allContainers, null, null);
}
@@ -9869,11 +9920,13 @@ function handleRpcMessage(response, conn) {
case 'systemInfo':
console.log('[INFO] Handling system information...');
updateSystemInfo(response.data);
touchDashboardCache('systemInfo');
break;
case 'images':
console.log('[INFO] Handling images list...');
renderImages(response.data);
touchDashboardCache('images');
// Update dashboard stats if on dashboard view
if (currentView === 'dashboard') {
updateDashboardStats(null, response.data, null);
@@ -9883,6 +9936,7 @@ function handleRpcMessage(response, conn) {
case 'networks':
console.log('[INFO] Handling networks list...');
renderNetworks(response.data);
touchDashboardCache('networks');
// Update dashboard stats if on dashboard view
if (currentView === 'dashboard') {
updateDashboardStats(null, null, response.data);
@@ -9903,6 +9957,7 @@ function handleRpcMessage(response, conn) {
if (volumesToRender !== null) {
// Always update store first (this will trigger subscriptions)
volumesStore.set(volumesToRender);
touchDashboardCache('volumes');
// Always render if on volumes view to ensure UI is updated
if (currentView === 'volumes') {
renderVolumes(volumesToRender);
@@ -9945,6 +10000,13 @@ function handleRpcMessage(response, conn) {
}
case 'dockerEvent': {
// Fleet-changing events: force dashboard slices to re-fetch on next visit
const ev = response.data || response;
const t = String(ev?.Type || ev?.type || '').toLowerCase();
if (t === 'container') invalidateDashboardCache('containers');
else if (t === 'image') invalidateDashboardCache(['images', 'systemDf']);
else if (t === 'volume') invalidateDashboardCache(['volumes', 'systemDf']);
else if (t === 'network') invalidateDashboardCache('networks');
if (window.handleDockerEvent) {
window.handleDockerEvent(response.data);
}
@@ -10015,6 +10077,7 @@ function handleRpcMessage(response, conn) {
case 'systemDf':
renderSystemDf(response.data);
touchDashboardCache('systemDf');
break;
case 'containerTop':
@@ -11248,8 +11311,12 @@ function updateConnectionDisplay(topicId) {
: String(fullKey);
}
// Health monitoring for connections (UI latency badge). Dead-link detection + reconnect
// is owned by ConnectionManager's health loop / PearDockConnection.ping().
/**
* Sync connection-row health UI from the live peer.
* Ping / dead-link detection is owned solely by ConnectionManager's health loop
* (manager emits 'health' updateConnectionDisplay). A second interval here used
* to double docker.version() load on the server every 10s.
*/
function startHealthMonitoring(topicId) {
const connection = connections[topicId];
if (!connection) return;
@@ -11258,36 +11325,20 @@ function startHealthMonitoring(topicId) {
connection.healthCheckInterval = null;
}
const healthCheckInterval = setInterval(async () => {
const entry = connections[topicId];
// Prefer manager's live socket if UI map is briefly stale after reconnect
const peer = entry?.peer?.connected
? entry.peer
const peer =
connection.peer?.connected
? connection.peer
: manager.connections.get(topicId)?.connected
? manager.connections.get(topicId)
: null;
if (!peer) {
clearInterval(healthCheckInterval);
if (entry) entry.healthCheckInterval = null;
return;
}
if (entry && entry.peer !== peer) entry.peer = peer;
try {
const ms = await peer.ping();
entry.latency = ms;
entry.lastHealthCheck = Date.now();
entry.healthStatus = ms < 5000 ? 'healthy' : 'slow';
updateConnectionStatus(topicId, true);
updateConnectionDisplay(topicId);
} catch (err) {
entry.healthStatus = 'unhealthy';
updateConnectionStatus(topicId, false);
updateConnectionDisplay(topicId);
// Do not clear forever — manager will disconnect + redial; bindLivePeer restarts this loop
}
}, 10000);
if (!peer) return;
if (connection.peer !== peer) connection.peer = peer;
connections[topicId].healthCheckInterval = healthCheckInterval;
connection.latency = peer.latency ?? connection.latency;
connection.healthStatus = peer.healthStatus || connection.healthStatus || 'healthy';
connection.lastHealthCheck = peer.lastHealthCheck || connection.lastHealthCheck || Date.now();
updateConnectionStatus(topicId, true);
updateConnectionDisplay(topicId);
}
// Switch between connections
@@ -11336,6 +11387,7 @@ function switchConnection(topicId, opts = {}) {
// Drop previous host's containers immediately so the table never mixes peers
resetContainerList();
containerStore.topicId = manager.active?.id || topicId || '';
invalidateDashboardCache('all');
console.log(`[INFO] Switched to connection: ${topicId}`);
startStatsInterval();
sendCommand(Methods.listContainers);