Skip to content

Commit daa1f36

Browse files
committed
Fix: Prevent I/O metrics zeroing on slider drag via snapshotting & improved rate calculation.
1 parent d3448cb commit daa1f36

File tree

2 files changed

+125
-26
lines changed

2 files changed

+125
-26
lines changed

src/public/js/ui/dashboard.js

Lines changed: 87 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ PulseApp.ui = PulseApp.ui || {};
22

33
PulseApp.ui.dashboard = (() => {
44
let searchInput = null;
5+
let guestMetricDragSnapshot = {}; // To store metrics during slider drag
56

67
function init() {
78
searchInput = document.getElementById('dashboard-search');
@@ -30,18 +31,26 @@ PulseApp.ui.dashboard = (() => {
3031
);
3132

3233
if (validHistory.length < 2) return null;
34+
3335
const oldest = validHistory[0];
3436
const newest = validHistory[validHistory.length - 1];
3537
const valueDiff = newest[key] - oldest[key];
36-
const timeDiff = (newest.timestamp - oldest.timestamp) / 1000;
38+
const timeDiffSeconds = (newest.timestamp - oldest.timestamp) / 1000;
39+
40+
if (timeDiffSeconds <= 0) {
41+
return null;
42+
}
3743

38-
if (timeDiff <= 0) return 0;
39-
return valueDiff / timeDiff;
44+
if (valueDiff < 0) {
45+
return null;
46+
}
47+
48+
return valueDiff / timeDiffSeconds;
4049
}
4150

4251
const processGuest = (guest, type) => {
4352
let avgCpu = 0, avgMem = 0, avgDisk = 0;
44-
let avgDiskReadRate = 0, avgDiskWriteRate = 0, avgNetInRate = 0, avgNetOutRate = 0;
53+
let avgDiskReadRate = null, avgDiskWriteRate = null, avgNetInRate = null, avgNetOutRate = null;
4554
let avgMemoryPercent = 'N/A', avgDiskPercent = 'N/A';
4655

4756
const metricsData = PulseApp.state.get('metricsData') || [];
@@ -54,27 +63,60 @@ PulseApp.ui.dashboard = (() => {
5463

5564
const guestUniqueId = guest.id;
5665

57-
if (guest.status === 'running' && metrics && metrics.current) {
58-
const currentDataPoint = {
59-
timestamp: Date.now(),
60-
...metrics.current
61-
};
62-
PulseApp.state.updateDashboardHistory(guestUniqueId, currentDataPoint);
63-
const history = PulseApp.state.getDashboardHistory()[guestUniqueId] || [];
64-
65-
avgCpu = calculateAverage(history, 'cpu') ?? 0;
66-
avgMem = calculateAverage(history, 'mem') ?? 0;
67-
avgDisk = calculateAverage(history, 'disk') ?? 0;
68-
avgDiskReadRate = calculateAverageRate(history, 'diskread') ?? 0;
69-
avgDiskWriteRate = calculateAverageRate(history, 'diskwrite') ?? 0;
70-
avgNetInRate = calculateAverageRate(history, 'netin') ?? 0;
71-
avgNetOutRate = calculateAverageRate(history, 'netout') ?? 0;
72-
avgMemoryPercent = (guest.maxmem > 0) ? Math.round(avgMem / guest.maxmem * 100) : 'N/A';
73-
avgDiskPercent = (guest.maxdisk > 0) ? Math.round(avgDisk / guest.maxdisk * 100) : 'N/A';
66+
// Check for drag state and apply snapshot if active
67+
const isDragging = PulseApp.ui.thresholds && PulseApp.ui.thresholds.isThresholdDragInProgress && PulseApp.ui.thresholds.isThresholdDragInProgress();
68+
const snapshot = guestMetricDragSnapshot[guestUniqueId];
69+
70+
if (isDragging && snapshot) {
71+
avgDiskReadRate = snapshot.diskread;
72+
avgDiskWriteRate = snapshot.diskwrite;
73+
avgNetInRate = snapshot.netin;
74+
avgNetOutRate = snapshot.netout;
75+
76+
// For other metrics, continue with live data or defaults if snapshot doesn't cover them
77+
// or if we decide to only snapshot I/O rates.
78+
// For now, let other metrics be calculated as usual even during drag.
79+
if (guest.status === 'running' && metrics && metrics.current) {
80+
const currentDataPoint = {
81+
timestamp: Date.now(),
82+
...metrics.current
83+
};
84+
PulseApp.state.updateDashboardHistory(guestUniqueId, currentDataPoint);
85+
const history = PulseApp.state.getDashboardHistory()[guestUniqueId] || [];
86+
avgCpu = calculateAverage(history, 'cpu') ?? 0;
87+
avgMem = calculateAverage(history, 'mem') ?? 0;
88+
avgDisk = calculateAverage(history, 'disk') ?? 0;
89+
// I/O rates are already set from snapshot if dragging
90+
} else {
91+
PulseApp.state.clearDashboardHistoryEntry(guestUniqueId);
92+
// If not running, CPU/Mem/Disk also go to their defaults (0 or N/A)
93+
}
7494

7595
} else {
76-
PulseApp.state.clearDashboardHistoryEntry(guestUniqueId);
96+
// Original logic if not dragging or no snapshot
97+
if (guest.status === 'running' && metrics && metrics.current) {
98+
const currentDataPoint = {
99+
timestamp: Date.now(),
100+
...metrics.current
101+
};
102+
PulseApp.state.updateDashboardHistory(guestUniqueId, currentDataPoint);
103+
const history = PulseApp.state.getDashboardHistory()[guestUniqueId] || [];
104+
105+
avgCpu = calculateAverage(history, 'cpu') ?? 0;
106+
avgMem = calculateAverage(history, 'mem') ?? 0;
107+
avgDisk = calculateAverage(history, 'disk') ?? 0;
108+
avgDiskReadRate = calculateAverageRate(history, 'diskread');
109+
avgDiskWriteRate = calculateAverageRate(history, 'diskwrite');
110+
avgNetInRate = calculateAverageRate(history, 'netin');
111+
avgNetOutRate = calculateAverageRate(history, 'netout');
112+
} else {
113+
PulseApp.state.clearDashboardHistoryEntry(guestUniqueId);
114+
// Rates remain null (will be N/A), others default
115+
}
77116
}
117+
118+
avgMemoryPercent = (guest.maxmem > 0 && typeof avgMem === 'number') ? Math.round(avgMem / guest.maxmem * 100) : 'N/A';
119+
avgDiskPercent = (guest.maxdisk > 0 && typeof avgDisk === 'number') ? Math.round(avgDisk / guest.maxdisk * 100) : 'N/A';
78120

79121
const name = guest.name || `${guest.type === 'qemu' ? 'VM' : 'CT'} ${guest.vmid}`;
80122
const uptimeFormatted = PulseApp.utils.formatUptime(guest.uptime);
@@ -341,10 +383,32 @@ PulseApp.ui.dashboard = (() => {
341383
return row;
342384
}
343385

386+
function snapshotGuestMetricsForDrag() {
387+
guestMetricDragSnapshot = {}; // Clear previous snapshot
388+
const currentDashboardData = PulseApp.state.get('dashboardData') || [];
389+
currentDashboardData.forEach(guest => {
390+
if (guest && guest.uniqueId) {
391+
guestMetricDragSnapshot[guest.uniqueId] = {
392+
diskread: guest.diskread,
393+
diskwrite: guest.diskwrite,
394+
netin: guest.netin,
395+
netout: guest.netout
396+
// Optionally snapshot other metrics if they also show issues
397+
};
398+
}
399+
});
400+
}
401+
402+
function clearGuestMetricSnapshots() {
403+
guestMetricDragSnapshot = {};
404+
}
405+
344406
return {
345407
init,
346408
refreshDashboardData,
347409
updateDashboardTable,
348-
createGuestRow
410+
createGuestRow,
411+
snapshotGuestMetricsForDrag, // Export snapshot function
412+
clearGuestMetricSnapshots // Export clear function
349413
};
350414
})();

src/public/js/ui/thresholds.js

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ PulseApp.ui.thresholds = (() => {
77
let sliders = {};
88
let thresholdSelects = {};
99
let startLogButton = null;
10+
let isDraggingSlider = false;
1011

1112
function init() {
1213
thresholdRow = document.getElementById('threshold-slider-row');
@@ -67,8 +68,36 @@ PulseApp.ui.thresholds = (() => {
6768
});
6869

6970
const showTooltip = (event) => PulseApp.tooltips.updateSliderTooltip(event.target);
70-
sliderElement.addEventListener('mousedown', showTooltip);
71-
sliderElement.addEventListener('touchstart', showTooltip, { passive: true });
71+
sliderElement.addEventListener('mousedown', (event) => {
72+
showTooltip(event);
73+
isDraggingSlider = true;
74+
if (PulseApp.ui.dashboard && PulseApp.ui.dashboard.snapshotGuestMetricsForDrag) {
75+
PulseApp.ui.dashboard.snapshotGuestMetricsForDrag();
76+
}
77+
});
78+
document.addEventListener('mouseup', () => {
79+
if (isDraggingSlider) {
80+
isDraggingSlider = false;
81+
if (PulseApp.ui.dashboard && PulseApp.ui.dashboard.clearGuestMetricSnapshots) {
82+
PulseApp.ui.dashboard.clearGuestMetricSnapshots();
83+
}
84+
}
85+
});
86+
sliderElement.addEventListener('touchstart', (event) => {
87+
showTooltip(event);
88+
isDraggingSlider = true;
89+
if (PulseApp.ui.dashboard && PulseApp.ui.dashboard.snapshotGuestMetricsForDrag) {
90+
PulseApp.ui.dashboard.snapshotGuestMetricsForDrag();
91+
}
92+
}, { passive: true });
93+
document.addEventListener('touchend', () => {
94+
if (isDraggingSlider) {
95+
isDraggingSlider = false;
96+
if (PulseApp.ui.dashboard && PulseApp.ui.dashboard.clearGuestMetricSnapshots) {
97+
PulseApp.ui.dashboard.clearGuestMetricSnapshots();
98+
}
99+
}
100+
});
72101
} else {
73102
console.warn(`Slider element not found for type: ${type}`);
74103
}
@@ -197,9 +226,15 @@ PulseApp.ui.thresholds = (() => {
197226
startLogButton.classList.toggle('hidden', !isAnyFilterActive);
198227
}
199228

229+
// Getter for dashboard.js to check drag state
230+
function isThresholdDragInProgress() {
231+
return isDraggingSlider;
232+
}
233+
200234
return {
201235
init,
202236
resetThresholds,
203-
updateLogControlsVisibility
237+
updateLogControlsVisibility,
238+
isThresholdDragInProgress
204239
};
205240
})();

0 commit comments

Comments
 (0)