common: add MovingAverage class for real-time windowed average calculation (#34569)

* add MovingAverage class for real-time windowed average calculation

* move to util.py
notmaster
Dean Lee 2 months ago committed by GitHub
parent c3c878908d
commit 61bec65f32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 27
      cereal/messaging/__init__.py
  2. 11
      common/realtime.py
  3. 24
      common/util.py

@ -9,11 +9,11 @@ import os
import capnp import capnp
import time import time
from typing import Optional, List, Union, Dict, Deque from typing import Optional, List, Union, Dict
from collections import deque
from cereal import log from cereal import log
from cereal.services import SERVICE_LIST from cereal.services import SERVICE_LIST
from openpilot.common.util import MovingAverage
NO_TRAVERSAL_LIMIT = 2**64-1 NO_TRAVERSAL_LIMIT = 2**64-1
@ -108,10 +108,8 @@ class FrequencyTracker:
self.min_freq = min_freq * 0.8 self.min_freq = min_freq * 0.8
self.max_freq = max_freq * 1.2 self.max_freq = max_freq * 1.2
self.recv_dts: Deque[float] = deque(maxlen=int(10 * freq)) self.avg_dt = MovingAverage(int(10 * freq))
self.recv_dts_sum = 0.0 self.recent_avg_dt = MovingAverage(int(freq))
self.recent_recv_dts: Deque[float] = deque(maxlen=int(freq))
self.recent_recv_dts_sum = 0.0
self.prev_time = 0.0 self.prev_time = 0.0
def record_recv_time(self, cur_time: float) -> None: def record_recv_time(self, cur_time: float) -> None:
@ -119,28 +117,21 @@ class FrequencyTracker:
if self.prev_time > 1e-5: if self.prev_time > 1e-5:
dt = cur_time - self.prev_time dt = cur_time - self.prev_time
if len(self.recv_dts) == self.recv_dts.maxlen: self.avg_dt.add_value(dt)
self.recv_dts_sum -= self.recv_dts[0] self.recent_avg_dt.add_value(dt)
self.recv_dts.append(dt)
self.recv_dts_sum += dt
if len(self.recent_recv_dts) == self.recent_recv_dts.maxlen:
self.recent_recv_dts_sum -= self.recent_recv_dts[0]
self.recent_recv_dts.append(dt)
self.recent_recv_dts_sum += dt
self.prev_time = cur_time self.prev_time = cur_time
@property @property
def valid(self) -> bool: def valid(self) -> bool:
if not self.recv_dts: if self.avg_dt.count == 0:
return False return False
avg_freq = len(self.recv_dts) / self.recv_dts_sum avg_freq = 1.0 / self.avg_dt.get_average()
if self.min_freq <= avg_freq <= self.max_freq: if self.min_freq <= avg_freq <= self.max_freq:
return True return True
avg_freq_recent = len(self.recent_recv_dts) / self.recent_recv_dts_sum avg_freq_recent = 1.0 / self.recent_avg_dt.get_average()
return self.min_freq <= avg_freq_recent <= self.max_freq return self.min_freq <= avg_freq_recent <= self.max_freq

@ -2,10 +2,10 @@
import gc import gc
import os import os
import time import time
from collections import deque
from setproctitle import getproctitle from setproctitle import getproctitle
from openpilot.common.util import MovingAverage
from openpilot.system.hardware import PC from openpilot.system.hardware import PC
@ -48,10 +48,12 @@ class Ratekeeper:
self._frame = 0 self._frame = 0
self._remaining = 0.0 self._remaining = 0.0
self._process_name = getproctitle() self._process_name = getproctitle()
self._dts = deque([self._interval], maxlen=100)
self._last_monitor_time = -1. self._last_monitor_time = -1.
self._next_frame_time = -1. self._next_frame_time = -1.
self.avg_dt = MovingAverage(100)
self.avg_dt.add_value(self._interval)
@property @property
def frame(self) -> int: def frame(self) -> int:
return self._frame return self._frame
@ -62,9 +64,8 @@ class Ratekeeper:
@property @property
def lagging(self) -> bool: def lagging(self) -> bool:
avg_dt = sum(self._dts) / len(self._dts)
expected_dt = self._interval * (1 / 0.9) expected_dt = self._interval * (1 / 0.9)
return avg_dt > expected_dt return self.avg_dt.get_average() > expected_dt
# Maintain loop rate by calling this at the end of each loop # Maintain loop rate by calling this at the end of each loop
def keep_time(self) -> bool: def keep_time(self) -> bool:
@ -81,7 +82,7 @@ class Ratekeeper:
prev = self._last_monitor_time prev = self._last_monitor_time
self._last_monitor_time = time.monotonic() self._last_monitor_time = time.monotonic()
self._dts.append(self._last_monitor_time - prev) self.avg_dt.add_value(self._last_monitor_time - prev)
lagged = False lagged = False
remaining = self._next_frame_time - time.monotonic() remaining = self._next_frame_time - time.monotonic()

@ -0,0 +1,24 @@
class MovingAverage:
def __init__(self, window_size: int):
self.window_size: int = window_size
self.buffer: list[float] = [0.0] * window_size
self.index: int = 0
self.count: int = 0
self.sum: float = 0.0
def add_value(self, new_value: float):
# Update the sum: subtract the value being replaced and add the new value
self.sum -= self.buffer[self.index]
self.buffer[self.index] = new_value
self.sum += new_value
# Update the index in a circular manner
self.index = (self.index + 1) % self.window_size
# Track the number of added values (for partial windows)
self.count = min(self.count + 1, self.window_size)
def get_average(self) -> float:
if self.count == 0:
return float('nan')
return self.sum / self.count
Loading…
Cancel
Save