realtime.py 2.8 KB

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