use std::sync::Mutex;
use std::time::Duration;
use tracing::Span;
use crate::ga::TerminationCause;
use crate::observer::{ExtensionEvent, GaObserver, IslandGaObserver, Nsga2Observer};
use crate::stats::GenerationStats;
use crate::traits::ChromosomeT;
pub struct TracingObserver {
run_span: Mutex<Option<Span>>,
gen_span: Mutex<Option<Span>>,
}
impl TracingObserver {
pub fn new() -> Self {
Self {
run_span: Mutex::new(None),
gen_span: Mutex::new(None),
}
}
}
impl Default for TracingObserver {
fn default() -> Self {
Self::new()
}
}
impl<U: ChromosomeT> GaObserver<U> for TracingObserver {
fn on_run_start(&self) {
let span = tracing::info_span!("ga_run");
tracing::info!(parent: &span, "ga run started");
if let Ok(mut guard) = self.run_span.lock() {
*guard = Some(span);
}
}
fn on_generation_start(&self, generation: usize) {
let run_guard = self.run_span.lock().ok();
let _run_entered = run_guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
let gen_span = tracing::debug_span!("ga_generation", generation);
if let Ok(mut guard) = self.gen_span.lock() {
*guard = Some(gen_span);
}
}
fn on_selection_complete(&self, generation: usize, duration: Duration, population_size: usize) {
let guard = self.gen_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
let duration_ms = duration.as_secs_f64() * 1000.0;
tracing::trace!(generation, duration_ms, population_size, "selection_complete");
}
fn on_crossover_complete(&self, generation: usize, duration: Duration, offspring_count: usize) {
let guard = self.gen_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
let duration_ms = duration.as_secs_f64() * 1000.0;
tracing::trace!(generation, duration_ms, offspring_count, "crossover_complete");
}
fn on_mutation_complete(&self, generation: usize, duration: Duration, population_size: usize) {
let guard = self.gen_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
let duration_ms = duration.as_secs_f64() * 1000.0;
tracing::trace!(generation, duration_ms, population_size, "mutation_complete");
}
fn on_fitness_evaluation_complete(&self, generation: usize, duration: Duration, population_size: usize) {
let guard = self.gen_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
let duration_ms = duration.as_secs_f64() * 1000.0;
tracing::trace!(generation, duration_ms, population_size, "fitness_evaluation_complete");
}
fn on_survivor_selection_complete(&self, generation: usize, duration: Duration, population_size: usize) {
let guard = self.gen_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
let duration_ms = duration.as_secs_f64() * 1000.0;
tracing::trace!(generation, duration_ms, population_size, "survivor_selection_complete");
}
fn on_new_best(&self, generation: usize, best: U) {
let guard = self.gen_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
let fitness = best.fitness();
tracing::info!(generation, fitness, "new_best");
}
fn on_stagnation(&self, generation: usize, stagnation_count: usize) {
let guard = self.gen_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
tracing::warn!(generation, stagnation_count, "stagnation detected");
}
fn on_extension_triggered(&self, event: ExtensionEvent) {
let guard = self.gen_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
tracing::info!(
generation = event.generation,
diversity = event.diversity,
extension_type = event.extension_type,
threshold = event.threshold,
"extension_triggered"
);
}
fn on_generation_end(&self, stats: &GenerationStats) {
let guard = self.gen_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
tracing::debug!(
generation = stats.generation,
best_fitness = stats.best_fitness,
avg_fitness = stats.avg_fitness,
worst_fitness = stats.worst_fitness,
fitness_std_dev = stats.fitness_std_dev,
population_size = stats.population_size,
diversity = stats.diversity,
"generation_end"
);
drop(_entered);
drop(guard);
if let Ok(mut gen_guard) = self.gen_span.lock() {
*gen_guard = None;
}
}
fn on_run_end(&self, cause: TerminationCause, all_stats: &[GenerationStats]) {
let guard = self.run_span.lock().ok();
let _entered = guard
.as_deref()
.and_then(|opt| opt.as_ref())
.map(|span| span.enter());
let total_generations = all_stats.len();
tracing::info!(cause = ?cause, total_generations, "ga run ended");
drop(_entered);
drop(guard);
if let Ok(mut run_guard) = self.run_span.lock() {
*run_guard = None;
}
}
}
impl<U: ChromosomeT> IslandGaObserver<U> for TracingObserver {}
impl<U: ChromosomeT> Nsga2Observer<U> for TracingObserver {}