Files
notarius/internal/framework/diagnostics/collector.go

94 lines
2.9 KiB
Go

// Package diagnostics provides bounded local grouping for producer and
// validator diagnostic results.
package diagnostics
import (
"errors"
"fmt"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)
// Collector merges local producer diagnostics by their semantic identity. Its
// zero value is ready for use.
type Collector struct {
diagnostics []contracts.ProducerDiagnostic
indices map[key]int
}
// NewCollector returns an empty local diagnostic collector.
func NewCollector() *Collector {
return &Collector{}
}
// Add validates and merges one producer diagnostic. Every occurrence remains
// counted, while the first three distinct samples in input order are retained.
func (collector *Collector) Add(diagnostic contracts.ProducerDiagnostic) error {
if err := diagnostic.Validate(); err != nil {
return fmt.Errorf("producer diagnostic: %w", err)
}
if collector.indices == nil {
collector.indices = make(map[key]int)
}
diagnosticKey := key{disposition: diagnostic.Disposition, category: diagnostic.Category, reasonCode: diagnostic.ReasonCode}
index, exists := collector.indices[diagnosticKey]
if !exists {
if len(collector.diagnostics) >= contracts.MaxProducerDiagnosticGroups {
return errors.New("producer diagnostics exceed maximum group count")
}
collector.indices[diagnosticKey] = len(collector.diagnostics)
collector.diagnostics = append(collector.diagnostics, contracts.CloneProducerDiagnostics([]contracts.ProducerDiagnostic{diagnostic})[0])
return nil
}
current := &collector.diagnostics[index]
if diagnostic.OccurrenceCount > maximumInt()-current.OccurrenceCount {
return errors.New("producer diagnostic occurrence count overflow")
}
current.OccurrenceCount += diagnostic.OccurrenceCount
for _, sample := range diagnostic.Samples {
if len(current.Samples) == contracts.MaxDiagnosticSamples || containsSample(current.Samples, sample) {
continue
}
current.Samples = append(current.Samples, cloneSample(sample))
}
current.OmittedSampleCount = current.OccurrenceCount - len(current.Samples)
return nil
}
// Diagnostics returns an independently owned snapshot in first-occurrence
// order.
func (collector *Collector) Diagnostics() []contracts.ProducerDiagnostic {
if collector == nil {
return nil
}
return contracts.CloneProducerDiagnostics(collector.diagnostics)
}
func containsSample(samples []contracts.DiagnosticSample, candidate contracts.DiagnosticSample) bool {
for _, sample := range samples {
if sample.Scope == candidate.Scope && sample.Message == candidate.Message {
return true
}
}
return false
}
func cloneSample(sample contracts.DiagnosticSample) contracts.DiagnosticSample {
if sample.ChunkIndex != nil {
chunkIndex := *sample.ChunkIndex
sample.ChunkIndex = &chunkIndex
}
return sample
}
func maximumInt() int {
return int(^uint(0) >> 1)
}
type key struct {
disposition contracts.DiagnosticDisposition
category contracts.DiagnosticCategory
reasonCode string
}