|  |  |  | """Utilities for reading real time clocks and keeping soft real time constraints."""
 | 
					
						
							|  |  |  | import os
 | 
					
						
							|  |  |  | import time
 | 
					
						
							|  |  |  | import platform
 | 
					
						
							|  |  |  | import subprocess
 | 
					
						
							|  |  |  | import multiprocessing
 | 
					
						
							|  |  |  | from cffi import FFI
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from common.android import ANDROID
 | 
					
						
							|  |  |  | from common.common_pyx import sec_since_boot  # pylint: disable=no-name-in-module, import-error
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # time step for each process
 | 
					
						
							|  |  |  | DT_CTRL = 0.01  # controlsd
 | 
					
						
							|  |  |  | DT_MDL = 0.05  # model
 | 
					
						
							|  |  |  | DT_DMON = 0.1  # driver monitoring
 | 
					
						
							|  |  |  | DT_TRML = 0.5  # thermald and manager
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ffi = FFI()
 | 
					
						
							|  |  |  | ffi.cdef("long syscall(long number, ...);")
 | 
					
						
							|  |  |  | libc = ffi.dlopen(None)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _get_tid():
 | 
					
						
							|  |  |  |   if platform.machine() == "x86_64":
 | 
					
						
							|  |  |  |     NR_gettid = 186
 | 
					
						
							|  |  |  |   elif platform.machine() == "aarch64":
 | 
					
						
							|  |  |  |     NR_gettid = 178
 | 
					
						
							|  |  |  |   else:
 | 
					
						
							|  |  |  |     raise NotImplementedError
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return libc.syscall(NR_gettid)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def set_realtime_priority(level):
 | 
					
						
							|  |  |  |   if os.getuid() != 0:
 | 
					
						
							|  |  |  |     print("not setting priority, not root")
 | 
					
						
							|  |  |  |     return
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return subprocess.call(['chrt', '-f', '-p', str(level), str(_get_tid())])
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def set_core_affinity(core):
 | 
					
						
							|  |  |  |   if os.getuid() != 0:
 | 
					
						
							|  |  |  |     print("not setting affinity, not root")
 | 
					
						
							|  |  |  |     return
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if ANDROID:
 | 
					
						
							|  |  |  |     return subprocess.call(['taskset', '-p', str(core), str(_get_tid())])
 | 
					
						
							|  |  |  |   else:
 | 
					
						
							|  |  |  |     return -1
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Ratekeeper():
 | 
					
						
							|  |  |  |   def __init__(self, rate, print_delay_threshold=0.):
 | 
					
						
							|  |  |  |     """Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative."""
 | 
					
						
							|  |  |  |     self._interval = 1. / rate
 | 
					
						
							|  |  |  |     self._next_frame_time = sec_since_boot() + self._interval
 | 
					
						
							|  |  |  |     self._print_delay_threshold = print_delay_threshold
 | 
					
						
							|  |  |  |     self._frame = 0
 | 
					
						
							|  |  |  |     self._remaining = 0
 | 
					
						
							|  |  |  |     self._process_name = multiprocessing.current_process().name
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property
 | 
					
						
							|  |  |  |   def frame(self):
 | 
					
						
							|  |  |  |     return self._frame
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property
 | 
					
						
							|  |  |  |   def remaining(self):
 | 
					
						
							|  |  |  |     return self._remaining
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Maintain loop rate by calling this at the end of each loop
 | 
					
						
							|  |  |  |   def keep_time(self):
 | 
					
						
							|  |  |  |     lagged = self.monitor_time()
 | 
					
						
							|  |  |  |     if self._remaining > 0:
 | 
					
						
							|  |  |  |       time.sleep(self._remaining)
 | 
					
						
							|  |  |  |     return lagged
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # this only monitor the cumulative lag, but does not enforce a rate
 | 
					
						
							|  |  |  |   def monitor_time(self):
 | 
					
						
							|  |  |  |     lagged = False
 | 
					
						
							|  |  |  |     remaining = self._next_frame_time - sec_since_boot()
 | 
					
						
							|  |  |  |     self._next_frame_time += self._interval
 | 
					
						
							|  |  |  |     if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold:
 | 
					
						
							|  |  |  |       print("%s lagging by %.2f ms" % (self._process_name, -remaining * 1000))
 | 
					
						
							|  |  |  |       lagged = True
 | 
					
						
							|  |  |  |     self._frame += 1
 | 
					
						
							|  |  |  |     self._remaining = remaining
 | 
					
						
							|  |  |  |     return lagged
 |