use crate::trace::event::{TraceData, TraceEvent, TraceEventKind};
use crate::trace::independence::independent;
use crate::util::DetHasher;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
#[derive(Debug)]
pub struct FoataTrace {
layers: Vec<Vec<TraceEvent>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TraceEventKey {
pub kind: u8,
pub primary: u64,
pub secondary: u64,
pub tertiary: u64,
}
impl TraceEventKey {
#[must_use]
pub const fn new(kind: u8, primary: u64, secondary: u64, tertiary: u64) -> Self {
Self {
kind,
primary,
secondary,
tertiary,
}
}
}
impl FoataTrace {
#[must_use]
pub fn depth(&self) -> usize {
self.layers.len()
}
#[must_use]
pub fn len(&self) -> usize {
self.layers.iter().map(Vec::len).sum()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.layers.is_empty()
}
#[must_use]
pub fn layers(&self) -> &[Vec<TraceEvent>] {
&self.layers
}
#[must_use]
pub fn flatten(&self) -> Vec<TraceEvent> {
self.layers.iter().flat_map(|l| l.iter().cloned()).collect()
}
#[must_use]
pub fn fingerprint(&self) -> u64 {
let mut hasher = DetHasher::default();
for (layer_idx, layer) in self.layers.iter().enumerate() {
layer_idx.hash(&mut hasher);
layer.len().hash(&mut hasher);
for event in layer {
event_hash_key(event).hash(&mut hasher);
}
}
hasher.finish()
}
}
#[derive(Debug)]
pub struct TraceMonoid {
canonical: FoataTrace,
fingerprint: u64,
}
impl TraceMonoid {
#[must_use]
pub fn identity() -> Self {
let canonical = FoataTrace { layers: vec![] };
let fingerprint = canonical.fingerprint();
Self {
canonical,
fingerprint,
}
}
#[must_use]
pub fn from_events(events: &[TraceEvent]) -> Self {
let canonical = canonicalize(events);
let fingerprint = canonical.fingerprint();
Self {
canonical,
fingerprint,
}
}
#[must_use]
pub fn concat(&self, other: &Self) -> Self {
if self.is_identity() {
return Self {
canonical: FoataTrace {
layers: other.canonical.layers.clone(),
},
fingerprint: other.fingerprint,
};
}
if other.is_identity() {
return Self {
canonical: FoataTrace {
layers: self.canonical.layers.clone(),
},
fingerprint: self.fingerprint,
};
}
let mut combined = self.canonical.flatten();
combined.extend(other.canonical.flatten());
Self::from_events(&combined)
}
#[must_use]
pub fn is_identity(&self) -> bool {
self.canonical.is_empty()
}
#[must_use]
pub fn canonical_form(&self) -> &FoataTrace {
&self.canonical
}
#[must_use]
pub fn class_fingerprint(&self) -> u64 {
self.fingerprint
}
#[must_use]
pub fn len(&self) -> usize {
self.canonical.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.canonical.is_empty()
}
#[must_use]
pub fn critical_path_length(&self) -> usize {
self.canonical.depth()
}
#[must_use]
pub fn max_parallelism(&self) -> usize {
self.canonical
.layers
.iter()
.map(Vec::len)
.max()
.unwrap_or(0)
}
#[must_use]
pub fn equivalent(&self, other: &Self) -> bool {
self.fingerprint == other.fingerprint
}
#[must_use]
pub fn equivalent_exact(&self, other: &Self) -> bool {
if self.canonical.depth() != other.canonical.depth() {
return false;
}
for (la, lb) in self
.canonical
.layers
.iter()
.zip(other.canonical.layers.iter())
{
if la.len() != lb.len() {
return false;
}
for (ea, eb) in la.iter().zip(lb.iter()) {
if !semantically_equal_event(ea, eb) {
return false;
}
}
}
true
}
}
impl PartialEq for TraceMonoid {
fn eq(&self, other: &Self) -> bool {
if self.fingerprint != other.fingerprint {
return false;
}
self.equivalent_exact(other)
}
}
impl Eq for TraceMonoid {}
#[must_use]
pub fn canonicalize(events: &[TraceEvent]) -> FoataTrace {
let n = events.len();
if n == 0 {
return FoataTrace { layers: vec![] };
}
let mut layer_of = vec![0usize; n];
let mut max_layer = 0usize;
for j in 1..n {
for i in 0..j {
if !independent(&events[i], &events[j]) {
layer_of[j] = layer_of[j].max(layer_of[i] + 1);
}
}
max_layer = max_layer.max(layer_of[j]);
}
let mut layers: Vec<Vec<TraceEvent>> = vec![vec![]; max_layer + 1];
for (idx, event) in events.iter().enumerate() {
layers[layer_of[idx]].push(event.clone());
}
for layer in &mut layers {
layer.sort_by(event_cmp);
}
FoataTrace { layers }
}
#[must_use]
pub fn trace_fingerprint(events: &[TraceEvent]) -> u64 {
let n = events.len();
if n == 0 {
return FoataTrace { layers: vec![] }.fingerprint();
}
let mut layer_of = vec![0usize; n];
let mut max_layer = 0usize;
for j in 1..n {
for i in 0..j {
if !independent(&events[i], &events[j]) {
layer_of[j] = layer_of[j].max(layer_of[i] + 1);
}
}
max_layer = max_layer.max(layer_of[j]);
}
let mut layer_indices: Vec<Vec<usize>> = vec![vec![]; max_layer + 1];
for (idx, &layer) in layer_of.iter().enumerate() {
layer_indices[layer].push(idx);
}
let mut hasher = DetHasher::default();
for (layer_idx, indices) in layer_indices.iter_mut().enumerate() {
indices.sort_by(|&a, &b| event_cmp(&events[a], &events[b]));
layer_idx.hash(&mut hasher);
indices.len().hash(&mut hasher);
for &idx in indices.iter() {
event_hash_key(&events[idx]).hash(&mut hasher);
}
}
hasher.finish()
}
fn kind_discriminant(kind: TraceEventKind) -> u8 {
match kind {
TraceEventKind::Spawn => 0,
TraceEventKind::Schedule => 1,
TraceEventKind::Yield => 2,
TraceEventKind::Wake => 3,
TraceEventKind::Poll => 4,
TraceEventKind::Complete => 5,
TraceEventKind::CancelRequest => 6,
TraceEventKind::CancelAck => 7,
TraceEventKind::RegionCloseBegin => 8,
TraceEventKind::RegionCloseComplete => 9,
TraceEventKind::RegionCreated => 10,
TraceEventKind::RegionCancelled => 11,
TraceEventKind::ObligationReserve => 12,
TraceEventKind::ObligationCommit => 13,
TraceEventKind::ObligationAbort => 14,
TraceEventKind::ObligationLeak => 15,
TraceEventKind::TimeAdvance => 16,
TraceEventKind::TimerScheduled => 17,
TraceEventKind::TimerFired => 18,
TraceEventKind::TimerCancelled => 19,
TraceEventKind::IoRequested => 20,
TraceEventKind::IoReady => 21,
TraceEventKind::IoResult => 22,
TraceEventKind::IoError => 23,
TraceEventKind::RngSeed => 24,
TraceEventKind::RngValue => 25,
TraceEventKind::Checkpoint => 26,
TraceEventKind::FuturelockDetected => 27,
TraceEventKind::ChaosInjection => 28,
TraceEventKind::UserTrace => 29,
TraceEventKind::MonitorCreated => 30,
TraceEventKind::MonitorDropped => 31,
TraceEventKind::DownDelivered => 32,
TraceEventKind::LinkCreated => 33,
TraceEventKind::LinkDropped => 34,
TraceEventKind::ExitDelivered => 35,
TraceEventKind::WorkerCancelRequested => 36,
TraceEventKind::WorkerCancelAcknowledged => 37,
TraceEventKind::WorkerDrainStarted => 38,
TraceEventKind::WorkerDrainCompleted => 39,
TraceEventKind::WorkerFinalizeCompleted => 40,
}
}
fn pack_arena(idx: crate::util::ArenaIndex) -> u64 {
(u64::from(idx.index()) << 32) | u64::from(idx.generation())
}
fn event_sort_key(event: &TraceEvent) -> (u8, u64, u64, u64) {
let k = kind_discriminant(event.kind);
match &event.data {
TraceData::Task { task, region }
| TraceData::Cancel { task, region, .. }
| TraceData::Futurelock { task, region, .. } => {
(k, pack_arena(task.0), pack_arena(region.0), 0)
}
TraceData::Region { region, parent } => (
k,
pack_arena(region.0),
parent.map_or(0, |p| pack_arena(p.0)),
0,
),
TraceData::RegionCancel { region, .. } => (k, pack_arena(region.0), 0, 0),
TraceData::Obligation {
obligation,
task,
region,
..
} => (
k,
pack_arena(obligation.0),
pack_arena(task.0),
pack_arena(region.0),
),
TraceData::Time { old, new } => (k, old.as_nanos(), new.as_nanos(), 0),
TraceData::Timer { timer_id, .. } => (k, *timer_id, 0, 0),
TraceData::IoRequested { token, .. } | TraceData::IoReady { token, .. } => {
(k, *token, 0, 0)
}
TraceData::IoResult { token, bytes } => {
let bytes_key = (*bytes).cast_unsigned() ^ (1u64 << 63);
(k, *token, bytes_key, 0)
}
TraceData::IoError { token, kind } => (k, *token, u64::from(*kind), 0),
TraceData::RngSeed { seed } => (k, *seed, 0, 0),
TraceData::RngValue { value } => (k, *value, 0, 0),
TraceData::Checkpoint {
sequence,
active_tasks,
active_regions,
} => (
k,
*sequence,
u64::from(*active_tasks),
u64::from(*active_regions),
),
TraceData::Chaos { task, .. } => {
let task_key = task.map_or(0, |t| pack_arena(t.0));
(k, task_key, 0, 0)
}
TraceData::Message(msg) => {
let mut h = DetHasher::default();
msg.hash(&mut h);
(k, h.finish(), 0, 0)
}
TraceData::Monitor {
monitor_ref,
watcher,
monitored,
..
} => (
k,
*monitor_ref,
pack_arena(watcher.0),
pack_arena(monitored.0),
),
TraceData::Down {
monitor_ref,
monitored,
completion_vt,
..
} => (
k,
completion_vt.as_nanos(),
pack_arena(monitored.0),
*monitor_ref,
),
TraceData::Link {
link_ref,
task_a,
task_b,
..
} => (k, *link_ref, pack_arena(task_a.0), pack_arena(task_b.0)),
TraceData::Exit {
link_ref,
from,
failure_vt,
..
} => (k, failure_vt.as_nanos(), pack_arena(from.0), *link_ref),
TraceData::Worker {
job_id,
task,
region,
..
} => (k, *job_id, pack_arena(task.0), pack_arena(region.0)),
TraceData::None => (k, 0, 0, 0),
}
}
#[must_use]
pub fn trace_event_key(event: &TraceEvent) -> TraceEventKey {
let (kind, primary, secondary, tertiary) = event_sort_key(event);
TraceEventKey::new(kind, primary, secondary, tertiary)
}
fn event_cmp(a: &TraceEvent, b: &TraceEvent) -> Ordering {
event_sort_key(a).cmp(&event_sort_key(b))
}
fn semantically_equal_event(a: &TraceEvent, b: &TraceEvent) -> bool {
a.version == b.version && a.kind == b.kind && a.data == b.data
}
fn event_hash_key(event: &TraceEvent) -> (u8, u64, u64, u64) {
event_sort_key(event)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::monitor::DownReason;
use crate::record::{ObligationAbortReason, ObligationKind};
use crate::types::{CancelReason, ObligationId, RegionId, TaskId, Time};
use insta::assert_json_snapshot;
use serde::Serialize;
#[derive(Debug, Serialize)]
struct CanonicalTraceSnapshot {
depth: usize,
len: usize,
layers: Vec<Vec<CanonicalTraceEventSnapshot>>,
}
#[derive(Debug, Serialize)]
struct CanonicalTraceEventSnapshot {
seq: u64,
time_ns: u64,
kind: &'static str,
key: TraceEventKey,
data: String,
}
fn tid(n: u32) -> TaskId {
TaskId::new_for_test(n, 0)
}
fn rid(n: u32) -> RegionId {
RegionId::new_for_test(n, 0)
}
fn oid(n: u32) -> ObligationId {
ObligationId::new_for_test(n, 0)
}
fn canonical_trace_snapshot(events: &[TraceEvent]) -> CanonicalTraceSnapshot {
let foata = canonicalize(events);
CanonicalTraceSnapshot {
depth: foata.depth(),
len: foata.len(),
layers: foata
.layers()
.iter()
.map(|layer| layer.iter().map(snapshot_event).collect())
.collect(),
}
}
fn snapshot_event(event: &TraceEvent) -> CanonicalTraceEventSnapshot {
CanonicalTraceEventSnapshot {
seq: event.seq,
time_ns: event.time.as_nanos(),
kind: event.kind.stable_name(),
key: trace_event_key(event),
data: snapshot_event_data(&event.data),
}
}
fn snapshot_event_data(data: &TraceData) -> String {
match data {
TraceData::None => "none".to_string(),
TraceData::Task { task, region } => {
format!("task={} region={}", task.as_u64(), region.as_u64())
}
TraceData::Region { region, parent } => format!(
"region={} parent={}",
region.as_u64(),
parent
.map(|region| region.as_u64().to_string())
.unwrap_or_else(|| "none".to_string())
),
TraceData::Obligation {
obligation,
task,
region,
kind,
state,
duration_ns,
abort_reason,
} => format!(
"obligation={} task={} region={} kind={} state={state:?} duration_ns={} abort_reason={}",
pack_arena(obligation.arena_index()),
task.as_u64(),
region.as_u64(),
kind.as_str(),
duration_ns
.map(|value| value.to_string())
.unwrap_or_else(|| "none".to_string()),
abort_reason
.map(|reason| reason.as_str().to_string())
.unwrap_or_else(|| "none".to_string())
),
TraceData::Cancel {
task,
region,
reason,
} => format!(
"task={} region={} reason={reason}",
task.as_u64(),
region.as_u64()
),
TraceData::RegionCancel { region, reason } => {
format!("region={} reason={reason}", region.as_u64())
}
TraceData::Time { old, new } => {
format!("old={} new={}", old.as_nanos(), new.as_nanos())
}
TraceData::Timer { timer_id, deadline } => format!(
"timer_id={timer_id} deadline={}",
deadline
.map(|time| time.as_nanos().to_string())
.unwrap_or_else(|| "none".to_string())
),
TraceData::Checkpoint {
sequence,
active_tasks,
active_regions,
} => format!(
"sequence={sequence} active_tasks={active_tasks} active_regions={active_regions}"
),
other => format!("{other:?}"),
}
}
#[test]
fn trace_canonicalize_happy_path_snapshot() {
let events = [
TraceEvent::region_created(1, Time::ZERO, rid(1), None),
TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(3, Time::ZERO, tid(2), rid(1)),
TraceEvent::poll(4, Time::from_nanos(5), tid(1), rid(1)),
TraceEvent::complete(5, Time::from_nanos(9), tid(1), rid(1)),
TraceEvent::complete(6, Time::from_nanos(11), tid(2), rid(1)),
];
assert_json_snapshot!(
"trace_canonicalize_happy_path",
canonical_trace_snapshot(&events)
);
}
#[test]
fn trace_canonicalize_empty_snapshot() {
assert_json_snapshot!("trace_canonicalize_empty", canonical_trace_snapshot(&[]));
}
#[test]
fn trace_canonicalize_max_budget_snapshot() {
let events = [
TraceEvent::region_created(1, Time::ZERO, rid(9), None),
TraceEvent::spawn(2, Time::from_nanos(1), tid(9), rid(9)),
TraceEvent::obligation_reserve(
3,
Time::from_nanos(2),
oid(7),
tid(9),
rid(9),
ObligationKind::Lease,
),
TraceEvent::time_advance(4, Time::from_nanos(3), Time::ZERO, Time::MAX),
TraceEvent::timer_scheduled(5, Time::MAX, u64::MAX, Time::MAX),
TraceEvent::obligation_commit(
6,
Time::MAX,
oid(7),
tid(9),
rid(9),
ObligationKind::Lease,
u64::MAX,
),
TraceEvent::checkpoint(7, Time::MAX, u64::MAX, u32::MAX, u32::MAX),
];
assert_json_snapshot!(
"trace_canonicalize_max_budget",
canonical_trace_snapshot(&events)
);
}
#[test]
fn trace_canonicalize_cancellation_chain_snapshot() {
let cancel = CancelReason::timeout();
let events = [
TraceEvent::region_created(1, Time::ZERO, rid(3), None),
TraceEvent::spawn(2, Time::from_nanos(1), tid(4), rid(3)),
TraceEvent::obligation_reserve(
3,
Time::from_nanos(2),
oid(4),
tid(4),
rid(3),
ObligationKind::SendPermit,
),
TraceEvent::cancel_request(4, Time::from_nanos(3), tid(4), rid(3), cancel.clone()),
TraceEvent::new(
5,
Time::from_nanos(4),
TraceEventKind::CancelAck,
TraceData::Cancel {
task: tid(4),
region: rid(3),
reason: cancel.clone(),
},
),
TraceEvent::obligation_abort(
6,
Time::from_nanos(5),
oid(4),
tid(4),
rid(3),
ObligationKind::SendPermit,
17,
ObligationAbortReason::Cancel,
),
TraceEvent::region_cancelled(7, Time::from_nanos(6), rid(3), cancel),
];
assert_json_snapshot!(
"trace_canonicalize_cancellation_chain",
canonical_trace_snapshot(&events)
);
}
#[test]
fn empty_trace() {
let foata = canonicalize(&[]);
assert!(foata.is_empty());
assert_eq!(foata.depth(), 0);
assert_eq!(foata.len(), 0);
}
#[test]
fn single_event() {
let events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
let foata = canonicalize(&events);
assert_eq!(foata.depth(), 1);
assert_eq!(foata.len(), 1);
}
#[test]
fn all_independent_events_in_one_layer() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3)),
];
let foata = canonicalize(&events);
assert_eq!(foata.depth(), 1);
assert_eq!(foata.layers()[0].len(), 3);
}
#[test]
fn chain_of_dependent_events() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::poll(2, Time::ZERO, tid(1), rid(1)),
TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
];
let foata = canonicalize(&events);
assert_eq!(foata.depth(), 3);
assert_eq!(foata.layers()[0].len(), 1);
assert_eq!(foata.layers()[1].len(), 1);
assert_eq!(foata.layers()[2].len(), 1);
}
#[test]
fn diamond_dependency() {
let events = [
TraceEvent::region_created(1, Time::ZERO, rid(1), None),
TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(3, Time::ZERO, tid(2), rid(1)),
TraceEvent::complete(4, Time::ZERO, tid(1), rid(1)),
TraceEvent::complete(5, Time::ZERO, tid(2), rid(1)),
];
let foata = canonicalize(&events);
assert_eq!(foata.depth(), 3);
assert_eq!(foata.layers()[0].len(), 1); assert_eq!(foata.layers()[1].len(), 2); assert_eq!(foata.layers()[2].len(), 2); }
#[test]
fn swapped_independent_events_same_fingerprint() {
let trace_a = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
];
let trace_b = [
TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
];
let fp_a = trace_fingerprint(&trace_a);
let fp_b = trace_fingerprint(&trace_b);
assert_eq!(fp_a, fp_b);
}
#[test]
fn swapped_independent_events_same_canonical_form() {
let trace_a = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
];
let trace_b = [
TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
];
let foata_a = canonicalize(&trace_a);
let foata_b = canonicalize(&trace_b);
assert_eq!(foata_a.depth(), foata_b.depth());
assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
}
#[test]
fn different_dependent_orders_different_fingerprints() {
let trace_a = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
];
let trace_b = [
TraceEvent::complete(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
];
let fp_a = trace_fingerprint(&trace_a);
let fp_b = trace_fingerprint(&trace_b);
assert_ne!(fp_a, fp_b);
}
#[test]
fn down_delivered_canonicalizes_by_completion_vt_monitored_monitor_ref() {
let e0 = TraceEvent::down_delivered(
1,
Time::ZERO,
1,
tid(10),
tid(3),
Time::from_nanos(4),
DownReason::Normal,
);
let e1 = TraceEvent::down_delivered(
2,
Time::ZERO,
10,
tid(11),
tid(1),
Time::from_nanos(5),
DownReason::Normal,
);
let e2 = TraceEvent::down_delivered(
3,
Time::ZERO,
9,
tid(12),
tid(1),
Time::from_nanos(5),
DownReason::Normal,
);
let e3 = TraceEvent::down_delivered(
4,
Time::ZERO,
1,
tid(13),
tid(2),
Time::from_nanos(5),
DownReason::Normal,
);
let trace_a = [e0.clone(), e1.clone(), e2.clone(), e3.clone()];
let trace_b = [e3.clone(), e2.clone(), e1.clone(), e0.clone()];
let foata_a = canonicalize(&trace_a);
let foata_b = canonicalize(&trace_b);
assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
assert_eq!(foata_a.depth(), 1);
let flat = foata_a.flatten();
assert_eq!(flat.len(), 4);
assert_eq!(flat[0], e0);
assert_eq!(flat[1], e2);
assert_eq!(flat[2], e1);
assert_eq!(flat[3], e3);
}
#[test]
fn exit_delivered_canonicalizes_by_failure_vt_from_link_ref() {
let e0 = TraceEvent::exit_delivered(
1,
Time::ZERO,
1,
tid(2),
tid(20),
Time::from_nanos(9),
DownReason::Error("boom".to_string()),
);
let e1 = TraceEvent::exit_delivered(
2,
Time::ZERO,
10,
tid(1),
tid(21),
Time::from_nanos(10),
DownReason::Error("boom".to_string()),
);
let e2 = TraceEvent::exit_delivered(
3,
Time::ZERO,
9,
tid(1),
tid(22),
Time::from_nanos(10),
DownReason::Error("boom".to_string()),
);
let e3 = TraceEvent::exit_delivered(
4,
Time::ZERO,
1,
tid(3),
tid(23),
Time::from_nanos(10),
DownReason::Error("boom".to_string()),
);
let trace_a = [e3.clone(), e2.clone(), e1.clone(), e0.clone()];
let trace_b = [e0.clone(), e1.clone(), e2.clone(), e3.clone()];
let foata_a = canonicalize(&trace_a);
let foata_b = canonicalize(&trace_b);
assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
assert_eq!(foata_a.depth(), 1);
let flat = foata_a.flatten();
assert_eq!(flat.len(), 4);
assert_eq!(flat[0], e0);
assert_eq!(flat[1], e2);
assert_eq!(flat[2], e1);
assert_eq!(flat[3], e3);
}
#[test]
fn three_independent_events_all_permutations_same_fingerprint() {
let e1 = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
let e2 = TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2));
let e3 = TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3));
let permutations: Vec<Vec<TraceEvent>> = vec![
vec![e1.clone(), e2.clone(), e3.clone()],
vec![e1.clone(), e3.clone(), e2.clone()],
vec![e2.clone(), e1.clone(), e3.clone()],
vec![e2.clone(), e3.clone(), e1.clone()],
vec![e3.clone(), e1.clone(), e2.clone()],
vec![e3, e2, e1],
];
let fp0 = trace_fingerprint(&permutations[0]);
for (i, perm) in permutations.iter().enumerate() {
let fp = trace_fingerprint(perm);
assert_eq!(fp, fp0, "Permutation {i} has different fingerprint");
}
}
#[test]
fn mixed_trace_canonical_form() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::time_advance(2, Time::ZERO, Time::ZERO, Time::from_nanos(100)),
TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
TraceEvent::timer_fired(4, Time::ZERO, 1),
];
let foata = canonicalize(&events);
assert_eq!(foata.depth(), 2);
assert_eq!(foata.layers()[0].len(), 3); assert_eq!(foata.layers()[1].len(), 1); }
#[test]
fn intra_layer_ordering_is_deterministic() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(3), rid(3)),
TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
];
let foata = canonicalize(&events);
assert_eq!(foata.depth(), 1);
let layer = &foata.layers()[0];
let keys: Vec<_> = layer.iter().map(event_sort_key).collect();
assert!(keys.windows(2).all(|w| w[0] <= w[1]));
}
#[test]
fn fingerprint_matches_foata_fingerprint() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
];
let foata = canonicalize(&events);
let direct = trace_fingerprint(&events);
assert_eq!(foata.fingerprint(), direct);
}
#[test]
fn empty_trace_fingerprint_matches_identity() {
let id = TraceMonoid::identity();
let empty = TraceMonoid::from_events(&[]);
assert_eq!(trace_fingerprint(&[]), id.class_fingerprint());
assert_eq!(id.class_fingerprint(), empty.class_fingerprint());
assert!(id.equivalent(&empty));
}
#[test]
fn depth_reflects_critical_path() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
TraceEvent::complete(4, Time::ZERO, tid(2), rid(2)),
];
let foata = canonicalize(&events);
assert_eq!(foata.depth(), 2);
assert_eq!(foata.layers()[0].len(), 2); assert_eq!(foata.layers()[1].len(), 2); }
#[test]
fn flatten_preserves_event_count() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
];
let foata = canonicalize(&events);
assert_eq!(foata.flatten().len(), events.len());
}
#[test]
fn monoid_identity_left() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
];
let t = TraceMonoid::from_events(&events);
let result = TraceMonoid::identity().concat(&t);
assert!(result.equivalent(&t));
assert!(result.equivalent_exact(&t));
}
#[test]
fn monoid_identity_right() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
];
let t = TraceMonoid::from_events(&events);
let result = t.concat(&TraceMonoid::identity());
assert!(result.equivalent(&t));
assert!(result.equivalent_exact(&t));
}
#[test]
fn monoid_identity_is_empty() {
let id = TraceMonoid::identity();
assert!(id.is_identity());
assert!(id.is_empty());
assert_eq!(id.len(), 0);
assert_eq!(id.critical_path_length(), 0);
assert_eq!(id.max_parallelism(), 0);
}
#[test]
fn monoid_associativity() {
let a_events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
let b_events = [TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2))];
let c_events = [TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3))];
let a = TraceMonoid::from_events(&a_events);
let b = TraceMonoid::from_events(&b_events);
let c = TraceMonoid::from_events(&c_events);
let ab_c = a.concat(&b).concat(&c);
let a_bc = a.concat(&b.concat(&c));
assert!(ab_c.equivalent(&a_bc));
}
#[test]
fn monoid_associativity_with_dependencies() {
let a_events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
let b_events = [TraceEvent::complete(2, Time::ZERO, tid(1), rid(1))];
let c_events = [TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2))];
let a = TraceMonoid::from_events(&a_events);
let b = TraceMonoid::from_events(&b_events);
let c = TraceMonoid::from_events(&c_events);
let ab_c = a.concat(&b).concat(&c);
let a_bc = a.concat(&b.concat(&c));
assert!(ab_c.equivalent(&a_bc));
}
#[test]
fn monoid_equivalent_traces() {
let trace_a = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
];
let trace_b = [
TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
];
let ma = TraceMonoid::from_events(&trace_a);
let mb = TraceMonoid::from_events(&trace_b);
assert_eq!(ma, mb);
assert!(ma.equivalent_exact(&mb));
}
#[test]
fn partial_eq_requires_exact_canonical_match_not_just_fingerprint() {
let trace_a = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
];
let trace_b = [
TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
TraceEvent::complete(2, Time::ZERO, tid(2), rid(2)),
];
let ma = TraceMonoid::from_events(&trace_a);
let mb = TraceMonoid::from_events(&trace_b);
assert!(!ma.equivalent_exact(&mb));
let TraceMonoid {
canonical: FoataTrace { layers },
..
} = mb;
let spoof = TraceMonoid {
canonical: FoataTrace { layers },
fingerprint: ma.fingerprint,
};
assert!(ma.equivalent(&spoof));
assert_ne!(ma, spoof);
}
#[test]
fn exact_equivalence_distinguishes_region_cancel_reason_payload() {
let trace_a = [TraceEvent::region_cancelled(
1,
Time::ZERO,
rid(1),
CancelReason::shutdown(),
)];
let trace_b = [TraceEvent::region_cancelled(
1,
Time::ZERO,
rid(1),
CancelReason::timeout(),
)];
let ma = TraceMonoid::from_events(&trace_a);
let mb = TraceMonoid::from_events(&trace_b);
assert!(ma.equivalent(&mb));
assert!(!ma.equivalent_exact(&mb));
assert_ne!(ma, mb);
}
#[test]
fn monoid_nonequivalent_traces() {
let trace_a = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
];
let trace_b = [
TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
TraceEvent::complete(2, Time::ZERO, tid(2), rid(2)),
];
let ma = TraceMonoid::from_events(&trace_a);
let mb = TraceMonoid::from_events(&trace_b);
assert_ne!(ma, mb);
}
#[test]
fn monoid_parallelism_metrics() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3)),
TraceEvent::complete(4, Time::ZERO, tid(1), rid(1)),
TraceEvent::complete(5, Time::ZERO, tid(2), rid(2)),
];
let m = TraceMonoid::from_events(&events);
assert_eq!(m.len(), 5);
assert_eq!(m.critical_path_length(), 2); assert_eq!(m.max_parallelism(), 3); }
#[test]
fn monoid_from_events_mapping() {
let events = [
TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
TraceEvent::region_created(2, Time::ZERO, rid(2), None),
TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
];
let m = TraceMonoid::from_events(&events);
assert_eq!(m.len(), events.len());
assert_eq!(m.class_fingerprint(), trace_fingerprint(&events));
}
#[test]
fn independent_obligations_same_layer() {
let events = [
TraceEvent::obligation_reserve(
1,
Time::ZERO,
oid(1),
tid(1),
rid(1),
ObligationKind::SendPermit,
),
TraceEvent::obligation_reserve(
2,
Time::ZERO,
oid(2),
tid(2),
rid(2),
ObligationKind::Ack,
),
];
let foata = canonicalize(&events);
assert_eq!(foata.depth(), 1);
}
#[test]
fn same_obligation_events_form_chain() {
let events = [
TraceEvent::obligation_reserve(
1,
Time::ZERO,
oid(1),
tid(1),
rid(1),
ObligationKind::Lease,
),
TraceEvent::obligation_commit(
2,
Time::ZERO,
oid(1),
tid(1),
rid(1),
ObligationKind::Lease,
5000,
),
];
let foata = canonicalize(&events);
assert_eq!(foata.depth(), 2);
}
#[test]
fn trace_event_key_debug_clone_copy_eq_hash() {
use std::collections::HashSet;
let k = TraceEventKey::new(1, 2, 3, 4);
let k2 = k; let k3 = k;
assert_eq!(k, k2);
assert_eq!(k, k3);
assert_ne!(k, TraceEventKey::new(1, 2, 3, 5));
let dbg = format!("{k:?}");
assert!(dbg.contains("TraceEventKey"));
let mut set = HashSet::new();
set.insert(k);
assert!(set.contains(&k2));
}
}