///|
/// Online digest for large traces that should not retain every change.
pub struct StreamingDigest {
mut changes : Int
signals : Array[(String, Int)]
mut first_time : Int64?
mut last_time : Int64?
mut unknown : Int
mut checksum : Int64
} derive(Debug, Eq)
///|
/// Immutable digest snapshot suitable for persistence.
pub struct DigestSummary {
changes : Int
signals : Int
first_time : Int64?
last_time : Int64?
unknown : Int
checksum : Int64
} derive(Debug, Eq)
///|
/// Create an empty online digest.
pub fn new_streaming_digest() -> StreamingDigest {
{
changes: 0,
signals: [],
first_time: None,
last_time: None,
unknown: 0,
checksum: 0L,
}
}
///|
/// Add one change to the digest in source order.
pub fn StreamingDigest::push_change(
self : StreamingDigest,
change : Change,
) -> Unit {
self.changes += 1
match self.first_time {
None => self.first_time = Some(change.time)
Some(_) => ()
}
self.last_time = Some(change.time)
if change.value.is_unknown() {
self.unknown += 1
}
self.checksum = self.checksum * 31L +
change.time +
change.id.length().to_int64()
self.checksum = self.checksum * 31L + change.value.width().to_int64()
match self.signals.search_by(fn(item) { item.0 == change.id }) {
Some(index) => self.signals[index] = (change.id, self.signals[index].1 + 1)
None => self.signals.push((change.id, 1))
}
}
///|
/// Consume all changes from a trace into an online digest.
pub fn digest_trace(trace : Trace) -> DigestSummary {
let digest = new_streaming_digest()
for change in trace.changes {
digest.push_change(change)
}
digest.snapshot()
}
///|
/// Freeze the current online counters.
pub fn StreamingDigest::snapshot(self : StreamingDigest) -> DigestSummary {
{
changes: self.changes,
signals: self.signals.length(),
first_time: self.first_time,
last_time: self.last_time,
unknown: self.unknown,
checksum: self.checksum,
}
}
///|
/// Return the count for one signal from a mutable digest.
pub fn StreamingDigest::signal_change_count(
self : StreamingDigest,
signal_id : String,
) -> Int {
match self.signals.search_by(fn(item) { item.0 == signal_id }) {
Some(index) => self.signals[index].1
None => 0
}
}
///|
/// Return a stable text representation of an immutable digest.
pub fn DigestSummary::to_text(self : DigestSummary) -> String {
"changes=" +
self.changes.to_string() +
" signals=" +
self.signals.to_string() +
" first=" +
optional_time_text(self.first_time) +
" last=" +
optional_time_text(self.last_time) +
" unknown=" +
self.unknown.to_string() +
" checksum=" +
self.checksum.to_string()
}
///|
/// Return a stable JSON representation of an immutable digest.
pub fn DigestSummary::to_json(self : DigestSummary) -> String {
"{\"changes\":" +
self.changes.to_string() +
",\"signals\":" +
self.signals.to_string() +
",\"first\":" +
optional_time_json(self.first_time) +
",\"last\":" +
optional_time_json(self.last_time) +
",\"unknown\":" +
self.unknown.to_string() +
",\"checksum\":" +
self.checksum.to_string() +
"}"
}
///|
fn optional_time_text(value : Int64?) -> String {
match value {
Some(time) => time.to_string()
None => "-"
}
}
///|
fn optional_time_json(value : Int64?) -> String {
match value {
Some(time) => time.to_string()
None => "null"
}
}
///|
/// Return whether a digest describes an empty input.
pub fn DigestSummary::is_empty(self : DigestSummary) -> Bool {
self.changes == 0
}
///|
/// Return the duration covered by an immutable digest.
pub fn DigestSummary::duration(self : DigestSummary) -> Int64 {
match (self.first_time, self.last_time) {
(Some(first), Some(last)) => last - first
_ => 0L
}
}