realtime.py 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. """Utilities for reading real time clocks and keeping soft real time constraints."""
  2. import gc
  3. import os
  4. import time
  5. from collections import deque
  6. from setproctitle import getproctitle
  7. from openpilot.system.hardware import PC
  8. # time step for each process
  9. DT_CTRL = 0.01 # controlsd
  10. DT_MDL = 0.05 # model
  11. DT_HW = 0.5 # hardwared and manager
  12. DT_DMON = 0.05 # driver monitoring
  13. class Priority:
  14. # CORE 2
  15. # - modeld = 55
  16. # - camerad = 54
  17. CTRL_LOW = 51 # plannerd & radard
  18. # CORE 3
  19. # - pandad = 55
  20. CTRL_HIGH = 53
  21. def set_realtime_priority(level: int) -> None:
  22. if not PC:
  23. os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level))
  24. def set_core_affinity(cores: list[int]) -> None:
  25. if not PC:
  26. os.sched_setaffinity(0, cores)
  27. def config_realtime_process(cores: int | list[int], priority: int) -> None:
  28. gc.disable()
  29. set_realtime_priority(priority)
  30. c = cores if isinstance(cores, list) else [cores, ]
  31. set_core_affinity(c)
  32. class Ratekeeper:
  33. def __init__(self, rate: float, print_delay_threshold: float | None = 0.0) -> None:
  34. """Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative."""
  35. self._interval = 1. / rate
  36. self._next_frame_time = time.monotonic() + self._interval
  37. self._print_delay_threshold = print_delay_threshold
  38. self._frame = 0
  39. self._remaining = 0.0
  40. self._process_name = getproctitle()
  41. self._dts = deque([self._interval], maxlen=100)
  42. self._last_monitor_time = time.monotonic()
  43. @property
  44. def frame(self) -> int:
  45. return self._frame
  46. @property
  47. def remaining(self) -> float:
  48. return self._remaining
  49. @property
  50. def lagging(self) -> bool:
  51. avg_dt = sum(self._dts) / len(self._dts)
  52. expected_dt = self._interval * (1 / 0.9)
  53. return avg_dt > expected_dt
  54. # Maintain loop rate by calling this at the end of each loop
  55. def keep_time(self) -> bool:
  56. lagged = self.monitor_time()
  57. if self._remaining > 0:
  58. time.sleep(self._remaining)
  59. return lagged
  60. # Monitors the cumulative lag, but does not enforce a rate
  61. def monitor_time(self) -> bool:
  62. prev = self._last_monitor_time
  63. self._last_monitor_time = time.monotonic()
  64. self._dts.append(self._last_monitor_time - prev)
  65. lagged = False
  66. remaining = self._next_frame_time - time.monotonic()
  67. self._next_frame_time += self._interval
  68. if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold:
  69. print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms")
  70. lagged = True
  71. self._frame += 1
  72. self._remaining = remaining
  73. return lagged