summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/greeneye/analyze.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
commit002e3d7d13c414ea874abbee8d82803dd3a72fe5 (patch)
tree6627c12fa07f6a1cf73e5aaeb7390e698dd3a896 /debug/risc-v-gsp-rm-vm/greeneye/analyze.py
parent2f249817df327f5dcccf0464ad517fa60d6071d3 (diff)
downloadopen-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.tar.gz
open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.zip
debug: green-eye wave 3 - RM harness, local simulator, device map, MCP plugin, value-validation demos, CI pipeline, toolchain docs; add host RM tools rmtool/rmtop
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/greeneye/analyze.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/analyze.py157
1 files changed, 152 insertions, 5 deletions
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/analyze.py b/debug/risc-v-gsp-rm-vm/greeneye/analyze.py
index 66429e9..1c7e930 100644
--- a/debug/risc-v-gsp-rm-vm/greeneye/analyze.py
+++ b/debug/risc-v-gsp-rm-vm/greeneye/analyze.py
@@ -6,12 +6,14 @@ offline comparison between runs (bare-metal vs VFIO, old firmware vs new).
"""
from __future__ import annotations
+import json
import os
import re
from collections import Counter, defaultdict
from .record import load_events, load_meta
+_TRACE_KINDS = ("exec", "access", "read", "write")
_ACCESS_RE = re.compile(
r"Invalid (read|write) at addr (0x[0-9a-fA-F]+), size (\d+), region '([^']*)', reason: (\w+)"
)
@@ -90,13 +92,56 @@ def describe_fault(fault: dict, bias: int = 0) -> str:
return "\n".join(lines)
+def _trace_kind(record: dict) -> str | None:
+ """Native/TCG trace records use ``t``; folded session events use ``type``."""
+ return record.get("type") or record.get("t")
+
+
+def _number(value, default: int = 0) -> int:
+ if isinstance(value, int):
+ return value
+ if isinstance(value, str):
+ try:
+ return int(value, 0)
+ except ValueError:
+ return default
+ return default
+
+
+def load_trace(session_dir: str, meta: dict | None = None) -> list[dict]:
+ """Load the trace blob referenced by ``meta['trace_blob']`` (R4)."""
+ meta = meta if meta is not None else load_meta(session_dir)
+ rel = meta.get("trace_blob")
+ if not rel:
+ return []
+ path = rel if os.path.isabs(rel) else os.path.join(session_dir, rel)
+ records: list[dict] = []
+ try:
+ with open(path, "r", encoding="utf-8") as fh:
+ for line in fh:
+ line = line.strip()
+ if not line:
+ continue
+ record = json.loads(line)
+ if _trace_kind(record) != "summary":
+ records.append(record)
+ except (OSError, ValueError):
+ return []
+ return records
+
+
def render_summary(session_dir: str, symbols=None, max_rows: int = 40) -> str:
meta = load_meta(session_dir)
events = load_events(session_dir)
- return render_summary_from(meta, events, symbols=symbols, max_rows=max_rows)
+ return render_summary_from(
+ meta, events, symbols=symbols, max_rows=max_rows, trace=load_trace(session_dir, meta)
+ )
-def render_summary_from(meta: dict, events: list[dict], symbols=None, max_rows: int = 40) -> str:
+def render_summary_from(meta: dict, events: list[dict], symbols=None, max_rows: int = 40,
+ trace: list[dict] | None = None) -> str:
+ if trace is None:
+ trace = [e for e in events if _trace_kind(e) in _TRACE_KINDS]
lines: list[str] = []
lines.append(f"# green-eye session summary: {meta.get('name', '?')}")
lines.append("")
@@ -194,6 +239,79 @@ def render_summary_from(meta: dict, events: list[dict], symbols=None, max_rows:
lines.append(f"| `{page:#x}` | {count} | `{sym.name if sym else ''}` |")
lines.append("")
+ # Execution/access trace coverage (native R4 or folded TCG trace)
+ if trace:
+ def access_kind(record):
+ kind = _trace_kind(record)
+ if kind == "access":
+ return "write" if str(record.get("rw", "")).startswith("w") else "read"
+ return kind
+
+ execs = [r for r in trace if access_kind(r) == "exec"]
+ reads = [r for r in trace if access_kind(r) == "read"]
+ writes = [r for r in trace if access_kind(r) == "write"]
+ lines.append("## Execution trace")
+ lines.append("")
+ blob = meta.get("trace_blob")
+ if blob:
+ lines.append(f"* trace blob: `{blob}`")
+ pcs = Counter()
+ for record in execs:
+ pcs[_number(record.get("pc"))] += 1
+ suffix = f", unique PCs: {len(pcs)}" if execs else " (exec=off)"
+ lines.append(f"* exec events: {len(execs)}{suffix}")
+ read_pages = Counter()
+ for record in reads:
+ read_pages[(_number(record.get("addr")) >> 12) << 12] += 1
+ write_pages = Counter()
+ for record in writes:
+ write_pages[(_number(record.get("addr")) >> 12) << 12] += 1
+ lines.append(
+ f"* accesses: {len(reads)} reads ({len(read_pages)} pages), "
+ f"{len(writes)} writes ({len(write_pages)} pages)"
+ )
+ config = meta.get("trace") or {}
+ if config:
+ bits = [f"exec={'on' if config.get('exec') else 'off'}"]
+ for key in ("start", "end"):
+ if config.get(key) is not None:
+ bits.append(f"{key}={config[key]:#x}" if isinstance(config[key], int) else f"{key}={config[key]}")
+ if config.get("max_events"):
+ bits.append(f"max_events={config['max_events']}")
+ if config.get("dropped"):
+ bits.append(f"dropped={config['dropped']}")
+ lines.append("* trace config: " + ", ".join(bits))
+ lines.append("")
+
+ if execs:
+ by_sym = defaultdict(lambda: [0, 0]) # unique PCs, exec count
+ for pc, count in pcs.items():
+ sym = symbols.resolve(pc) if symbols else None
+ entry = by_sym[sym.name if sym else f"{pc:#x}"]
+ entry[0] += 1
+ entry[1] += count
+ lines.append("### Unique PCs by symbol")
+ lines.append("")
+ lines.append("| unique PCs | execs | symbol |")
+ lines.append("|---:|---:|---|")
+ for name, (unique, count) in sorted(by_sym.items(), key=lambda kv: -kv[1][1])[:max_rows]:
+ lines.append(f"| {unique} | {count} | `{name}` |")
+ lines.append("")
+
+ if read_pages or write_pages:
+ lines.append("### Accessed pages")
+ lines.append("")
+ lines.append("| page | reads | writes | symbol |")
+ lines.append("|---|---:|---:|---|")
+ for page in sorted(set(read_pages) | set(write_pages),
+ key=lambda p: -(read_pages.get(p, 0) + write_pages.get(p, 0)))[:max_rows]:
+ sym = symbols.resolve(page) if symbols else None
+ lines.append(
+ f"| `{page:#x}` | {read_pages.get(page, 0)} | {write_pages.get(page, 0)} "
+ f"| `{sym.name if sym else ''}` |"
+ )
+ lines.append("")
+
# Coverage
steps = [e for e in events if e.get("type") == "step"]
if steps:
@@ -277,9 +395,16 @@ def compare_sessions(a_dir: str, b_dir: str, symbols=None, max_rows: int = 40) -
"""Diff two recorded sessions at the symbolic event level (A/B tooling)."""
a_meta, b_meta = load_meta(a_dir), load_meta(b_dir)
a_events, b_events = load_events(a_dir), load_events(b_dir)
+ a_trace, b_trace = load_trace(a_dir, a_meta), load_trace(b_dir, b_meta)
lines = [f"# green-eye session comparison", "", f"* A: {a_meta.get('name')} ({a_dir})", f"* B: {b_meta.get('name')} ({b_dir})", ""]
- def profile(events):
+ def trace_kind(record: dict) -> str:
+ kind = _trace_kind(record)
+ if kind == "access":
+ return "write" if str(record.get("rw", "")).startswith("w") else "read"
+ return kind
+
+ def profile(events, trace):
bp = Counter()
watch = Counter()
writes = Counter()
@@ -290,10 +415,13 @@ def compare_sessions(a_dir: str, b_dir: str, symbols=None, max_rows: int = 40) -
watch[(event.get("addr"), event.get("kind"))] += 1
elif event.get("type") == "mem_write":
writes[(event.get("addr", 0) >> 12) << 12] += 1
+ for record in trace:
+ if trace_kind(record) == "write":
+ writes[(_number(record.get("addr")) >> 12) << 12] += 1
return bp, watch, writes
- a_bp, a_watch, a_writes = profile(a_events)
- b_bp, b_watch, b_writes = profile(b_events)
+ a_bp, a_watch, a_writes = profile(a_events, a_trace)
+ b_bp, b_watch, b_writes = profile(b_events, b_trace)
lines.append("## Breakpoint profile delta (A vs B)")
lines.append("")
@@ -318,4 +446,23 @@ def compare_sessions(a_dir: str, b_dir: str, symbols=None, max_rows: int = 40) -
for page in sorted(set(a_writes) | set(b_writes), key=lambda k: -(a_writes.get(k, 0) + b_writes.get(k, 0)))[:max_rows]:
lines.append(f"| `{page:#x}` | {a_writes.get(page, 0)} | {b_writes.get(page, 0)} |")
lines.append("")
+
+ def trace_totals(trace):
+ totals = Counter()
+ for record in trace:
+ kind = trace_kind(record)
+ if kind in ("exec", "read", "write"):
+ totals[kind] += 1
+ return totals
+
+ a_totals, b_totals = trace_totals(a_trace), trace_totals(b_trace)
+ if a_trace or b_trace:
+ lines.append("## Trace stream totals (A vs B)")
+ lines.append("")
+ lines.append("| stream | A | B | delta |")
+ lines.append("|---|---:|---:|---:|")
+ for kind in ("exec", "read", "write"):
+ a_count, b_count = a_totals.get(kind, 0), b_totals.get(kind, 0)
+ lines.append(f"| {kind} | {a_count} | {b_count} | {b_count - a_count:+d} |")
+ lines.append("")
return "\n".join(lines) + "\n"