///|
/// A signal/time selection used to derive a smaller trace.
pub struct TraceSelection {
pattern : String?
start : Int64
end : Int64
include_empty_signals : Bool
} derive(Debug, Eq)
///|
/// Create a selection covering the complete trace.
pub fn full_selection(trace : Trace) -> TraceSelection {
{ pattern: None, start: 0L, end: trace.end_time, include_empty_signals: true }
}
///|
/// Select signals by glob and changes by inclusive time range.
pub fn select_trace(trace : Trace, selection : TraceSelection) -> Trace {
let selected_signals = match selection.pattern {
Some(pattern) => find_signals_glob(trace, pattern)
None => trace.signals.copy()
}
let ids = selected_signals.map(fn(signal) { signal.id })
let changes = trace.changes.filter(fn(change) {
ids.contains(change.id) &&
change.time >= selection.start &&
change.time <= selection.end
})
let final_signals = if selection.include_empty_signals {
selected_signals
} else {
selected_signals.filter(fn(signal) {
ids.contains(signal.id) &&
changes.any(fn(change) { change.id == signal.id })
})
}
{
header: trace.header,
signals: final_signals,
changes,
end_time: Int64::min(
trace.end_time,
Int64::max(selection.start, selection.end),
),
}
}
///|
/// Clip a trace to an inclusive time window.
pub fn clip_trace(trace : Trace, start~ : Int64, end~ : Int64) -> Trace {
select_trace(trace, { pattern: None, start, end, include_empty_signals: true })
}
///|
/// Keep only declarations matching a path glob.
pub fn filter_trace_signals(trace : Trace, pattern : String) -> Trace {
select_trace(trace, {
pattern: Some(pattern),
start: 0L,
end: trace.end_time,
include_empty_signals: true,
})
}
///|
/// Remove consecutive duplicate values for each signal.
pub fn compact_trace(trace : Trace) -> Trace {
let result : Array[Change] = []
let previous : Map[String, Value] = Map([])
for change in trace.changes {
match previous.get(change.id) {
Some(value) if value == change.value => ()
_ => {
result.push(change)
previous[change.id] = change.value
}
}
}
{
header: trace.header,
signals: trace.signals,
changes: result,
end_time: trace.end_time,
}
}
///|
/// Remove duplicate writes at the same signal and timestamp, keeping the last.
pub fn deduplicate_trace(trace : Trace) -> Trace {
let result : Array[Change] = []
for change in trace.changes {
let previous = result.last()
match previous {
Some(item) if item.id == change.id && item.time == change.time =>
result[result.length() - 1] = change
_ => result.push(change)
}
}
{
header: trace.header,
signals: trace.signals,
changes: result,
end_time: trace.end_time,
}
}
///|
/// Scale all timestamps and the trace end time by an integer multiplier.
pub fn scale_trace_time(trace : Trace, multiplier : Int64) -> Trace {
let factor = Int64::max(0L, multiplier)
let changes = trace.changes.map(fn(change) {
{ time: change.time * factor, id: change.id, value: change.value }
})
{
header: trace.header,
signals: trace.signals,
changes,
end_time: trace.end_time * factor,
}
}
///|
/// Rename signal identifiers while preserving all values.
pub fn rename_signal(trace : Trace, old_id : String, new_id : String) -> Trace {
let signals = trace.signals.map(fn(signal) {
if signal.id == old_id {
{
id: new_id,
name: signal.name,
width: signal.width,
kind: signal.kind,
scope: signal.scope,
}
} else {
signal
}
})
let changes = trace.changes.map(fn(change) {
if change.id == old_id {
{ time: change.time, id: new_id, value: change.value }
} else {
change
}
})
{ header: trace.header, signals, changes, end_time: trace.end_time }
}
///|
/// Return a trace with all changes ordered by timestamp and stable input order.
pub fn sort_trace_changes(trace : Trace) -> Trace {
let changes = trace.changes.copy()
changes.sort_by(fn(left, right) {
if left.time < right.time {
-1
} else if left.time > right.time {
1
} else {
0
}
})
{
header: trace.header,
signals: trace.signals,
changes,
end_time: trace.end_time,
}
}
///|
/// Return a trace with a new explicit end timestamp.
pub fn with_end_time(trace : Trace, end_time : Int64) -> Trace {
{
header: trace.header,
signals: trace.signals,
changes: trace.changes,
end_time: Int64::max(0L, end_time),
}
}
///|
/// Merge changes from two traces with the same declaration shape.
pub fn merge_traces(left : Trace, right : Trace) -> Trace {
let changes = left.changes + right.changes
let merged = sort_trace_changes({
header: left.header,
signals: left.signals,
changes,
end_time: Int64::max(left.end_time, right.end_time),
})
deduplicate_trace(merged)
}
///|
/// Keep one change at every `step`-sized time bucket.
pub fn decimate_trace(trace : Trace, step : Int64) -> Trace {
let safe_step = Int64::max(1L, step)
let seen : Map[(String, Int64), Bool] = Map([])
let result : Array[Change] = []
for change in trace.changes {
let bucket = change.time / safe_step
let key = (change.id, bucket)
if !seen.contains(key) {
seen[key] = true
result.push(change)
}
}
{
header: trace.header,
signals: trace.signals,
changes: result,
end_time: trace.end_time,
}
}
///|
/// Return whether a transformed trace still references declared identifiers.
pub fn transform_is_valid(trace : Trace) -> Bool {
validate(trace).length() == 0
}