feat(trace+ai): add cubetrace package and wire DBT/AI/CUBEsys command + module edits (green-lit WIP)
Brings in the cubetrace crate (PDF Package 4, §547): wraps a DBI engine via an
FFI seam and streams trace events (basic blocks, syscalls) into cubestore,
tagging each with CZYX coordinates and header flags.
- Cargo.toml: register cubetrace workspace member
- cubeai/src/lib.rs, cubedbt/src/lib.rs: DBT/AI rule + trace integration edits
- cubecode/src/{cb,cell}.rs: code-cell bytecode/module plumbing for traces
- cubesys/src/commands.rs: trace/AI command surface expansion
- cubetrace/: new crate (builds; 2 non-fatal warnings)
All layers of the OS-in-CUBE migration pass; recorded per user green-light.
This commit is contained in:
@@ -9,6 +9,7 @@ members = [
|
||||
"cubesys",
|
||||
"cubedbt",
|
||||
"cubeai",
|
||||
"cubetrace",
|
||||
"cube-bench",
|
||||
]
|
||||
|
||||
|
||||
+2
-3
@@ -191,7 +191,8 @@ impl CubeAi {
|
||||
target: OpClass::Mul,
|
||||
fragment: vec![Op::Shl],
|
||||
},
|
||||
rationale: "computation block: Mul may be replaced by Shl (power-of-two)".into(),
|
||||
rationale: "computation block: Mul may be replaced by Shl (power-of-two)"
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
BlockKind::Branch => {
|
||||
@@ -323,5 +324,3 @@ mod tests {
|
||||
cubedbt::C_DBT_RULE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+8
-4
@@ -35,8 +35,7 @@ pub const C_OS_EFFECT: u8 = 211;
|
||||
/// descriptors, each a dedicated bit in the spare 8..=15 range, leaving bit 12
|
||||
/// (`cubecrypt::HEADER_FLAG_ENCRYPTED`) untouched:
|
||||
/// PURE=9, IO_HEAVY=10, ALLOCATES=11, NETWORK=13, HOT_PATH=14, SECURITY=15.
|
||||
pub const HEADER_FLAG_BEHAVIOR: u16 =
|
||||
0b1110_1110_0000_0000; // bits 9,10,11,13,14,15 (bit12 reserved)
|
||||
pub const HEADER_FLAG_BEHAVIOR: u16 = 0b1110_1110_0000_0000; // bits 9,10,11,13,14,15 (bit12 reserved)
|
||||
|
||||
/// Behavior descriptors for an OS operator kernel (PDF §524–525, full set).
|
||||
///
|
||||
@@ -121,7 +120,11 @@ mod tests {
|
||||
Behavior::PURE | Behavior::IO_HEAVY | Behavior::ALLOCATES | Behavior::NETWORK,
|
||||
);
|
||||
let raw = b.to_flags();
|
||||
assert_eq!(raw, 0x0200 | 0x0400 | 0x0800 | 0x2000, "to_flags wrong: {raw:#x}");
|
||||
assert_eq!(
|
||||
raw,
|
||||
0x0200 | 0x0400 | 0x0800 | 0x2000,
|
||||
"to_flags wrong: {raw:#x}"
|
||||
);
|
||||
// ENCRYPTED bit (12) must never be set by a descriptor.
|
||||
assert_eq!(raw & (1 << 12), 0, "descriptor must not set ENCRYPTED bit");
|
||||
|
||||
@@ -137,7 +140,8 @@ mod tests {
|
||||
let (read_h, _) = store.get_record(&coord).expect("record present");
|
||||
let back = Behavior::from_flags(read_h.flags.bits());
|
||||
assert_eq!(
|
||||
back, b,
|
||||
back,
|
||||
b,
|
||||
"behavior dropped on store round-trip: stored {raw:#x}, got {:#x}",
|
||||
read_h.flags.bits()
|
||||
);
|
||||
|
||||
@@ -71,6 +71,7 @@ impl Kind {
|
||||
/// in the body. This keeps Package 4 fully compatible with the Package 2
|
||||
/// record wire format and with cubefs. `code_kind`/`set_kind` bridge the
|
||||
/// enum to the string.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CodeCell {
|
||||
/// The cube coordinate this cell is stored at.
|
||||
pub label: Czyx,
|
||||
|
||||
+8
-22
@@ -26,7 +26,7 @@
|
||||
//! `Vm`, returning the `RunResult`.
|
||||
|
||||
use cubecode::{opcode::Op, CodeCell, Kind, Vm};
|
||||
use cubecoords::{Czyx, CubeHeader};
|
||||
use cubecoords::{CubeHeader, Czyx};
|
||||
use cubestore::{CubeBackend, CubeStore, HashBackend};
|
||||
|
||||
/// `C` axis band where DBT translation rules are stored, kept distinct from the
|
||||
@@ -213,11 +213,7 @@ impl CodeCache {
|
||||
/// target (or whose rule target is `Any`) is replaced by that rule's
|
||||
/// fragment. The rewritten cell is stored at a fresh cache coordinate and
|
||||
/// returned (with its new label). The original store is untouched.
|
||||
pub fn patch(
|
||||
&mut self,
|
||||
original: &CodeCell,
|
||||
rules: &[TranslationRule],
|
||||
) -> CodeCell {
|
||||
pub fn patch(&mut self, original: &CodeCell, rules: &[TranslationRule]) -> CodeCell {
|
||||
let mut patched: Vec<Op> = Vec::with_capacity(original.code.len());
|
||||
for op in &original.code {
|
||||
let class = OpClass::of(op);
|
||||
@@ -237,8 +233,7 @@ impl CodeCache {
|
||||
self.next_x = self.next_x.wrapping_add(1).max(1);
|
||||
let (h, body) = cache_header_for(original, &cubecode::opcode::encode(&patched));
|
||||
self.store.put_record(label, &h, &body);
|
||||
CodeCell::from_record(label, &h, &body)
|
||||
.expect("patched cell is always valid bytecode")
|
||||
CodeCell::from_record(label, &h, &body).expect("patched cell is always valid bytecode")
|
||||
}
|
||||
|
||||
/// Run a patched cache entry in the VM, returning its result.
|
||||
@@ -317,11 +312,7 @@ impl<B: CubeBackend> DbRuntime<B> {
|
||||
|
||||
/// Store a translation rule into the `c220` rule band at a fresh coordinate.
|
||||
/// Returns the coordinate it was written to.
|
||||
pub fn store_rule<B: CubeBackend>(
|
||||
store: &mut CubeStore<B>,
|
||||
rule: &TranslationRule,
|
||||
x: u8,
|
||||
) -> Czyx {
|
||||
pub fn store_rule<B: CubeBackend>(store: &mut CubeStore<B>, rule: &TranslationRule, x: u8) -> Czyx {
|
||||
let label = Czyx::new(C_DBT_RULE, 1, 1, x);
|
||||
let mut h = CubeHeader::new();
|
||||
h.title = Some(rule.name.clone());
|
||||
@@ -363,12 +354,10 @@ mod tests {
|
||||
#[test]
|
||||
fn cache_patches_and_runs() {
|
||||
// Original: Const 3, Const 4, Add, Halt => 3+4 = 7.
|
||||
let orig = sample_cell(Czyx::new(1, 1, 1, 1), vec![
|
||||
Op::Const(3),
|
||||
Op::Const(4),
|
||||
Op::Add,
|
||||
Op::Halt,
|
||||
]);
|
||||
let orig = sample_cell(
|
||||
Czyx::new(1, 1, 1, 1),
|
||||
vec![Op::Const(3), Op::Const(4), Op::Add, Op::Halt],
|
||||
);
|
||||
// Rule: replace `Add` with `Const 2, Mul` => (a)*(2). With a=3,b=4:
|
||||
// naive in-place substitution yields Const3, Const4, Const2, Mul =>
|
||||
// 4*2 = 8. This proves the patched fragment is what runs.
|
||||
@@ -424,6 +413,3 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+74
-25
@@ -499,8 +499,17 @@ impl Session {
|
||||
let owner = self.identity.as_ref().map(|i| i.owner_local.as_str());
|
||||
let value = {
|
||||
let mut scratch = CubeStore::new(HashBackend::new());
|
||||
crate::store_code_cell(&mut scratch, path, kind, name, &[], &ops, owner, descriptor)
|
||||
.map_err(|e| e.to_string())?;
|
||||
crate::store_code_cell(
|
||||
&mut scratch,
|
||||
path,
|
||||
kind,
|
||||
name,
|
||||
&[],
|
||||
&ops,
|
||||
owner,
|
||||
descriptor,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
scratch.get_raw(&coord).unwrap_or_default()
|
||||
};
|
||||
let header = header_for_code(kind, name, &ops, owner, descriptor);
|
||||
@@ -845,8 +854,17 @@ impl Session {
|
||||
let owner = self.identity.as_ref().map(|i| i.owner_local.as_str());
|
||||
let value = {
|
||||
let mut scratch = CubeStore::new(HashBackend::new());
|
||||
crate::store_code_cell(&mut scratch, path, Kind::Fn, name, &[], &code, owner, None)
|
||||
.map_err(|e| e.to_string())?;
|
||||
crate::store_code_cell(
|
||||
&mut scratch,
|
||||
path,
|
||||
Kind::Fn,
|
||||
name,
|
||||
&[],
|
||||
&code,
|
||||
owner,
|
||||
None,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
scratch.get_raw(&coord).unwrap_or_default()
|
||||
};
|
||||
let header = header_for_code(Kind::Fn, name, &code, owner, None);
|
||||
@@ -1275,12 +1293,7 @@ pub fn txn_snapshot(s: &Session) -> CubeStore<HashBackend> {
|
||||
|
||||
/// Compute the coordinate a `store_code_cell` call would target, without
|
||||
/// writing — used to buffer `prog`/`write` mutations during a transaction.
|
||||
fn scratch_code_coord(
|
||||
path: &str,
|
||||
kind: Kind,
|
||||
name: &str,
|
||||
code: &[Op],
|
||||
) -> Result<Czyx, String> {
|
||||
fn scratch_code_coord(path: &str, kind: Kind, name: &str, code: &[Op]) -> Result<Czyx, String> {
|
||||
let mut scratch = CubeStore::new(HashBackend::new());
|
||||
crate::store_code_cell(&mut scratch, path, kind, name, &[], code, None, None)
|
||||
.map_err(|e| e.to_string())
|
||||
@@ -1452,33 +1465,69 @@ mod tests {
|
||||
|
||||
// Lay down the OS kernel call graph.
|
||||
let out = s.exec("tick").expect("tick should lay down kernels");
|
||||
assert!(out.contains("normalize-config"), "cfg kernel missing: {out}");
|
||||
assert!(out.contains("decide-snapshot"), "decide kernel missing: {out}");
|
||||
assert!(out.contains("summarize-procs"), "summarize kernel missing: {out}");
|
||||
assert!(
|
||||
out.contains("normalize-config"),
|
||||
"cfg kernel missing: {out}"
|
||||
);
|
||||
assert!(
|
||||
out.contains("decide-snapshot"),
|
||||
"decide kernel missing: {out}"
|
||||
);
|
||||
assert!(
|
||||
out.contains("summarize-procs"),
|
||||
"summarize kernel missing: {out}"
|
||||
);
|
||||
assert!(out.contains("cube-os-tick"), "tick kernel missing: {out}");
|
||||
// The root links the three leaves (call graph, not foreign code).
|
||||
assert!(out.contains("links cfg,decide,summarize"), "call graph not wired: {out}");
|
||||
assert!(
|
||||
out.contains("links cfg,decide,summarize"),
|
||||
"call graph not wired: {out}"
|
||||
);
|
||||
// Behavior descriptors are stamped (round-trip through header flags).
|
||||
assert!(out.contains("[pure]") && out.contains("[io]") && out.contains("[pure,hot]"),
|
||||
"behavior descriptors not stamped: {out}");
|
||||
assert!(
|
||||
out.contains("[pure]") && out.contains("[io]") && out.contains("[pure,hot]"),
|
||||
"behavior descriptors not stamped: {out}"
|
||||
);
|
||||
|
||||
// Run the root kernel: it must traverse the call graph (CallLink 0..2)
|
||||
// and return, proving the OS's behavior lives as addressable kernels.
|
||||
let run_out = s.exec("run /c210/z001/y001/x004").expect("tick kernel must run");
|
||||
assert!(run_out.contains("Halted"), "tick kernel should halt: {run_out}");
|
||||
let run_out = s
|
||||
.exec("run /c210/z001/y001/x004")
|
||||
.expect("tick kernel must run");
|
||||
assert!(
|
||||
run_out.contains("Halted"),
|
||||
"tick kernel should halt: {run_out}"
|
||||
);
|
||||
|
||||
// Step 2 — the effector reads the COMPUTED result and emits the effect.
|
||||
let eff = s.exec("native-apply /c210/z001/y001/x002")
|
||||
let eff = s
|
||||
.exec("native-apply /c210/z001/y001/x002")
|
||||
.expect("effector must run decide-snapshot");
|
||||
assert!(eff.contains("computed result = 1"), "decide kernel result wrong: {eff}");
|
||||
assert!(eff.contains("OS EFFECT"), "effector must emit OS EFFECT: {eff}");
|
||||
assert!(eff.contains("snapshot NOW"), "decision=1 should snapshot: {eff}");
|
||||
assert!(
|
||||
eff.contains("computed result = 1"),
|
||||
"decide kernel result wrong: {eff}"
|
||||
);
|
||||
assert!(
|
||||
eff.contains("OS EFFECT"),
|
||||
"effector must emit OS EFFECT: {eff}"
|
||||
);
|
||||
assert!(
|
||||
eff.contains("snapshot NOW"),
|
||||
"decision=1 should snapshot: {eff}"
|
||||
);
|
||||
|
||||
// A plain pure kernel also routes through the effector with no store-IO.
|
||||
let eff2 = s.exec("native-apply /c210/z001/y001/x003")
|
||||
let eff2 = s
|
||||
.exec("native-apply /c210/z001/y001/x003")
|
||||
.expect("effector must run summarize-procs");
|
||||
assert!(eff2.contains("computed result = 20"), "summarize result wrong: {eff2}");
|
||||
assert!(eff2.contains("descriptors=[\"pure\", \"hot\"]"), "descriptor readback wrong: {eff2}");
|
||||
assert!(
|
||||
eff2.contains("computed result = 20"),
|
||||
"summarize result wrong: {eff2}"
|
||||
);
|
||||
assert!(
|
||||
eff2.contains("descriptors=[\"pure\", \"hot\"]"),
|
||||
"descriptor readback wrong: {eff2}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "cubetrace"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "CUBELinux-2 tracing package: wraps a DBI engine (via FFI seam) and streams trace events (basic blocks, syscalls) into cubestore, tagging each with CZYX coordinates and header flags (PDF Package 4, §547)."
|
||||
|
||||
[dependencies]
|
||||
cubecoords = { path = "../cubecoords" }
|
||||
cubestore = { path = "../cubestore" }
|
||||
cubecode = { path = "../cubecode" }
|
||||
@@ -0,0 +1,302 @@
|
||||
//! CUBELinux-2 tracing package.
|
||||
//!
|
||||
//! Per the PDF (Package 4, §547 / §1113): *"cubetrace: wraps a DBI engine
|
||||
//! (via FFI) and streams trace events (basic blocks, syscalls) into
|
||||
//! cubestore, tagging each with CZYX coordinates and header flags."*
|
||||
//!
|
||||
//! This crate is the *head* of the Package‑4 pipeline (cubetrace → cubeai →
|
||||
//! cubedbt). It is dependency-free and exercises today on a `Null` (offline)
|
||||
//! source so it builds and is testable without an external DBI engine; the
|
||||
//! real engine is reached through the documented `DbiEngine` FFI seam.
|
||||
//!
|
||||
//! Model
|
||||
//! -----
|
||||
//! * A [`TraceEvent`] is a basic block / syscall / metadata observation with a
|
||||
//! timestamp, a target coordinate, and policy/behavior header flags.
|
||||
//! * A [`Tracer`] streams events into a [`CubeStore`] keyed by CZYX (the
|
||||
//! `c240` trace band), tagging each record's header with time, environment,
|
||||
//! and policy flags (§976).
|
||||
//! * `replay_all` deterministically replays the stored stream in timestamp
|
||||
//! order (§977/§1118: "deterministic replay first").
|
||||
|
||||
use cubecode::{Behavior, CodeCell, Kind};
|
||||
use cubecoords::{CubeHeader, Czyx};
|
||||
use cubestore::{CubeBackend, CubeStore, HashBackend};
|
||||
|
||||
/// `C` axis band where trace events are stored (keyed CZYX, §975).
|
||||
pub const C_TRACE: u8 = 240;
|
||||
|
||||
/// Kind of traced observation.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum EventKind {
|
||||
/// A basic block executed (carries the block's code/length).
|
||||
BasicBlock,
|
||||
/// A syscall entered/exited (carries the syscall number).
|
||||
Syscall,
|
||||
/// Free-form metadata about the run (policy, environment).
|
||||
Meta,
|
||||
}
|
||||
|
||||
/// A single traced observation: what happened, when, where, and with what
|
||||
/// policy/behavior tags. Serialized into a trace record's body.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TraceEvent {
|
||||
pub kind: EventKind,
|
||||
/// Monotonic timestamp (ns from trace start).
|
||||
pub ts: u64,
|
||||
/// Target CZYX the event is about (the block/syscall coordinate).
|
||||
pub coord: Czyx,
|
||||
/// For `BasicBlock`: the executed bytecode; for `Syscall`: the syscall
|
||||
/// number; for `Meta`: unused (0).
|
||||
pub payload: Vec<u8>,
|
||||
/// Behavior/policy descriptors to stamp onto the record header.
|
||||
pub behavior: Behavior,
|
||||
}
|
||||
|
||||
impl TraceEvent {
|
||||
/// Serialize to a stable byte form (no external deps).
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
out.push(self.kind as u8);
|
||||
out.extend_from_slice(&self.ts.to_le_bytes());
|
||||
out.extend_from_slice(&CZYX_BYTES);
|
||||
out.extend_from_slice(&self.coord.pack_u32().to_le_bytes());
|
||||
out.extend_from_slice(&self.behavior.to_flags().to_le_bytes());
|
||||
out.push(self.payload.len() as u8);
|
||||
out.extend_from_slice(&self.payload);
|
||||
out
|
||||
}
|
||||
|
||||
/// Inverse of [`to_bytes`]. Returns `None` on malformed input.
|
||||
pub fn from_bytes(b: &[u8]) -> Option<TraceEvent> {
|
||||
let mut i = 0;
|
||||
let kind = match *b.get(i)? {
|
||||
0 => EventKind::BasicBlock,
|
||||
1 => EventKind::Syscall,
|
||||
2 => EventKind::Meta,
|
||||
_ => return None,
|
||||
};
|
||||
i += 1;
|
||||
let ts = u64::from_le_bytes(b.get(i..i + 8)?.try_into().ok()?);
|
||||
i += 8;
|
||||
i += 4; // skip CZYX_BYTES sentinel
|
||||
let packed = u32::from_le_bytes(b.get(i..i + 4)?.try_into().ok()?);
|
||||
i += 4;
|
||||
let coord = Czyx::new(
|
||||
(packed >> 24) as u8,
|
||||
(packed >> 16) as u8,
|
||||
(packed >> 8) as u8,
|
||||
packed as u8,
|
||||
);
|
||||
let flags = u16::from_le_bytes(b.get(i..i + 2)?.try_into().ok()?);
|
||||
i += 2;
|
||||
let plen = *b.get(i)? as usize;
|
||||
i += 1;
|
||||
let payload = b.get(i..i + plen)?.to_vec();
|
||||
Some(TraceEvent {
|
||||
kind,
|
||||
ts,
|
||||
coord,
|
||||
payload,
|
||||
behavior: Behavior::from_flags(flags),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Sentinel to make the on-wire format self-identifying as a CZYX record.
|
||||
const CZYX_BYTES: [u8; 4] = *b"CZYX";
|
||||
|
||||
/// Source of trace events. In the full stack this is the FFI seam to a DBI
|
||||
/// engine (DynamoRIO / Intel Pin / QBDI / Frida — §970). Here it is a trait so
|
||||
/// the crate is testable offline via [`NullEngine`] and a real engine can be
|
||||
/// dropped in without changing callers.
|
||||
pub trait DbiEngine {
|
||||
/// Attach to a target (pid or path). Returns `Err` if the engine can't.
|
||||
fn attach(&mut self, target: &str) -> Result<(), String>;
|
||||
/// Pull the next event, or `None` when the run is exhausted.
|
||||
fn next_event(&mut self) -> Option<TraceEvent>;
|
||||
}
|
||||
|
||||
/// Offline engine: replays a pre-recorded event vector. Stands in for a live
|
||||
/// DBI backend in tests and headless environments.
|
||||
pub struct NullEngine {
|
||||
events: Vec<TraceEvent>,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl NullEngine {
|
||||
pub fn new(events: Vec<TraceEvent>) -> Self {
|
||||
NullEngine { events, idx: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl DbiEngine for NullEngine {
|
||||
fn attach(&mut self, _target: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
fn next_event(&mut self) -> Option<TraceEvent> {
|
||||
if self.idx < self.events.len() {
|
||||
let e = self.events[self.idx].clone();
|
||||
self.idx += 1;
|
||||
Some(e)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Streams trace events into a [`CubeStore`] (the `c240` band), tagging each
|
||||
/// record with time, environment, and policy header flags (PDF §976).
|
||||
pub struct Tracer<B: CubeBackend> {
|
||||
store: CubeStore<B>,
|
||||
next_x: u8,
|
||||
}
|
||||
|
||||
impl<B: CubeBackend> Tracer<B> {
|
||||
pub fn new(store: CubeStore<B>) -> Self {
|
||||
Tracer { store, next_x: 1 }
|
||||
}
|
||||
|
||||
/// Drain an engine into the store, returning the number of events stored.
|
||||
pub fn run<E: DbiEngine>(&mut self, engine: &mut E) -> usize {
|
||||
let mut count = 0;
|
||||
while let Some(ev) = engine.next_event() {
|
||||
self.store_event(&ev);
|
||||
count += 1;
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
/// Store one event under a fresh CZYX coordinate in the `c240` band.
|
||||
pub fn store_event(&mut self, ev: &TraceEvent) -> Czyx {
|
||||
let label = Czyx::new(C_TRACE, 1, (ev.kind as u8), self.next_x);
|
||||
self.next_x = self.next_x.wrapping_add(1).max(1);
|
||||
let mut h = CubeHeader::new();
|
||||
h.title = Some(format!("{:?}:{:?}", ev.kind, ev.coord));
|
||||
h.doc_type = Some(Kind::Other.as_str().into());
|
||||
h.size_bytes = Some(ev.to_bytes().len() as u64);
|
||||
h.flags.0 |= ev.behavior.to_flags();
|
||||
h.refresh_flags();
|
||||
self.store.put_record(label, &h, &ev.to_bytes());
|
||||
label
|
||||
}
|
||||
|
||||
/// Borrow the backing store (e.g. to hand to `cubeai`/others).
|
||||
pub fn store(&self) -> &CubeStore<B> {
|
||||
&self.store
|
||||
}
|
||||
}
|
||||
|
||||
/// Deterministic replay: collect every trace event from the `c240` band and
|
||||
/// return them in timestamp order (PDF §977/§1118: "deterministic replay
|
||||
/// first"). The trace is the immutable cube record; replay never mutates it.
|
||||
pub fn replay_all(store: &CubeStore<HashBackend>) -> Vec<TraceEvent> {
|
||||
let mut events = Vec::new();
|
||||
for k in store.keys() {
|
||||
if k.c != C_TRACE {
|
||||
continue;
|
||||
}
|
||||
if let Some((_, body)) = store.get_record(&k) {
|
||||
if let Some(ev) = TraceEvent::from_bytes(&body) {
|
||||
events.push(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
events.sort_by_key(|e| e.ts);
|
||||
events
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn ev(kind: EventKind, ts: u64, coord: Czyx, beh: Behavior) -> TraceEvent {
|
||||
TraceEvent {
|
||||
kind,
|
||||
ts,
|
||||
coord,
|
||||
payload: vec![kind as u8],
|
||||
behavior: beh,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_round_trips_through_bytes() {
|
||||
let e = ev(
|
||||
EventKind::Syscall,
|
||||
42,
|
||||
Czyx::new(1, 2, 3, 4),
|
||||
Behavior::HOT_PATH,
|
||||
);
|
||||
let back = TraceEvent::from_bytes(&e.to_bytes()).expect("decodes");
|
||||
assert_eq!(e, back);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracer_streams_to_c240() {
|
||||
let mut engine = NullEngine::new(vec![
|
||||
ev(
|
||||
EventKind::BasicBlock,
|
||||
10,
|
||||
Czyx::new(1, 0, 0, 1),
|
||||
Behavior::PURE,
|
||||
),
|
||||
ev(
|
||||
EventKind::Syscall,
|
||||
20,
|
||||
Czyx::new(1, 0, 0, 2),
|
||||
Behavior::IO_HEAVY,
|
||||
),
|
||||
ev(
|
||||
EventKind::Meta,
|
||||
5,
|
||||
Czyx::new(1, 0, 0, 3),
|
||||
Behavior::default(),
|
||||
),
|
||||
]);
|
||||
let mut tracer = Tracer::new(CubeStore::new(HashBackend::new()));
|
||||
assert_eq!(tracer.run(&mut engine), 3);
|
||||
// All records landed in the c240 band.
|
||||
let stored: usize = tracer
|
||||
.store()
|
||||
.keys()
|
||||
.into_iter()
|
||||
.filter(|k| k.c == C_TRACE)
|
||||
.count();
|
||||
assert_eq!(stored, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replay_is_deterministic_and_ordered() {
|
||||
let mut engine = NullEngine::new(vec![
|
||||
ev(
|
||||
EventKind::BasicBlock,
|
||||
30,
|
||||
Czyx::new(1, 0, 0, 1),
|
||||
Behavior::PURE,
|
||||
),
|
||||
ev(
|
||||
EventKind::Syscall,
|
||||
10,
|
||||
Czyx::new(1, 0, 0, 2),
|
||||
Behavior::IO_HEAVY,
|
||||
),
|
||||
ev(
|
||||
EventKind::Meta,
|
||||
20,
|
||||
Czyx::new(1, 0, 0, 3),
|
||||
Behavior::default(),
|
||||
),
|
||||
]);
|
||||
let mut tracer = Tracer::new(CubeStore::new(HashBackend::new()));
|
||||
tracer.run(&mut engine);
|
||||
let replayed = replay_all(tracer.store());
|
||||
assert_eq!(replayed.len(), 3);
|
||||
// Sorted by ts: 10 (Syscall), 20 (Meta), 30 (BasicBlock).
|
||||
assert_eq!(replayed[0].kind, EventKind::Syscall);
|
||||
assert_eq!(replayed[1].kind, EventKind::Meta);
|
||||
assert_eq!(replayed[2].kind, EventKind::BasicBlock);
|
||||
// Behavior flags survived the round-trip through the store.
|
||||
assert_eq!(replayed[0].behavior, Behavior(Behavior::IO_HEAVY));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user