///|
/// Kind of event in an auditable measurement run.
pub(all) enum TraceEventKind {
PlanStarted
PlanStep
ObservationRecorded
DecisionIssued
WarningIssued
PlanFinished
} derive(Eq, Debug)
///|
/// One immutable trace event.
pub(all) struct TraceEvent {
kind : TraceEventKind
part_id : String
feature_id : String
step_index : Int
message : String
}
///|
/// Ordered trace log for a plan run.
pub(all) struct TraceLog {
part_id : String
events : Array[TraceEvent]
}
///|
/// Create a trace log containing lifecycle and step events.
pub fn trace_log_from_plan(plan : MeasurementPlan) -> TraceLog {
let events = [
{
kind: TraceEventKind::PlanStarted,
part_id: plan.part_id,
feature_id: "",
step_index: -1,
message: "plan started",
},
]
for i, step in plan.steps {
events.push({
kind: TraceEventKind::PlanStep,
part_id: plan.part_id,
feature_id: step.feature_id,
step_index: i,
message: "scheduled on " + step.device_id,
})
}
events.push({
kind: TraceEventKind::PlanFinished,
part_id: plan.part_id,
feature_id: "",
step_index: -1,
message: "plan finished",
})
{ part_id: plan.part_id, events }
}
///|
/// Append an event to a trace log.
pub fn trace_append(log : TraceLog, event : TraceEvent) -> TraceLog {
let events = []
for item in log.events {
events.push(item)
}
events.push(event)
{ part_id: log.part_id, events }
}
///|
/// Count events of one kind.
pub fn trace_event_count(log : TraceLog, kind : TraceEventKind) -> Int {
let mut result = 0
for event in log.events {
if event.kind == kind {
result += 1
}
}
result
}
///|
/// Check lifecycle markers and step index ordering.
pub fn trace_is_complete(log : TraceLog) -> Bool {
if log.events.length() < 2 {
false
} else {
let first = log.events[0].kind == TraceEventKind::PlanStarted
let last = log.events[log.events.length() - 1].kind ==
TraceEventKind::PlanFinished
first && last && trace_steps_are_ordered(log)
}
}
///|
fn trace_steps_are_ordered(log : TraceLog) -> Bool {
let mut expected = 0
let mut result = true
for event in log.events {
if event.kind == TraceEventKind::PlanStep {
if event.step_index != expected {
result = false
}
expected += 1
}
}
result
}
///|
/// Check whether every event belongs to a part identifier.
pub fn trace_has_part(log : TraceLog, part_id : String) -> Bool {
if log.part_id != part_id {
false
} else {
let mut result = true
for event in log.events {
if event.part_id != part_id {
result = false
}
}
result
}
}
///|
/// Return feature IDs represented by trace step events.
pub fn trace_feature_ids(log : TraceLog) -> Array[String] {
let result = []
for event in log.events {
if event.kind == TraceEventKind::PlanStep {
result.push(event.feature_id)
}
}
result
}
///|
/// Return the number of events associated with a feature.
pub fn trace_feature_event_count(log : TraceLog, feature_id : String) -> Int {
let mut result = 0
for event in log.events {
if event.feature_id == feature_id {
result += 1
}
}
result
}
///|
/// Return whether a warning event is present.
pub fn trace_has_warning(log : TraceLog) -> Bool {
trace_event_count(log, TraceEventKind::WarningIssued) > 0
}
///|
/// Return all step indices in trace order.
pub fn trace_step_indices(log : TraceLog) -> Array[Int] {
let result = []
for event in log.events {
if event.kind == TraceEventKind::PlanStep {
result.push(event.step_index)
}
}
result
}
///|
/// Return a stable one-line trace summary.
pub fn trace_summary(log : TraceLog) -> String {
log.part_id +
" events=" +
log.events.length().to_string() +
" steps=" +
trace_event_count(log, TraceEventKind::PlanStep).to_string() +
" warnings=" +
trace_event_count(log, TraceEventKind::WarningIssued).to_string()
}
///|
/// Return whether a trace contains a feature step.
pub fn trace_has_feature(log : TraceLog, feature_id : String) -> Bool {
trace_feature_event_count(log, feature_id) > 0
}