perf(browser-mem): Polling-Frequenz + Page-Hide-Cleanup (#183)

Drei Mitigations:

1) Admin-Queue-Polling 5s → 15s. Die Queue ändert sich pro Sekunde
   ohnehin nicht spürbar; senkt CPU + Network ohne UX-Verlust.

2) ``pagehide``-Listener in admin_queue.html, admin_stand.html und
   auswertungen.html. Zerstört Chart.js-Instanzen + cleart setInterval-
   Handles, sobald die Page in den Back/Forward-Cache geht oder
   geschlossen wird. Bisher hingen sie bis Browser-GC.

3) /auswertungen: zentrales Cleanup für ``_histChart``, alle ``_svCharts.*``
   und ``window._zeitreiheModalChart`` beim pagehide. Bisher zerstört
   nur die einzelnen Render-Funktionen ihre Vorgänger; beim Page-Verlassen
   blieben sie alle stehen.

Was nicht abgedeckt ist (für eventuelle Folge-Iteration mit konkretem
Heap-Snapshot):
- Lazy-Render lange News-/Drucksachen-Listen via IntersectionObserver
- Detaillierte Detached-DOM-Untersuchung pro Seite

Bestehende Maßnahmen (bereits da, hier nicht angefasst): chart.destroy()
vor jedem neuen Chart, sim.stop() in cluster.html, visibilitychange-
Pause für Polling.
This commit is contained in:
Dotty Dotter 2026-05-09 02:17:23 +02:00
parent 61c39eb820
commit ad73c824d3
3 changed files with 29 additions and 4 deletions

View File

@ -216,16 +216,22 @@ async function refresh() {
} }
} }
// Polling-Frequenz: 15s (vorher 5s) — die Queue ändert sich pro Sekunde
// ohnehin nicht spürbar, das senkt CPU + Network ohne UX-Verlust (#183).
var _queuePollMs = 15000;
refresh(); refresh();
let _queueInterval = setInterval(refresh, 5000); let _queueInterval = setInterval(refresh, _queuePollMs);
// Pause-Polling wenn Tab versteckt (#183). // Pause-Polling wenn Tab versteckt + beim Verlassen der Page (#183).
document.addEventListener('visibilitychange', function () { document.addEventListener('visibilitychange', function () {
if (document.hidden) { if (document.hidden) {
if (_queueInterval) { clearInterval(_queueInterval); _queueInterval = null; } if (_queueInterval) { clearInterval(_queueInterval); _queueInterval = null; }
} else if (!_queueInterval) { } else if (!_queueInterval) {
refresh(); refresh();
_queueInterval = setInterval(refresh, 5000); _queueInterval = setInterval(refresh, _queuePollMs);
} }
}); });
window.addEventListener('pagehide', function () {
if (_queueInterval) { clearInterval(_queueInterval); _queueInterval = null; }
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -330,7 +330,7 @@ async function loadStand() {
loadStand(); loadStand();
let _standInterval = setInterval(loadStand, 30000); let _standInterval = setInterval(loadStand, 30000);
// Pause-Polling wenn Tab versteckt (#183). // Pause-Polling wenn Tab versteckt + beim Verlassen der Page (#183).
document.addEventListener('visibilitychange', function () { document.addEventListener('visibilitychange', function () {
if (document.hidden) { if (document.hidden) {
if (_standInterval) { clearInterval(_standInterval); _standInterval = null; } if (_standInterval) { clearInterval(_standInterval); _standInterval = null; }
@ -339,5 +339,8 @@ document.addEventListener('visibilitychange', function () {
_standInterval = setInterval(loadStand, 30000); _standInterval = setInterval(loadStand, 30000);
} }
}); });
window.addEventListener('pagehide', function () {
if (_standInterval) { clearInterval(_standInterval); _standInterval = null; }
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -424,6 +424,22 @@ let _histChart = null;
let _svCharts = { index: null, heuchelei: null, empfehlung: null, crossBl: null, zeitreihe: null }; let _svCharts = { index: null, heuchelei: null, empfehlung: null, crossBl: null, zeitreihe: null };
let _svMatrixAxis = 'werte'; // 'werte' or 'gruppen' let _svMatrixAxis = 'werte'; // 'werte' or 'gruppen'
// Cleanup beim Verlassen der Page (#183) — alle Charts zerstören.
// Verhindert hängende Chart.js-Instanzen + ihre Canvas-Contexts in Memory,
// wenn der Browser die Page nur cachet (zB. Back/Forward-Cache).
window.addEventListener('pagehide', function () {
try {
if (_histChart) { _histChart.destroy(); _histChart = null; }
Object.keys(_svCharts).forEach(function (k) {
if (_svCharts[k]) { try { _svCharts[k].destroy(); } catch (_) {} _svCharts[k] = null; }
});
if (window._zeitreiheModalChart) {
try { window._zeitreiheModalChart.destroy(); } catch (_) {}
window._zeitreiheModalChart = null;
}
} catch (_) {}
});
function setMatrixAxis(axis) { function setMatrixAxis(axis) {
_svMatrixAxis = axis; _svMatrixAxis = axis;
const werteBtn = document.getElementById('sv-axis-werte'); const werteBtn = document.getElementById('sv-axis-werte');