Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions pkg/agentdrain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,11 @@ Describes anomalies detected for a log line.

```go
type AnomalyReport struct {
IsNewTemplate bool // Line created a new cluster
LowSimilarity bool // Best match score was below SimThreshold
RareCluster bool // Matched cluster has been seen ≤ RareClusterThreshold times
NewClusterCreated bool // This event produced a brand-new cluster
AnomalyScore float64 // Weighted composite score in [0, 1]
Reason string // Human-readable anomaly description
IsNewTemplate bool // Line produced a brand-new log cluster
LowSimilarity bool // Best match score was below SimThreshold
RareCluster bool // Matched cluster has been seen ≤ RareClusterThreshold times
AnomalyScore float64 // Weighted composite score in [0, 1]
Reason string // Human-readable anomaly description
}
```

Expand Down
3 changes: 1 addition & 2 deletions pkg/agentdrain/anomaly.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ func NewAnomalyDetector(simThreshold float64, rareClusterThreshold int) (*Anomal
// - cluster is the cluster that was matched or created.
func (d *AnomalyDetector) Analyze(result *MatchResult, isNew bool, cluster *Cluster) *AnomalyReport {
report := &AnomalyReport{
IsNewTemplate: isNew,
NewClusterCreated: isNew,
IsNewTemplate: isNew,
// LowSimilarity is mutually exclusive with IsNewTemplate: brand-new templates are
// already classified as anomalies, so we only evaluate similarity for existing ones.
LowSimilarity: !isNew && result.Similarity < d.threshold,
Expand Down
18 changes: 1 addition & 17 deletions pkg/agentdrain/anomaly_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew bool
cluster *Cluster
wantIsNewTemplate bool
wantNewCluster bool
wantLowSimilarity bool
wantRareCluster bool
wantScore float64
wantReason string
}{
{
// isNew=true → both IsNewTemplate and NewClusterCreated; size=1 ≤ rareThreshold=2 → RareCluster.
// isNew=true → IsNewTemplate; size=1 ≤ rareThreshold=2 → RareCluster.
// score = (1.0 + 0.3) / 2.0 = 0.65
name: "new template creates cluster and is also rare",
simThreshold: 0.4,
Expand All @@ -34,7 +33,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew: true,
cluster: &Cluster{ID: 1, Template: []string{"stage=plan"}, Size: 1},
wantIsNewTemplate: true,
wantNewCluster: true,
wantLowSimilarity: false,
wantRareCluster: true,
wantScore: 0.65,
Expand All @@ -50,7 +48,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew: false,
cluster: &Cluster{ID: 1, Template: []string{"a", "b", "c"}, Size: 5},
wantIsNewTemplate: false,
wantNewCluster: false,
wantLowSimilarity: true,
wantRareCluster: false,
wantScore: 0.35,
Expand All @@ -66,7 +63,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew: false,
cluster: &Cluster{ID: 1, Template: []string{"a"}, Size: 1},
wantIsNewTemplate: false,
wantNewCluster: false,
wantLowSimilarity: false,
wantRareCluster: true,
wantScore: 0.15,
Expand All @@ -81,7 +77,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew: false,
cluster: &Cluster{ID: 1, Template: []string{"a", "b"}, Size: 100},
wantIsNewTemplate: false,
wantNewCluster: false,
wantLowSimilarity: false,
wantRareCluster: false,
wantScore: 0.0,
Expand All @@ -96,7 +91,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew: false,
cluster: &Cluster{ID: 1, Template: []string{"a"}, Size: 5},
wantIsNewTemplate: false,
wantNewCluster: false,
wantLowSimilarity: false,
wantRareCluster: false,
wantScore: 0.0,
Expand All @@ -112,7 +106,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew: false,
cluster: &Cluster{ID: 1, Template: []string{"a"}, Size: 5},
wantIsNewTemplate: false,
wantNewCluster: false,
wantLowSimilarity: true,
wantRareCluster: false,
wantScore: 0.35,
Expand All @@ -128,7 +121,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew: false,
cluster: &Cluster{ID: 1, Template: []string{"a"}, Size: 1},
wantIsNewTemplate: false,
wantNewCluster: false,
wantLowSimilarity: true,
wantRareCluster: true,
wantScore: 0.5,
Expand All @@ -143,7 +135,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew: false,
cluster: nil,
wantIsNewTemplate: false,
wantNewCluster: false,
wantLowSimilarity: false,
wantRareCluster: false,
wantScore: 0.0,
Expand All @@ -159,7 +150,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {
isNew: true,
cluster: &Cluster{ID: 1, Template: []string{"a"}, Size: 5},
wantIsNewTemplate: true,
wantNewCluster: true,
wantLowSimilarity: false,
wantRareCluster: true,
wantScore: 0.65,
Expand All @@ -177,7 +167,6 @@ func TestAnomalyDetector_Analyze(t *testing.T) {

require.NotNil(t, report, "Analyze should always return a non-nil report")
assert.Equal(t, tt.wantIsNewTemplate, report.IsNewTemplate, "IsNewTemplate mismatch")
assert.Equal(t, tt.wantNewCluster, report.NewClusterCreated, "NewClusterCreated mismatch")
assert.Equal(t, tt.wantLowSimilarity, report.LowSimilarity, "LowSimilarity mismatch")
assert.Equal(t, tt.wantRareCluster, report.RareCluster, "RareCluster mismatch")
assert.InDelta(t, tt.wantScore, report.AnomalyScore, 1e-9, "AnomalyScore mismatch")
Expand Down Expand Up @@ -342,28 +331,24 @@ func TestAnalyzeEvent(t *testing.T) {
name string
event AgentEvent
wantIsNew bool
wantNewCluster bool
errorDescription string
}{
{
name: "first occurrence is flagged as new template",
event: evtPlan,
wantIsNew: true,
wantNewCluster: true,
errorDescription: "first event",
},
{
name: "second identical occurrence is not flagged as new",
event: evtPlan,
wantIsNew: false,
wantNewCluster: false,
errorDescription: "second identical event",
},
{
name: "distinct event creates its own new template",
event: evtFinish,
wantIsNew: true,
wantNewCluster: true,
errorDescription: "distinct event",
},
}
Expand All @@ -375,7 +360,6 @@ func TestAnalyzeEvent(t *testing.T) {
require.NotNil(t, result, "AnalyzeEvent should return a non-nil result")
require.NotNil(t, report, "AnalyzeEvent should return a non-nil report")
assert.Equal(t, tt.wantIsNew, report.IsNewTemplate, "IsNewTemplate mismatch")
assert.Equal(t, tt.wantNewCluster, report.NewClusterCreated, "NewClusterCreated mismatch")
})
}
}
3 changes: 1 addition & 2 deletions pkg/agentdrain/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ func TestSpec_Types_MatchResult(t *testing.T) {
}

// TestSpec_Types_AnomalyReport validates the documented AnomalyReport type structure.
// Spec: IsNewTemplate, LowSimilarity, RareCluster, NewClusterCreated, AnomalyScore in [0,1], Reason.
// Spec: IsNewTemplate, LowSimilarity, RareCluster, AnomalyScore in [0,1], Reason.
func TestSpec_Types_AnomalyReport(t *testing.T) {
cfg := agentdrain.DefaultConfig()
miner, err := agentdrain.NewMiner(cfg)
Expand All @@ -439,7 +439,6 @@ func TestSpec_Types_AnomalyReport(t *testing.T) {
_ = report.IsNewTemplate
_ = report.LowSimilarity
_ = report.RareCluster
_ = report.NewClusterCreated
_ = report.Reason
assert.GreaterOrEqual(t, report.AnomalyScore, 0.0, "AnomalyReport.AnomalyScore should be in documented range [0, 1]")
assert.LessOrEqual(t, report.AnomalyScore, 1.0, "AnomalyReport.AnomalyScore should be in documented range [0, 1]")
Expand Down
4 changes: 1 addition & 3 deletions pkg/agentdrain/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,12 @@ type MatchResult struct {

// AnomalyReport describes anomalies detected for a log line.
type AnomalyReport struct {
// IsNewTemplate is true when the log line created a new cluster.
// IsNewTemplate is true when the log line produced a brand-new log cluster.
IsNewTemplate bool
// LowSimilarity is true when the best match score was below the configured threshold.
LowSimilarity bool
// RareCluster is true when the matched cluster has been seen fewer times than the rare threshold.
RareCluster bool
// NewClusterCreated is true when this event produced a brand-new cluster.
NewClusterCreated bool
// AnomalyScore is a weighted composite score in the range [0, 1].
AnomalyScore float64
// Reason is a human-readable description of all anomalies that were detected.
Expand Down