#!/usr/bin/env python3
import argparse
import capnp
from collections import defaultdict

from cereal.messaging import SubMaster
from openpilot.common.numpy_fast import mean

def cputime_total(ct):
  return ct.user + ct.nice + ct.system + ct.idle + ct.iowait + ct.irq + ct.softirq


def cputime_busy(ct):
  return ct.user + ct.nice + ct.system + ct.irq + ct.softirq


def proc_cputime_total(ct):
  return ct.cpuUser + ct.cpuSystem + ct.cpuChildrenUser + ct.cpuChildrenSystem


def proc_name(proc):
  name = proc.name
  if len(proc.cmdline):
    name = proc.cmdline[0]
  if len(proc.exe):
    name = proc.exe + " - " + name

  return name


if __name__ == "__main__":
  parser = argparse.ArgumentParser()
  parser.add_argument('--mem', action='store_true')
  parser.add_argument('--cpu', action='store_true')
  args = parser.parse_args()

  sm = SubMaster(['deviceState', 'procLog'])

  last_temp = 0.0
  last_mem = 0.0
  total_times = [0.]*8
  busy_times = [0.]*8

  prev_proclog: capnp._DynamicStructReader | None = None
  prev_proclog_t: int | None = None

  while True:
    sm.update()

    if sm.updated['deviceState']:
      t = sm['deviceState']
      last_temp = mean(t.cpuTempC)
      last_mem = t.memoryUsagePercent

    if sm.updated['procLog']:
      m = sm['procLog']

      cores = [0.]*8
      total_times_new = [0.]*8
      busy_times_new = [0.]*8

      for c in m.cpuTimes:
        n = c.cpuNum
        total_times_new[n] = cputime_total(c)
        busy_times_new[n] = cputime_busy(c)

      for n in range(8):
        t_busy = busy_times_new[n] - busy_times[n]
        t_total = total_times_new[n] - total_times[n]
        cores[n] = t_busy / t_total

      total_times = total_times_new[:]
      busy_times = busy_times_new[:]

      print(f"CPU {100.0 * mean(cores):.2f}% - RAM: {last_mem:.2f}% - Temp {last_temp:.2f}C")

      if args.cpu and prev_proclog is not None and prev_proclog_t is not None:
        procs: dict[str, float] = defaultdict(float)
        dt = (sm.logMonoTime['procLog'] - prev_proclog_t) / 1e9
        for proc in m.procs:
          try:
            name = proc_name(proc)
            prev_proc = [p for p in prev_proclog.procs if proc.pid == p.pid][0]
            cpu_time = proc_cputime_total(proc) - proc_cputime_total(prev_proc)
            cpu_usage = cpu_time / dt * 100.
            procs[name] += cpu_usage
          except IndexError:
            pass

        print("Top CPU usage:")
        for k, v in sorted(procs.items(), key=lambda item: item[1], reverse=True)[:10]:
          print(f"{k.rjust(70)}   {v:.2f} %")
        print()

      if args.mem:
        mems = {}
        for proc in m.procs:
          name = proc_name(proc)
          mems[name] = float(proc.memRss) / 1e6
        print("Top memory usage:")
        for k, v in sorted(mems.items(), key=lambda item: item[1], reverse=True)[:10]:
          print(f"{k.rjust(70)}   {v:.2f} MB")
        print()

      prev_proclog = m
      prev_proclog_t = sm.logMonoTime['procLog']
