|
|
|
@ -11,6 +11,7 @@ import numpy as np |
|
|
|
|
import zstandard as zstd |
|
|
|
|
from collections import Counter, defaultdict |
|
|
|
|
from pathlib import Path |
|
|
|
|
from tabulate import tabulate |
|
|
|
|
|
|
|
|
|
from cereal import car, log |
|
|
|
|
import cereal.messaging as messaging |
|
|
|
@ -241,10 +242,9 @@ class TestOnroad: |
|
|
|
|
assert len(veryslow) < 5, f"Too many slow frame draw times: {veryslow}" |
|
|
|
|
|
|
|
|
|
def test_cpu_usage(self, subtests): |
|
|
|
|
result = "\n" |
|
|
|
|
result += "------------------------------------------------\n" |
|
|
|
|
result += "------------------ CPU Usage -------------------\n" |
|
|
|
|
result += "------------------------------------------------\n" |
|
|
|
|
print("\n------------------------------------------------") |
|
|
|
|
print("------------------ CPU Usage -------------------") |
|
|
|
|
print("------------------------------------------------") |
|
|
|
|
|
|
|
|
|
plogs_by_proc = defaultdict(list) |
|
|
|
|
for pl in self.msgs['procLog']: |
|
|
|
@ -252,13 +252,14 @@ class TestOnroad: |
|
|
|
|
if len(x.cmdline) > 0: |
|
|
|
|
n = list(x.cmdline)[0] |
|
|
|
|
plogs_by_proc[n].append(x) |
|
|
|
|
print(plogs_by_proc.keys()) |
|
|
|
|
|
|
|
|
|
cpu_ok = True |
|
|
|
|
dt = (self.msgs['procLog'][-1].logMonoTime - self.msgs['procLog'][0].logMonoTime) / 1e9 |
|
|
|
|
header = ['process', 'usage', 'expected', 'max allowed', 'test result'] |
|
|
|
|
rows = [] |
|
|
|
|
for proc_name, expected in PROCS.items(): |
|
|
|
|
|
|
|
|
|
err = "" |
|
|
|
|
error = "" |
|
|
|
|
usage = 0. |
|
|
|
|
x = plogs_by_proc[proc_name] |
|
|
|
|
if len(x) > 2: |
|
|
|
@ -267,15 +268,15 @@ class TestOnroad: |
|
|
|
|
|
|
|
|
|
max_allowed = max(expected * 1.8, expected + 5.0) |
|
|
|
|
if usage > max_allowed: |
|
|
|
|
err = "USING MORE CPU THAN EXPECTED" |
|
|
|
|
error = "❌ USING MORE CPU THAN EXPECTED ❌" |
|
|
|
|
cpu_ok = False |
|
|
|
|
|
|
|
|
|
else: |
|
|
|
|
err = "NO METRICS FOUND" |
|
|
|
|
|
|
|
|
|
result += f"{proc_name.ljust(35)} {usage=:5.2f}% {expected=:5.2f}% {max_allowed=:5.2f}% {err}\n" |
|
|
|
|
if len(err) > 0: |
|
|
|
|
error = "❌ NO METRICS FOUND ❌" |
|
|
|
|
cpu_ok = False |
|
|
|
|
result += "------------------------------------------------\n" |
|
|
|
|
|
|
|
|
|
rows.append([proc_name, usage, expected, max_allowed, error or "✅"]) |
|
|
|
|
print(tabulate(rows, header, tablefmt="simple_grid", stralign="center", numalign="center", floatfmt=".2f")) |
|
|
|
|
|
|
|
|
|
# Ensure there's no missing procs |
|
|
|
|
all_procs = {p.name for p in self.msgs['managerState'][0].managerState.processes if p.shouldBeRunning} |
|
|
|
@ -287,11 +288,9 @@ class TestOnroad: |
|
|
|
|
procs_tot = sum([(max(x) if isinstance(x, tuple) else x) for x in PROCS.values()]) |
|
|
|
|
with subtests.test(name="total CPU"): |
|
|
|
|
assert procs_tot < MAX_TOTAL_CPU, "Total CPU budget exceeded" |
|
|
|
|
result += "------------------------------------------------\n" |
|
|
|
|
result += f"Total allocated CPU usage is {procs_tot}%, budget is {MAX_TOTAL_CPU}%, {MAX_TOTAL_CPU-procs_tot:.1f}% left\n" |
|
|
|
|
result += "------------------------------------------------\n" |
|
|
|
|
|
|
|
|
|
print(result) |
|
|
|
|
print("------------------------------------------------") |
|
|
|
|
print(f"Total allocated CPU usage is {procs_tot}%, budget is {MAX_TOTAL_CPU}%, {MAX_TOTAL_CPU-procs_tot:.1f}% left") |
|
|
|
|
print("------------------------------------------------") |
|
|
|
|
|
|
|
|
|
assert cpu_ok |
|
|
|
|
|
|
|
|
@ -379,10 +378,12 @@ class TestOnroad: |
|
|
|
|
|
|
|
|
|
def test_timings(self): |
|
|
|
|
passed = True |
|
|
|
|
result = "\n" |
|
|
|
|
result += "------------------------------------------------\n" |
|
|
|
|
result += "----------------- Service Timings --------------\n" |
|
|
|
|
result += "------------------------------------------------\n" |
|
|
|
|
print("\n------------------------------------------------") |
|
|
|
|
print("----------------- Service Timings --------------") |
|
|
|
|
print("------------------------------------------------") |
|
|
|
|
|
|
|
|
|
header = ['service', 'max', 'min', 'mean', 'expected mean', 'rsd', 'max allowed rsd', 'test result'] |
|
|
|
|
rows = [] |
|
|
|
|
for s, (maxmin, rsd) in TIMINGS.items(): |
|
|
|
|
offset = int(SERVICE_LIST[s].frequency * LOG_OFFSET) |
|
|
|
|
msgs = [m.logMonoTime for m in self.msgs[s][offset:]] |
|
|
|
@ -392,21 +393,17 @@ class TestOnroad: |
|
|
|
|
ts = np.diff(msgs) / 1e9 |
|
|
|
|
dt = 1 / SERVICE_LIST[s].frequency |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
np.testing.assert_allclose(np.mean(ts), dt, rtol=0.03, err_msg=f"{s} - failed mean timing check") |
|
|
|
|
np.testing.assert_allclose([np.max(ts), np.min(ts)], dt, rtol=maxmin, err_msg=f"{s} - failed max/min timing check") |
|
|
|
|
except Exception as e: |
|
|
|
|
result += str(e) + "\n" |
|
|
|
|
passed = False |
|
|
|
|
|
|
|
|
|
if np.std(ts) / dt > rsd: |
|
|
|
|
result += f"{s} - failed RSD timing check\n" |
|
|
|
|
passed = False |
|
|
|
|
|
|
|
|
|
result += f"{s.ljust(40)}: {np.array([np.mean(ts), np.max(ts), np.min(ts)])*1e3}\n" |
|
|
|
|
result += f"{''.ljust(40)} {np.max(np.absolute([np.max(ts)/dt, np.min(ts)/dt]))} {np.std(ts)/dt}\n" |
|
|
|
|
result += "="*67 |
|
|
|
|
print(result) |
|
|
|
|
errors = [] |
|
|
|
|
if not np.allclose(np.mean(ts), dt, rtol=0.03, atol=0): |
|
|
|
|
errors.append("❌ FAILED MEAN TIMING CHECK ❌") |
|
|
|
|
if not np.allclose([np.max(ts), np.min(ts)], dt, rtol=maxmin, atol=0): |
|
|
|
|
errors.append("❌ FAILED MAX/MIN TIMING CHECK ❌") |
|
|
|
|
if (np.std(ts)/dt) > rsd: |
|
|
|
|
errors.append("❌ FAILED RSD TIMING CHECK ❌") |
|
|
|
|
passed = not errors |
|
|
|
|
rows.append([s, *(np.array([np.max(ts), np.min(ts), np.mean(ts), dt])*1e3), np.std(ts)/dt, rsd, "\n".join(errors) or "✅"]) |
|
|
|
|
|
|
|
|
|
print(tabulate(rows, header, tablefmt="simple_grid", stralign="center", numalign="center", floatfmt=".2f")) |
|
|
|
|
assert passed |
|
|
|
|
|
|
|
|
|
@release_only |
|
|
|
|