Show both fps_current and fps_actual in WebUI charts and labels
- Charts: blue filled area for fps_actual (rolling avg), green line for fps_current (real-time sends/sec) - Labels: fps_current/fps_target as primary, avg fps_actual as secondary - Track fps_current in metrics history for dashboard chart preload - Applied to both LED targets page and dashboard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,32 +42,49 @@ document.addEventListener('languageChanged', () => {
|
||||
|
||||
// --- FPS sparkline history and chart instances for target cards ---
|
||||
const _TARGET_MAX_FPS_SAMPLES = 30;
|
||||
const _targetFpsHistory = {};
|
||||
const _targetFpsHistory = {}; // fps_actual (rolling avg)
|
||||
const _targetFpsCurrentHistory = {}; // fps_current (sends/sec)
|
||||
const _targetFpsCharts = {};
|
||||
|
||||
function _pushTargetFps(targetId, value) {
|
||||
function _pushTargetFps(targetId, actual, current) {
|
||||
if (!_targetFpsHistory[targetId]) _targetFpsHistory[targetId] = [];
|
||||
const h = _targetFpsHistory[targetId];
|
||||
h.push(value);
|
||||
h.push(actual);
|
||||
if (h.length > _TARGET_MAX_FPS_SAMPLES) h.shift();
|
||||
|
||||
if (!_targetFpsCurrentHistory[targetId]) _targetFpsCurrentHistory[targetId] = [];
|
||||
const c = _targetFpsCurrentHistory[targetId];
|
||||
c.push(current);
|
||||
if (c.length > _TARGET_MAX_FPS_SAMPLES) c.shift();
|
||||
}
|
||||
|
||||
function _createTargetFpsChart(canvasId, history, fpsTarget, maxHwFps) {
|
||||
function _createTargetFpsChart(canvasId, actualHistory, currentHistory, fpsTarget, maxHwFps) {
|
||||
const canvas = document.getElementById(canvasId);
|
||||
if (!canvas) return null;
|
||||
const datasets = [{
|
||||
data: [...history],
|
||||
borderColor: '#2196F3',
|
||||
backgroundColor: 'rgba(33,150,243,0.12)',
|
||||
borderWidth: 1.5,
|
||||
tension: 0.3,
|
||||
fill: true,
|
||||
pointRadius: 0,
|
||||
}];
|
||||
const labels = actualHistory.map(() => '');
|
||||
const datasets = [
|
||||
{
|
||||
data: [...actualHistory],
|
||||
borderColor: '#2196F3',
|
||||
backgroundColor: 'rgba(33,150,243,0.12)',
|
||||
borderWidth: 1.5,
|
||||
tension: 0.3,
|
||||
fill: true,
|
||||
pointRadius: 0,
|
||||
},
|
||||
{
|
||||
data: [...currentHistory],
|
||||
borderColor: '#4CAF50',
|
||||
borderWidth: 1.5,
|
||||
tension: 0.3,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
},
|
||||
];
|
||||
// Flat line showing hardware max FPS
|
||||
if (maxHwFps && maxHwFps < fpsTarget * 1.15) {
|
||||
datasets.push({
|
||||
data: history.map(() => maxHwFps),
|
||||
data: actualHistory.map(() => maxHwFps),
|
||||
borderColor: 'rgba(255,152,0,0.5)',
|
||||
borderWidth: 1,
|
||||
borderDash: [4, 3],
|
||||
@@ -77,7 +94,7 @@ function _createTargetFpsChart(canvasId, history, fpsTarget, maxHwFps) {
|
||||
}
|
||||
return new Chart(canvas, {
|
||||
type: 'line',
|
||||
data: { labels: history.map(() => ''), datasets },
|
||||
data: { labels, datasets },
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
animation: false,
|
||||
@@ -93,9 +110,11 @@ function _createTargetFpsChart(canvasId, history, fpsTarget, maxHwFps) {
|
||||
function _updateTargetFpsChart(targetId, fpsTarget) {
|
||||
const chart = _targetFpsCharts[targetId];
|
||||
if (!chart) return;
|
||||
const history = _targetFpsHistory[targetId] || [];
|
||||
chart.data.labels = history.map(() => '');
|
||||
chart.data.datasets[0].data = [...history];
|
||||
const actualH = _targetFpsHistory[targetId] || [];
|
||||
const currentH = _targetFpsCurrentHistory[targetId] || [];
|
||||
chart.data.labels = actualH.map(() => '');
|
||||
chart.data.datasets[0].data = [...actualH];
|
||||
chart.data.datasets[1].data = [...currentH];
|
||||
chart.options.scales.y.max = fpsTarget * 1.15;
|
||||
chart.update('none');
|
||||
}
|
||||
@@ -630,15 +649,16 @@ export async function loadTargetsTab() {
|
||||
if (target.state && target.state.processing) {
|
||||
runningIds.add(target.id);
|
||||
if (target.state.fps_actual != null) {
|
||||
_pushTargetFps(target.id, target.state.fps_actual);
|
||||
_pushTargetFps(target.id, target.state.fps_actual, target.state.fps_current ?? 0);
|
||||
}
|
||||
// Create chart if it doesn't exist (new or replaced card)
|
||||
if (!_targetFpsCharts[target.id]) {
|
||||
const history = _targetFpsHistory[target.id] || [];
|
||||
const actualH = _targetFpsHistory[target.id] || [];
|
||||
const currentH = _targetFpsCurrentHistory[target.id] || [];
|
||||
const fpsTarget = target.state.fps_target || 30;
|
||||
const device = devices.find(d => d.id === target.device_id);
|
||||
const maxHwFps = device ? _computeMaxFps(device.baud_rate, device.led_count, device.device_type) : null;
|
||||
const chart = _createTargetFpsChart(`target-fps-${target.id}`, history, fpsTarget, maxHwFps);
|
||||
const chart = _createTargetFpsChart(`target-fps-${target.id}`, actualH, currentH, fpsTarget, maxHwFps);
|
||||
if (chart) _targetFpsCharts[target.id] = chart;
|
||||
} else {
|
||||
// Chart survived reconcile — just update data
|
||||
@@ -648,7 +668,7 @@ export async function loadTargetsTab() {
|
||||
});
|
||||
// Clean up history and charts for targets no longer running
|
||||
Object.keys(_targetFpsHistory).forEach(id => {
|
||||
if (!runningIds.has(id)) delete _targetFpsHistory[id];
|
||||
if (!runningIds.has(id)) { delete _targetFpsHistory[id]; delete _targetFpsCurrentHistory[id]; }
|
||||
});
|
||||
Object.keys(_targetFpsCharts).forEach(id => {
|
||||
if (!runningIds.has(id)) {
|
||||
@@ -715,7 +735,8 @@ function _patchTargetMetrics(target) {
|
||||
const metrics = target.metrics || {};
|
||||
|
||||
const fps = card.querySelector('[data-tm="fps"]');
|
||||
if (fps) fps.innerHTML = `${state.fps_actual?.toFixed(1) || '0.0'}<span class="target-fps-target">/${state.fps_target || 0}</span>`;
|
||||
if (fps) fps.innerHTML = `${state.fps_current ?? 0}<span class="target-fps-target">/${state.fps_target || 0}</span>`
|
||||
+ `<span class="target-fps-avg">avg ${state.fps_actual?.toFixed(1) || '0.0'}</span>`;
|
||||
|
||||
const timing = card.querySelector('[data-tm="timing"]');
|
||||
if (timing && state.timing_total_ms != null) timing.innerHTML = _buildLedTimingHTML(state);
|
||||
|
||||
Reference in New Issue
Block a user