use crate::lifecycle::EventType;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LifecycleState {
Pending,
Running,
Completed,
Failed(String),
}
pub trait ReadoutContext {
fn subject_name(&self) -> &str;
fn subject_seq(&self) -> Option<(usize, usize)> {
None
}
fn subject_labels(&self) -> &str {
""
}
fn subject_exec_id(&self) -> u64 {
crate::execution_context::current_exec_id()
}
fn subject_id(&self) -> String {
let name = self.subject_name();
let labels = self.subject_labels();
if labels.is_empty() {
name.to_string()
} else {
format!("{name}@{labels}")
}
}
fn activity_name(&self) -> &str {
self.subject_name()
}
fn cycles_completed(&self) -> u64 {
0
}
fn cycles_total(&self) -> u64 {
0
}
fn ops_ok(&self) -> u64 {
0
}
fn skips(&self) -> u64 {
0
}
fn errors(&self) -> u64 {
0
}
fn retries(&self) -> u64 {
0
}
fn attempt_ok(&self) -> u64 {
0
}
fn attempt_failed(&self) -> u64 {
0
}
fn concurrency(&self) -> usize {
0
}
fn elapsed_secs(&self) -> f64 {
0.0
}
fn consumed(&self) -> u64 {
0
}
fn rows_consumed(&self) -> u64 {
0
}
fn rows_total(&self) -> u64 {
0
}
fn ops_started(&self) -> u64 {
0
}
fn ops_finished(&self) -> u64 {
self.cycles_completed()
}
fn eta_secs(&self) -> Option<f64> {
None
}
fn open_ended(&self) -> bool {
false
}
fn latency_p50_nanos(&self) -> u64 {
0
}
fn latency_p99_nanos(&self) -> u64 {
0
}
fn progress_fraction(&self) -> Option<f64> {
if let Some(f) = self.progress_override() {
return Some(f.clamp(0.0, 1.0));
}
if self.open_ended() {
return None;
}
let (rc, rt) = (self.rows_consumed(), self.rows_total());
if rt > 0 {
return Some((rc as f64 / rt as f64).clamp(0.0, 1.0));
}
let t = self.cycles_total();
if t > 0 {
return Some((self.cycles_completed() as f64 / t as f64).clamp(0.0, 1.0));
}
None
}
fn progress_override(&self) -> Option<f64> {
None
}
fn status_metric_chips(&self) -> String {
String::new()
}
fn adapter_counters_text(&self) -> String {
String::new()
}
fn batch_info_text(&self) -> String {
String::new()
}
fn depth_indent(&self) -> &str {
""
}
fn use_color(&self) -> bool {
false
}
fn phase_memo(&self) -> &str {
""
}
fn event(&self) -> EventType;
fn refresh_tick(&self) -> u64 {
0
}
fn subject_state(&self) -> LifecycleState {
LifecycleState::Running
}
fn outcome(&self) -> crate::phase_outcome::Outcome {
crate::phase_outcome::Outcome::completed()
}
fn outcome_errors(&self) -> &[crate::phase_outcome::PhaseErrorDetail] {
&[]
}
fn outcome_resume_cursor(&self) -> Option<&crate::phase_outcome::ResumeCursor> {
None
}
fn session_scenario_name(&self) -> &str {
""
}
fn session_workload_file(&self) -> &str {
""
}
fn stick_reattached_session(&self) -> &str {
""
}
fn session_phases_completed(&self) -> usize {
0
}
fn session_phases_failed(&self) -> usize {
0
}
fn session_phases_pending(&self) -> usize {
0
}
fn session_phases_total(&self) -> usize {
0
}
fn session_phases_truncated(&self) -> usize {
0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lifecycle::SubjectKind;
struct PhaseLikeCtx {
name: String,
labels: String,
}
impl ReadoutContext for PhaseLikeCtx {
fn subject_name(&self) -> &str {
&self.name
}
fn subject_labels(&self) -> &str {
&self.labels
}
fn event(&self) -> EventType {
EventType::PhaseEnd
}
}
#[test]
fn default_subject_id_collapses_to_name_when_no_labels() {
let ctx = PhaseLikeCtx {
name: "setup".into(),
labels: String::new(),
};
assert_eq!(ctx.subject_id(), "setup");
}
#[test]
fn default_subject_id_appends_labels_with_at_sign() {
let ctx = PhaseLikeCtx {
name: "ann_query".into(),
labels: "(profile=alpha), (k=10)".into(),
};
assert_eq!(ctx.subject_id(), "ann_query@(profile=alpha), (k=10)");
}
struct SessionLikeCtx;
impl ReadoutContext for SessionLikeCtx {
fn subject_name(&self) -> &str {
"session"
}
fn subject_id(&self) -> String {
"session".to_string()
}
fn event(&self) -> EventType {
EventType::SessionEnd
}
}
#[test]
fn session_context_overrides_subject_id_to_literal() {
let ctx = SessionLikeCtx;
assert_eq!(ctx.subject_id(), "session");
assert_eq!(ctx.event().subject_kind(), SubjectKind::Session);
}
#[test]
fn subject_kind_as_str_round_trips_via_table() {
assert_eq!(SubjectKind::Phase.as_str(), "phase");
assert_eq!(SubjectKind::Session.as_str(), "session");
assert_eq!(SubjectKind::Iteration.as_str(), "iteration");
assert_eq!(SubjectKind::Scope.as_str(), "scope");
}
}