94 lines
2.9 KiB
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
|
|
}
|