include db for gitea build
Build and Publish / Metadata (push) Successful in 10s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 2m16s

This commit is contained in:
mats committed 2026-09-20 11:01:56 +02:00
1 parent 8de4a2e010
commit 645f923175
33 files changed
+1490 -150

No files matched your search

+51 -1
View File
@@ -7,6 +7,8 @@ package features
import (
"sort"
"time"
"git.tornberg.me/go-otel/internal/uaclass"
)
// Event is the subset of a normalized event used by the detector. Text fields
@@ -31,6 +33,14 @@ type Event struct {
Route string
StatusCode int
ExceptionType string
// Trace-resolved context (see internal/tracectx). All optional: zero
// values mean "not resolved", which behaves like the pre-resolution data.
ClientIP string // real client behind proxy hops, when known
UAClass string // internal/uaclass class of the User-Agent
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
}
// Config controls feature and label windows. A zero Config receives the
@@ -94,6 +104,18 @@ type Row struct {
TimeoutCount int `json:"timeoutCount"`
CancellationCount int `json:"cancellationCount"`
// Logical-request view: DuplicateErrors are hops repeating an ancestor's
// failure, so LogicalErrors = ErrorCount - DuplicateErrors is the number of
// failed requests. DistinctClients / TopClientShare separate one noisy
// client from broad impact; BotErrorShare is the share of errors whose
// User-Agent classifies as a self-declared non-human client.
LogicalErrors int `json:"logicalErrors"`
DuplicateErrors int `json:"duplicateErrors"`
RootCauseErrors int `json:"rootCauseErrors"`
DistinctClients int `json:"distinctClients"`
TopClientShare float64 `json:"topClientShare"`
BotErrorShare float64 `json:"botErrorShare"`
BaselineErrorRate float64 `json:"baselineErrorRate"`
BaselineSamples int `json:"baselineSamples"`
FutureEventCount int `json:"futureEventCount"`
@@ -189,10 +211,13 @@ type accumulator struct {
durations []float64
traces map[string]struct{}
issues map[string]struct{}
clients map[string]int
botErrors int
classed int
}
func newAccumulator(start, end time.Time, service, release string) *accumulator {
return &accumulator{value: Row{WindowStart: start, WindowEnd: end, Service: service, Release: release}, traces: map[string]struct{}{}, issues: map[string]struct{}{}}
return &accumulator{value: Row{WindowStart: start, WindowEnd: end, Service: service, Release: release}, traces: map[string]struct{}{}, issues: map[string]struct{}{}, clients: map[string]int{}}
}
func (a *accumulator) add(e Event) {
@@ -223,6 +248,20 @@ func (a *accumulator) add(e Event) {
return
}
a.value.ErrorCount++
if e.IsDuplicate {
a.value.DuplicateErrors++
} else if e.ClientIP != "" {
a.clients[e.ClientIP]++
}
if e.IsRootCause {
a.value.RootCauseErrors++
}
if e.UAClass != "" && !e.IsDuplicate {
a.classed++
if uaclass.IsBot(e.UAClass) {
a.botErrors++
}
}
if e.Source == "frontend" {
a.value.FrontendErrors++
} else {
@@ -252,6 +291,17 @@ func issueKey(e Event) string {
func (a *accumulator) row() Row {
a.value.ErrorRate = rate(a.value.ErrorCount, a.value.TotalEvents)
a.value.UniqueTraceCount, a.value.UniqueIssueCount = len(a.traces), len(a.issues)
a.value.LogicalErrors = a.value.ErrorCount - a.value.DuplicateErrors
a.value.DistinctClients = len(a.clients)
top, known := 0, 0
for _, n := range a.clients {
known += n
if n > top {
top = n
}
}
a.value.TopClientShare = rate(top, known)
a.value.BotErrorShare = rate(a.botErrors, a.classed)
if len(a.durations) != 0 {
sort.Float64s(a.durations)
a.value.P50Duration = percentile(a.durations, .50)