You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					228 lines
				
				6.1 KiB
			
		
		
			
		
	
	
					228 lines
				
				6.1 KiB
			| 
											2 months ago
										 | #!/usr/bin/env python3
 | ||
|  | import os
 | ||
|  | from typing import NoReturn, TypedDict
 | ||
|  | 
 | ||
|  | from cereal import messaging
 | ||
|  | from openpilot.common.realtime import Ratekeeper
 | ||
|  | from openpilot.common.swaglog import cloudlog
 | ||
|  | 
 | ||
|  | JIFFY = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
 | ||
|  | PAGE_SIZE = os.sysconf(os.sysconf_names['SC_PAGE_SIZE'])
 | ||
|  | 
 | ||
|  | 
 | ||
|  | def _cpu_times() -> list[dict[str, float]]:
 | ||
|  |   cpu_times: list[dict[str, float]] = []
 | ||
|  |   try:
 | ||
|  |     with open('/proc/stat') as f:
 | ||
|  |       lines = f.readlines()[1:]
 | ||
|  |     for line in lines:
 | ||
|  |       if not line.startswith('cpu') or len(line) < 4 or not line[3].isdigit():
 | ||
|  |         break
 | ||
|  |       parts = line.split()
 | ||
|  |       cpu_times.append({
 | ||
|  |         'cpuNum': int(parts[0][3:]),
 | ||
|  |         'user': float(parts[1]) / JIFFY,
 | ||
|  |         'nice': float(parts[2]) / JIFFY,
 | ||
|  |         'system': float(parts[3]) / JIFFY,
 | ||
|  |         'idle': float(parts[4]) / JIFFY,
 | ||
|  |         'iowait': float(parts[5]) / JIFFY,
 | ||
|  |         'irq': float(parts[6]) / JIFFY,
 | ||
|  |         'softirq': float(parts[7]) / JIFFY,
 | ||
|  |       })
 | ||
|  |   except Exception:
 | ||
|  |     cloudlog.exception("failed to read /proc/stat")
 | ||
|  |   return cpu_times
 | ||
|  | 
 | ||
|  | 
 | ||
|  | def _mem_info() -> dict[str, int]:
 | ||
|  |   keys = ["MemTotal:", "MemFree:", "MemAvailable:", "Buffers:", "Cached:", "Active:", "Inactive:", "Shmem:"]
 | ||
|  |   info: dict[str, int] = dict.fromkeys(keys, 0)
 | ||
|  |   try:
 | ||
|  |     with open('/proc/meminfo') as f:
 | ||
|  |       for line in f:
 | ||
|  |         parts = line.split()
 | ||
|  |         if parts and parts[0] in info:
 | ||
|  |           info[parts[0]] = int(parts[1]) * 1024
 | ||
|  |   except Exception:
 | ||
|  |     cloudlog.exception("failed to read /proc/meminfo")
 | ||
|  |   return info
 | ||
|  | 
 | ||
|  | 
 | ||
|  | _STAT_POS = {
 | ||
|  |   'pid': 1,
 | ||
|  |   'state': 3,
 | ||
|  |   'ppid': 4,
 | ||
|  |   'utime': 14,
 | ||
|  |   'stime': 15,
 | ||
|  |   'cutime': 16,
 | ||
|  |   'cstime': 17,
 | ||
|  |   'priority': 18,
 | ||
|  |   'nice': 19,
 | ||
|  |   'num_threads': 20,
 | ||
|  |   'starttime': 22,
 | ||
|  |   'vsize': 23,
 | ||
|  |   'rss': 24,
 | ||
|  |   'processor': 39,
 | ||
|  | }
 | ||
|  | 
 | ||
|  | class ProcStat(TypedDict):
 | ||
|  |   name: str
 | ||
|  |   pid: int
 | ||
|  |   state: str
 | ||
|  |   ppid: int
 | ||
|  |   utime: int
 | ||
|  |   stime: int
 | ||
|  |   cutime: int
 | ||
|  |   cstime: int
 | ||
|  |   priority: int
 | ||
|  |   nice: int
 | ||
|  |   num_threads: int
 | ||
|  |   starttime: int
 | ||
|  |   vms: int
 | ||
|  |   rss: int
 | ||
|  |   processor: int
 | ||
|  | 
 | ||
|  | 
 | ||
|  | def _parse_proc_stat(stat: str) -> ProcStat | None:
 | ||
|  |   open_paren = stat.find('(')
 | ||
|  |   close_paren = stat.rfind(')')
 | ||
|  |   if open_paren == -1 or close_paren == -1 or open_paren > close_paren:
 | ||
|  |     return None
 | ||
|  |   name = stat[open_paren + 1:close_paren]
 | ||
|  |   stat = stat[:open_paren] + stat[open_paren:close_paren].replace(' ', '_') + stat[close_paren:]
 | ||
|  |   parts = stat.split()
 | ||
|  |   if len(parts) < 52:
 | ||
|  |     return None
 | ||
|  |   try:
 | ||
|  |     return {
 | ||
|  |       'name': name,
 | ||
|  |       'pid': int(parts[_STAT_POS['pid'] - 1]),
 | ||
|  |       'state': parts[_STAT_POS['state'] - 1][0],
 | ||
|  |       'ppid': int(parts[_STAT_POS['ppid'] - 1]),
 | ||
|  |       'utime': int(parts[_STAT_POS['utime'] - 1]),
 | ||
|  |       'stime': int(parts[_STAT_POS['stime'] - 1]),
 | ||
|  |       'cutime': int(parts[_STAT_POS['cutime'] - 1]),
 | ||
|  |       'cstime': int(parts[_STAT_POS['cstime'] - 1]),
 | ||
|  |       'priority': int(parts[_STAT_POS['priority'] - 1]),
 | ||
|  |       'nice': int(parts[_STAT_POS['nice'] - 1]),
 | ||
|  |       'num_threads': int(parts[_STAT_POS['num_threads'] - 1]),
 | ||
|  |       'starttime': int(parts[_STAT_POS['starttime'] - 1]),
 | ||
|  |       'vms': int(parts[_STAT_POS['vsize'] - 1]),
 | ||
|  |       'rss': int(parts[_STAT_POS['rss'] - 1]),
 | ||
|  |       'processor': int(parts[_STAT_POS['processor'] - 1]),
 | ||
|  |     }
 | ||
|  |   except Exception:
 | ||
|  |     cloudlog.exception("failed to parse /proc/<pid>/stat")
 | ||
|  |     return None
 | ||
|  | 
 | ||
|  | class ProcExtra(TypedDict):
 | ||
|  |   pid: int
 | ||
|  |   name: str
 | ||
|  |   exe: str
 | ||
|  |   cmdline: list[str]
 | ||
|  | 
 | ||
|  | 
 | ||
|  | _proc_cache: dict[int, ProcExtra] = {}
 | ||
|  | 
 | ||
|  | 
 | ||
|  | def _get_proc_extra(pid: int, name: str) -> ProcExtra:
 | ||
|  |   cache: ProcExtra | None = _proc_cache.get(pid)
 | ||
|  |   if cache is None or cache.get('name') != name:
 | ||
|  |     exe = ''
 | ||
|  |     cmdline: list[str] = []
 | ||
|  |     try:
 | ||
|  |       exe = os.readlink(f'/proc/{pid}/exe')
 | ||
|  |     except OSError:
 | ||
|  |       pass
 | ||
|  |     try:
 | ||
|  |       with open(f'/proc/{pid}/cmdline', 'rb') as f:
 | ||
|  |         cmdline = [c.decode('utf-8', errors='replace') for c in f.read().split(b'\0') if c]
 | ||
|  |     except OSError:
 | ||
|  |       pass
 | ||
|  |     cache = {'pid': pid, 'name': name, 'exe': exe, 'cmdline': cmdline}
 | ||
|  |     _proc_cache[pid] = cache
 | ||
|  |   return cache
 | ||
|  | 
 | ||
|  | 
 | ||
|  | def _procs() -> list[ProcStat]:
 | ||
|  |   stats: list[ProcStat] = []
 | ||
|  |   for pid_str in os.listdir('/proc'):
 | ||
|  |     if not pid_str.isdigit():
 | ||
|  |       continue
 | ||
|  |     try:
 | ||
|  |       with open(f'/proc/{pid_str}/stat') as f:
 | ||
|  |         stat = f.read()
 | ||
|  |       parsed = _parse_proc_stat(stat)
 | ||
|  |       if parsed is not None:
 | ||
|  |         stats.append(parsed)
 | ||
|  |     except OSError:
 | ||
|  |       continue
 | ||
|  |   return stats
 | ||
|  | 
 | ||
|  | 
 | ||
|  | def build_proc_log_message(msg) -> None:
 | ||
|  |   pl = msg.procLog
 | ||
|  | 
 | ||
|  |   procs = _procs()
 | ||
|  |   l = pl.init('procs', len(procs))
 | ||
|  |   for i, r in enumerate(procs):
 | ||
|  |     proc = l[i]
 | ||
|  |     proc.pid = r['pid']
 | ||
|  |     proc.state = ord(r['state'][0])
 | ||
|  |     proc.ppid = r['ppid']
 | ||
|  |     proc.cpuUser = r['utime'] / JIFFY
 | ||
|  |     proc.cpuSystem = r['stime'] / JIFFY
 | ||
|  |     proc.cpuChildrenUser = r['cutime'] / JIFFY
 | ||
|  |     proc.cpuChildrenSystem = r['cstime'] / JIFFY
 | ||
|  |     proc.priority = r['priority']
 | ||
|  |     proc.nice = r['nice']
 | ||
|  |     proc.numThreads = r['num_threads']
 | ||
|  |     proc.startTime = r['starttime'] / JIFFY
 | ||
|  |     proc.memVms = r['vms']
 | ||
|  |     proc.memRss = r['rss'] * PAGE_SIZE
 | ||
|  |     proc.processor = r['processor']
 | ||
|  |     proc.name = r['name']
 | ||
|  | 
 | ||
|  |     extra = _get_proc_extra(r['pid'], r['name'])
 | ||
|  |     proc.exe = extra['exe']
 | ||
|  |     cmdline = proc.init('cmdline', len(extra['cmdline']))
 | ||
|  |     for j, arg in enumerate(extra['cmdline']):
 | ||
|  |       cmdline[j] = arg
 | ||
|  | 
 | ||
|  |   cpu_times = _cpu_times()
 | ||
|  |   cpu_list = pl.init('cpuTimes', len(cpu_times))
 | ||
|  |   for i, ct in enumerate(cpu_times):
 | ||
|  |     cpu = cpu_list[i]
 | ||
|  |     cpu.cpuNum = ct['cpuNum']
 | ||
|  |     cpu.user = ct['user']
 | ||
|  |     cpu.nice = ct['nice']
 | ||
|  |     cpu.system = ct['system']
 | ||
|  |     cpu.idle = ct['idle']
 | ||
|  |     cpu.iowait = ct['iowait']
 | ||
|  |     cpu.irq = ct['irq']
 | ||
|  |     cpu.softirq = ct['softirq']
 | ||
|  | 
 | ||
|  |   mem_info = _mem_info()
 | ||
|  |   pl.mem.total = mem_info["MemTotal:"]
 | ||
|  |   pl.mem.free = mem_info["MemFree:"]
 | ||
|  |   pl.mem.available = mem_info["MemAvailable:"]
 | ||
|  |   pl.mem.buffers = mem_info["Buffers:"]
 | ||
|  |   pl.mem.cached = mem_info["Cached:"]
 | ||
|  |   pl.mem.active = mem_info["Active:"]
 | ||
|  |   pl.mem.inactive = mem_info["Inactive:"]
 | ||
|  |   pl.mem.shared = mem_info["Shmem:"]
 | ||
|  | 
 | ||
|  | 
 | ||
|  | def main() -> NoReturn:
 | ||
|  |   pm = messaging.PubMaster(['procLog'])
 | ||
|  |   rk = Ratekeeper(0.5)
 | ||
|  |   while True:
 | ||
|  |     msg = messaging.new_message('procLog', valid=True)
 | ||
|  |     build_proc_log_message(msg)
 | ||
|  |     pm.send('procLog', msg)
 | ||
|  |     rk.keep_time()
 | ||
|  | 
 | ||
|  | 
 | ||
|  | if __name__ == '__main__':
 | ||
|  |   main()
 |