///|
/// A named analysis checkpoint at a simulation timestamp.
pub struct TraceCheckpoint {
name : String
time : Int64
digest : Int64
samples : Array[Sample]
change_count : Int
signal_count : Int
} derive(Debug)
///|
/// A checkpoint set for replay and incremental analysis.
pub struct CheckpointSet {
trace_digest : Int64
checkpoints : Array[TraceCheckpoint]
ordered : Bool
} derive(Debug)
///|
/// Create checkpoints at explicit times.
pub fn make_checkpoints(
trace : Trace,
times : Array[(String, Int64)],
) -> CheckpointSet {
let checkpoints : Array[TraceCheckpoint] = []
for item in times {
let snapshot = trace
.snapshot_at(item.1)
.map(fn(sample) { { signal_id: sample.0, time: item.1, value: sample.1 } })
let changes = trace.changes
.filter(fn(change) { change.time <= item.1 })
.length()
checkpoints.push({
name: item.0,
time: item.1,
digest: checkpoint_digest(snapshot),
samples: snapshot,
change_count: changes,
signal_count: snapshot.length(),
})
}
{
trace_digest: trace.shape_digest(),
checkpoints,
ordered: checkpoint_times_ordered(checkpoints),
}
}
///|
/// Create regularly spaced checkpoints.
pub fn regular_checkpoints(trace : Trace, step : Int64) -> CheckpointSet {
let safe_step = Int64::max(1L, step)
let times : Array[(String, Int64)] = []
let mut time = 0L
let mut index = 0
while time <= trace.end_time {
times.push(("checkpoint-" + index.to_string(), time))
time += safe_step
index += 1
}
make_checkpoints(trace, times)
}
///|
/// Find a checkpoint by name.
pub fn CheckpointSet::find(
self : CheckpointSet,
name : String,
) -> TraceCheckpoint? {
for checkpoint in self.checkpoints {
if checkpoint.name == name {
return Some(checkpoint)
}
}
None
}
///|
/// Find the latest checkpoint at or before a timestamp.
pub fn CheckpointSet::at_or_before(
self : CheckpointSet,
time : Int64,
) -> TraceCheckpoint? {
self.checkpoints.fold(init=None, fn(current, item) {
if item.time > time {
current
} else {
Some(item)
}
})
}
///|
/// Return whether two checkpoints have equivalent sampled state.
pub fn checkpoints_equal(
left : TraceCheckpoint,
right : TraceCheckpoint,
) -> Bool {
left.time == right.time &&
left.samples == right.samples &&
left.change_count == right.change_count
}
///|
/// Export checkpoints as CSV.
pub fn checkpoints_csv(checkpoints : Array[TraceCheckpoint]) -> String {
let out = StringBuilder()
out.write_string("name,time,digest,changes,signals\n")
for item in checkpoints {
out.write_string(
escape_text(item.name) +
"," +
item.time.to_string() +
"," +
item.digest.to_string() +
"," +
item.change_count.to_string() +
"," +
item.signal_count.to_string() +
"\n",
)
}
out.to_string()
}
///|
/// Render a checkpoint set as JSON.
pub fn CheckpointSet::to_json(self : CheckpointSet) -> String {
let items = self.checkpoints.map(checkpoint_json).join(",")
"{\"trace_digest\":" +
self.trace_digest.to_string() +
",\"ordered\":" +
self.ordered.to_string() +
",\"checkpoints\":[" +
items +
"]}"
}
///|
/// Render a checkpoint set for a terminal.
pub fn CheckpointSet::to_text(self : CheckpointSet) -> String {
"trace_digest=" +
self.trace_digest.to_string() +
" checkpoints=" +
self.checkpoints.length().to_string() +
" ordered=" +
self.ordered.to_string()
}
///|
/// Build a small digest from sampled values.
fn checkpoint_digest(samples : Array[Sample]) -> Int64 {
samples.fold(init=17L, fn(hash, item) {
let value_hash = match item.value {
Some(value) => value.display().hash()
None => 0
}
hash * 31L + item.time + value_hash.to_int64()
})
}
///|
/// Check monotonic checkpoint order.
fn checkpoint_times_ordered(checkpoints : Array[TraceCheckpoint]) -> Bool {
let mut previous : Int64? = None
for item in checkpoints {
match previous {
Some(time) if item.time < time => return false
_ => ()
}
previous = Some(item.time)
}
true
}
///|
/// Encode one checkpoint as JSON.
fn checkpoint_json(item : TraceCheckpoint) -> String {
"{\"name\":\"" +
escape_text(item.name) +
"\",\"time\":" +
item.time.to_string() +
",\"digest\":" +
item.digest.to_string() +
",\"changes\":" +
item.change_count.to_string() +
",\"signals\":" +
item.signal_count.to_string() +
"}"
}