commit for version used in evaluation of thesis
This commit is contained in:
commit
72635dc7b9
27 changed files with 6084 additions and 0 deletions
148
internal/detect/interface.go
Normal file
148
internal/detect/interface.go
Normal 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:
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue