diff --git a/android/app/src/main/java/com/fabledsword/minstrel/diagnostics/TransportFlapDetector.kt b/android/app/src/main/java/com/fabledsword/minstrel/diagnostics/TransportFlapDetector.kt index 2297914a..dfcda283 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/diagnostics/TransportFlapDetector.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/diagnostics/TransportFlapDetector.kt @@ -33,20 +33,30 @@ class TransportFlapDetector( */ fun onChange(observation: TransportObservation): List? { recent.addLast(observation) - while (recent.isNotEmpty() && - observation.atElapsedMs - recent.first().atElapsedMs > windowMs - ) { - recent.removeFirst() - } - if (recent.size < minChanges) return null - // One episode, one summary. A sustained fault would otherwise emit a - // summary per reading and bury the per-change events underneath them. - val since = lastSummaryAtMs - if (since != null && observation.atElapsedMs - since < summaryCooldownMs) return null + dropReadingsOlderThan(observation.atElapsedMs) + if (!isEpisode(observation.atElapsedMs)) return null lastSummaryAtMs = observation.atElapsedMs return recent.toList() } + private fun dropReadingsOlderThan(nowMs: Long) { + while (recent.isNotEmpty() && nowMs - recent.first().atElapsedMs > windowMs) { + recent.removeFirst() + } + } + + /** + * Enough changes packed together, and far enough from the last thing we + * wrote down. The cooldown is what keeps one episode to one summary: a + * sustained fault produces a change every poll, and a summary per reading + * would bury the per-change events underneath them. + */ + private fun isEpisode(nowMs: Long): Boolean { + val since = lastSummaryAtMs + val cooled = since == null || nowMs - since >= summaryCooldownMs + return recent.size >= minChanges && cooled + } + /** Forget everything — call when the route changes or casting ends. */ fun reset() { recent.clear()