fix
Build and Publish / Metadata (push) Successful in 10s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 2m15s

This commit is contained in:
mats committed 2026-09-20 11:39:20 +02:00
1 parent 645f923175
commit 2cf2cebf5c
20 files changed
+481 -51

No files matched your search

+37 -12
View File
@@ -41,6 +41,7 @@ type Event struct {
IsDuplicate bool // repeats an ancestor span's failure; not a new request
IsRootCause bool // error span with no erroring child
HasUpstream bool // an ingress span that had a child in another service
BadActor string // reason this event is attributed to a bad actor (see MarkBadActors); empty if not
}
// Config controls feature and label windows. A zero Config receives the
@@ -116,13 +117,24 @@ type Row struct {
TopClientShare float64 `json:"topClientShare"`
BotErrorShare float64 `json:"botErrorShare"`
// Clean errors are logical requests (no duplicate hops) not attributed to a
// bad actor. Label targets the clean future error rate, so scanner and
// crawler noise does not teach the model to alert; RawLabel is the old
// definition on raw error counts, kept for comparison.
BadActorErrors int `json:"badActorErrors"`
CleanErrors int `json:"cleanErrors"`
BaselineErrorRate float64 `json:"baselineErrorRate"`
BaselineCleanRate float64 `json:"baselineCleanRate"`
BaselineSamples int `json:"baselineSamples"`
FutureEventCount int `json:"futureEventCount"`
FutureErrorCount int `json:"futureErrorCount"`
FutureErrorRate float64 `json:"futureErrorRate"`
FutureCleanErrors int `json:"futureCleanErrors"`
FutureCleanRate float64 `json:"futureCleanRate"`
LabelKnown bool `json:"labelKnown"`
Label bool `json:"label"`
RawLabel bool `json:"rawLabel"`
}
// Build aggregates every service/release over rolling observation windows.
@@ -181,16 +193,18 @@ func Build(input []Event, cfg Config) []Row {
row.NewIssueCount++
}
}
futureTotal, futureErrors := count(events, start.Add(cfg.Window), start.Add(cfg.Window).Add(cfg.Future), row.Service)
baselineRate, samples := baseline(events, start, cfg.Window, cfg.BaselineDays, row.Service)
row.FutureEventCount, row.FutureErrorCount = futureTotal, futureErrors
futureTotal, futureErrors, futureClean := count(events, start.Add(cfg.Window), start.Add(cfg.Window).Add(cfg.Future), row.Service)
baselineRate, baselineClean, samples := baseline(events, start, cfg.Window, cfg.BaselineDays, row.Service)
row.FutureEventCount, row.FutureErrorCount, row.FutureCleanErrors = futureTotal, futureErrors, futureClean
row.FutureErrorRate = rate(futureErrors, futureTotal)
row.BaselineErrorRate, row.BaselineSamples = baselineRate, samples
row.FutureCleanRate = rate(futureClean, futureTotal)
row.BaselineErrorRate, row.BaselineCleanRate, row.BaselineSamples = baselineRate, baselineClean, samples
// A target is only known once the input has reached its exclusive
// end. This deliberately withholds the tail of an export instead of
// treating an incomplete future interval as a negative label.
row.LabelKnown = samples >= cfg.MinBaselineSamples && !start.Add(cfg.Window).Add(cfg.Future).After(observedUntil)
row.Label = row.LabelKnown && futureErrors >= cfg.MinFutureErrors && row.FutureErrorRate >= cfg.SpikeMultiple*baselineRate
row.RawLabel = row.LabelKnown && futureErrors >= cfg.MinFutureErrors && row.FutureErrorRate >= cfg.SpikeMultiple*baselineRate
row.Label = row.LabelKnown && futureClean >= cfg.MinFutureErrors && row.FutureCleanRate >= cfg.SpikeMultiple*baselineClean
rows = append(rows, row)
}
}
@@ -250,7 +264,12 @@ func (a *accumulator) add(e Event) {
a.value.ErrorCount++
if e.IsDuplicate {
a.value.DuplicateErrors++
} else if e.ClientIP != "" {
} else if e.BadActor != "" {
a.value.BadActorErrors++
} else {
a.value.CleanErrors++
}
if !e.IsDuplicate && e.ClientIP != "" {
a.clients[e.ClientIP]++
}
if e.IsRootCause {
@@ -310,35 +329,41 @@ func (a *accumulator) row() Row {
return a.value
}
func count(events []Event, start, end time.Time, service string) (total, errors int) {
// count returns events, raw errors and clean errors (logical requests not
// attributed to a bad actor) for a service in [start, end).
func count(events []Event, start, end time.Time, service string) (total, errors, clean int) {
for _, e := range events {
if e.Service == service && !e.Timestamp.Before(start) && e.Timestamp.Before(end) {
total++
if e.IsError {
errors++
if !e.IsDuplicate && e.BadActor == "" {
clean++
}
}
}
}
return total, errors
return total, errors, clean
}
// baseline samples the same UTC time window on the preceding calendar days.
// All intervals end before the current observation start, so no future data
// leaks into a training label.
func baseline(events []Event, start time.Time, window time.Duration, days int, service string) (float64, int) {
var total, errors, samples int
func baseline(events []Event, start time.Time, window time.Duration, days int, service string) (raw, clean float64, samples int) {
var total, errors, cleanErrors int
for day := 1; day <= days; day++ {
from := start.AddDate(0, 0, -day)
to := from.Add(window)
countTotal, countErrors := count(events, from, to, service)
countTotal, countErrors, countClean := count(events, from, to, service)
if countTotal == 0 {
continue
}
total += countTotal
errors += countErrors
cleanErrors += countClean
samples++
}
return rate(errors, total), samples
return rate(errors, total), rate(cleanErrors, total), samples
}
func percentile(values []float64, p float64) float64 {