///|
/// Context policy when slicing a trace around an interval.
pub enum SliceContext {
NoContext
IncludeInitialValues
IncludeFinalValues
IncludeBothValues
} derive(Debug, Eq)
///|
/// A named slice request suitable for batch analysis.
pub struct TraceSliceRequest {
name : String
start : Int64
end : Int64
signal_ids : Array[String]
context : SliceContext
keep_empty : Bool
} derive(Debug)
///|
/// A slice and its provenance metadata.
pub struct TraceSlice {
name : String
trace : Trace
source_start : Int64
source_end : Int64
before_count : Int
retained_count : Int
after_count : Int
} derive(Debug)
///|
/// List slice context policies.
pub fn slice_context_catalog() -> Array[SliceContext] {
[NoContext, IncludeInitialValues, IncludeFinalValues, IncludeBothValues]
}
///|
/// Create a bounded slice request.
pub fn slice_request(
name : String,
start : Int64,
end : Int64,
) -> TraceSliceRequest {
{
name,
start: Int64::min(start, end),
end: Int64::max(start, end),
signal_ids: [],
context: NoContext,
keep_empty: true,
}
}
///|
/// Select signal identifiers for a slice.
pub fn TraceSliceRequest::with_signals(
self : TraceSliceRequest,
signal_ids : Array[String],
) -> TraceSliceRequest {
{ ..self, signal_ids: signal_ids.copy() }
}
///|
/// Set context policy.
pub fn TraceSliceRequest::with_context(
self : TraceSliceRequest,
context : SliceContext,
) -> TraceSliceRequest {
{ ..self, context, }
}
///|
/// Include or omit empty signal declarations.
pub fn TraceSliceRequest::with_empty(
self : TraceSliceRequest,
keep_empty : Bool,
) -> TraceSliceRequest {
{ ..self, keep_empty, }
}
///|
/// Materialize one slice with optional boundary context.
pub fn slice_trace(trace : Trace, request : TraceSliceRequest) -> TraceSlice {
let ids = if request.signal_ids.length() == 0 {
trace.signals.map(fn(signal) { signal.id })
} else {
request.signal_ids
}
let before = trace.changes.filter(fn(change) {
ids.contains(change.id) && change.time < request.start
})
let inside = trace.changes.filter(fn(change) {
ids.contains(change.id) &&
change.time >= request.start &&
change.time <= request.end
})
let after = trace.changes.filter(fn(change) {
ids.contains(change.id) && change.time > request.end
})
let retained : Array[Change] = []
match request.context {
IncludeInitialValues | IncludeBothValues =>
for id in ids {
match slice_last_for(before, id) {
Some(change) => retained.push({ ..change, time: request.start })
None => ()
}
}
_ => ()
}
for change in inside {
retained.push(change)
}
match request.context {
IncludeFinalValues | IncludeBothValues =>
for id in ids {
match slice_first_for(after, id) {
Some(change) => retained.push({ ..change, time: request.end })
None => ()
}
}
_ => ()
}
let selected_signals = trace.signals.filter(fn(signal) {
ids.contains(signal.id) &&
(request.keep_empty || retained.any(fn(change) { change.id == signal.id }))
})
let result_trace = sort_trace_changes({
header: trace.header,
signals: selected_signals,
changes: retained,
end_time: Int64::max(0L, request.end - request.start),
})
{
name: request.name,
trace: result_trace,
source_start: request.start,
source_end: request.end,
before_count: before.length(),
retained_count: retained.length(),
after_count: after.length(),
}
}
///|
/// Materialize a batch of slices.
pub fn slice_trace_batch(
trace : Trace,
requests : Array[TraceSliceRequest],
) -> Array[TraceSlice] {
requests.map(fn(request) { slice_trace(trace, request) })
}
///|
/// Return whether a slice retained at least one source change.
pub fn TraceSlice::is_empty(self : TraceSlice) -> Bool {
self.retained_count == 0
}
///|
/// Export slice metadata as CSV.
pub fn trace_slices_csv(slices : Array[TraceSlice]) -> String {
let out = StringBuilder()
out.write_string("name,start,end,before,retained,after,signals,empty\n")
for item in slices {
out.write_string(
escape_text(item.name) +
"," +
item.source_start.to_string() +
"," +
item.source_end.to_string() +
"," +
item.before_count.to_string() +
"," +
item.retained_count.to_string() +
"," +
item.after_count.to_string() +
"," +
item.trace.signals.length().to_string() +
"," +
item.is_empty().to_string() +
"\n",
)
}
out.to_string()
}
///|
/// Render slice metadata as JSON.
pub fn TraceSlice::to_json(self : TraceSlice) -> String {
"{\"name\":\"" +
escape_text(self.name) +
"\",\"start\":" +
self.source_start.to_string() +
",\"end\":" +
self.source_end.to_string() +
",\"before\":" +
self.before_count.to_string() +
",\"retained\":" +
self.retained_count.to_string() +
",\"after\":" +
self.after_count.to_string() +
",\"trace\":" +
trace_json(self.trace) +
"}"
}
///|
/// Return the latest change for one signal.
fn slice_last_for(changes : Array[Change], signal_id : String) -> Change? {
let mut result : Change? = None
for change in changes {
if change.id == signal_id {
result = Some(change)
}
}
result
}
///|
/// Return the earliest change for one signal.
fn slice_first_for(changes : Array[Change], signal_id : String) -> Change? {
for change in changes {
if change.id == signal_id {
return Some(change)
}
}
None
}