commit for version used in evaluation of thesis

This commit is contained in:
Patryk Hegenberg 2026-03-29 10:03:18 +02:00
commit 72635dc7b9
27 changed files with 6084 additions and 0 deletions

View file

@ -0,0 +1,148 @@
package detect
import (
"context"
"log"
"sync"
"time"
"codeberg.org/pata1704/guenther/pkg/types"
)
// AnomalyDetector is the common interface for all detection algorithms.
// Implementations must be safe for concurrent use.
type AnomalyDetector interface {
// Fit trains the model on the supplied slice of labelled-normal vectors.
Fit(vectors []types.FeatureVector) error
// Score returns an anomaly assessment for vector. It must not block.
Score(vector types.FeatureVector) (types.AnomalyResult, error)
// Update buffers vector for incremental model updates.
Update(vector types.FeatureVector) error
}
// DetectionLayer reads FeatureVectors from inputChan, scores them with the
// configured AnomalyDetector, and forwards AnomalyResults to outputChan.
//
// The layer runs a single event-loop goroutine (no additional worker pool is
// needed because detection is CPU-bound in a single model, not I/O-bound).
// Health metrics are emitted to healthChan every 5 seconds.
//
// Backpressure: if outputChan is full the result is dropped and a warning is
// logged. This prevents the detection goroutine from blocking the upstream
// TransformEngine via backpressure handling.
type DetectionLayer struct {
detector AnomalyDetector
inputChan <-chan types.FeatureVector
outputChan chan<- types.AnomalyResult
healthChan chan<- types.StageHealth
scalingController *ScalingController // optional
wg sync.WaitGroup
mu sync.Mutex
processed uint64
dropped uint64
avgLatency float64
}
// NewDetectionLayer constructs a DetectionLayer wired to the given channels.
func NewDetectionLayer(
detector AnomalyDetector,
input <-chan types.FeatureVector,
output chan<- types.AnomalyResult,
health chan<- types.StageHealth,
) *DetectionLayer {
return &DetectionLayer{
detector: detector,
inputChan: input,
outputChan: output,
healthChan: health,
}
}
// SetScalingController attaches an auto-scaling controller to the layer.
func (l *DetectionLayer) SetScalingController(sc *ScalingController) {
l.scalingController = sc
}
// Start launches the detection event loop in a background goroutine.
// The method is idempotent: calling Start twice panics (close of closed channel).
func (l *DetectionLayer) Start(ctx context.Context) {
l.wg.Go(func() {
reportTicker := time.NewTicker(5 * time.Second)
defer reportTicker.Stop()
for {
select {
case fv := <-l.inputChan:
l.handle(fv)
case <-reportTicker.C:
l.emitHealth()
case <-ctx.Done():
return
}
}
})
}
// Wait waits for the event loop to exit after context cancellation.
func (l *DetectionLayer) Wait() {
l.wg.Wait()
}
func (l *DetectionLayer) handle(fv types.FeatureVector) {
if l.scalingController != nil {
l.scalingController.ObserveCPU(fv.AvgCPUPercent)
}
start := time.Now()
result, err := l.detector.Score(fv)
ms := time.Since(start).Seconds() * 1e3
l.mu.Lock()
l.processed++
if l.avgLatency == 0 {
l.avgLatency = ms
} else {
l.avgLatency = l.avgLatency*0.8 + ms*0.2
}
l.mu.Unlock()
if err != nil {
log.Printf("detection: score error: %v", err)
return
}
select {
case l.outputChan <- result:
default:
l.mu.Lock()
l.dropped++
l.mu.Unlock()
log.Printf("detection: output channel full dropping result (score=%.4f)", result.Score)
}
}
// emitHealth sends a StageHealth snapshot to healthChan.
// Non-blocking: skips the report if healthChan is full.
func (l *DetectionLayer) emitHealth() {
l.mu.Lock()
p := l.processed
d := l.dropped
avg := l.avgLatency
l.mu.Unlock()
select {
case l.healthChan <- types.StageHealth{
StageName: "detection_layer",
EventsProcessed: p,
EventsDropped: d,
AvgLatencyMs: avg,
LastUpdate: time.Now(),
}:
default:
}
}