165 lines
5.5 KiB
Go
165 lines
5.5 KiB
Go
package diagnostics
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
)
|
|
|
|
const (
|
|
MaxWarningGroups = 128
|
|
MaxNonWarningGroups = 256
|
|
)
|
|
|
|
// Aggregator merges origin-enriched diagnostics in caller-supplied canonical
|
|
// order. Its zero value is ready for use.
|
|
type Aggregator struct {
|
|
groups []contracts.DiagnosticGroup
|
|
indices map[groupKey]int
|
|
warningGroups int
|
|
nonWarningGroups int
|
|
warningOccurrences int
|
|
nonWarningOccurrences int
|
|
unrepresentedOccurrences int
|
|
}
|
|
|
|
// Add validates and incorporates one final diagnostic group. Actionable
|
|
// warnings cannot overflow; later non-warning groups are represented by exact
|
|
// unrepresented-occurrence metadata once their fixed bound is reached.
|
|
func (aggregator *Aggregator) Add(group contracts.DiagnosticGroup) error {
|
|
if err := group.Validate(); err != nil {
|
|
return fmt.Errorf("diagnostic group: %w", err)
|
|
}
|
|
if aggregator.indices == nil {
|
|
aggregator.indices = make(map[groupKey]int)
|
|
}
|
|
key := groupKeyFromGroup(group)
|
|
if index, exists := aggregator.indices[key]; exists {
|
|
if err := aggregator.checkOccurrenceTotal(group.Disposition, group.OccurrenceCount); err != nil {
|
|
return err
|
|
}
|
|
if err := aggregator.merge(index, group); err != nil {
|
|
return err
|
|
}
|
|
aggregator.addOccurrenceTotal(group.Disposition, group.OccurrenceCount)
|
|
return nil
|
|
}
|
|
if group.Disposition == contracts.DiagnosticDispositionWarning {
|
|
if aggregator.warningGroups >= MaxWarningGroups {
|
|
return errors.New("diagnostic warning groups exceed maximum count")
|
|
}
|
|
} else if aggregator.nonWarningGroups >= MaxNonWarningGroups {
|
|
if err := aggregator.checkOccurrenceTotal(group.Disposition, group.OccurrenceCount); err != nil {
|
|
return err
|
|
}
|
|
aggregator.addOccurrenceTotal(group.Disposition, group.OccurrenceCount)
|
|
return aggregator.addUnrepresented(group.OccurrenceCount)
|
|
}
|
|
if err := aggregator.checkOccurrenceTotal(group.Disposition, group.OccurrenceCount); err != nil {
|
|
return err
|
|
}
|
|
if group.Disposition == contracts.DiagnosticDispositionWarning {
|
|
aggregator.warningGroups++
|
|
} else {
|
|
aggregator.nonWarningGroups++
|
|
}
|
|
aggregator.addOccurrenceTotal(group.Disposition, group.OccurrenceCount)
|
|
aggregator.indices[key] = len(aggregator.groups)
|
|
aggregator.groups = append(aggregator.groups, contracts.CloneDiagnosticCollection(contracts.DiagnosticCollection{Groups: []contracts.DiagnosticGroup{group}}).Groups[0])
|
|
return nil
|
|
}
|
|
|
|
func (aggregator *Aggregator) checkOccurrenceTotal(disposition contracts.DiagnosticDisposition, count int) error {
|
|
current := aggregator.nonWarningOccurrences
|
|
if disposition == contracts.DiagnosticDispositionWarning {
|
|
current = aggregator.warningOccurrences
|
|
}
|
|
if count > maximumInt()-current {
|
|
return errors.New("diagnostic occurrence count overflow")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (aggregator *Aggregator) addOccurrenceTotal(disposition contracts.DiagnosticDisposition, count int) {
|
|
if disposition == contracts.DiagnosticDispositionWarning {
|
|
aggregator.warningOccurrences += count
|
|
return
|
|
}
|
|
aggregator.nonWarningOccurrences += count
|
|
}
|
|
|
|
// Collection returns an independently owned grouped result in first-occurrence
|
|
// order.
|
|
func (aggregator *Aggregator) Collection() contracts.DiagnosticCollection {
|
|
if aggregator == nil {
|
|
return contracts.DiagnosticCollection{}
|
|
}
|
|
return contracts.CloneDiagnosticCollection(contracts.DiagnosticCollection{
|
|
Groups: aggregator.groups,
|
|
Truncated: aggregator.unrepresentedOccurrences > 0,
|
|
UnrepresentedOccurrenceCount: aggregator.unrepresentedOccurrences,
|
|
})
|
|
}
|
|
|
|
func (aggregator *Aggregator) merge(index int, incoming contracts.DiagnosticGroup) error {
|
|
current := &aggregator.groups[index]
|
|
if incoming.OccurrenceCount > maximumInt()-current.OccurrenceCount {
|
|
return errors.New("diagnostic occurrence count overflow")
|
|
}
|
|
current.OccurrenceCount += incoming.OccurrenceCount
|
|
for _, sample := range incoming.Samples {
|
|
if len(current.Samples) == contracts.MaxDiagnosticSamples || containsGroupSample(current.Samples, sample) {
|
|
continue
|
|
}
|
|
current.Samples = append(current.Samples, cloneGroupSample(sample))
|
|
}
|
|
current.OmittedSampleCount = current.OccurrenceCount - len(current.Samples)
|
|
return nil
|
|
}
|
|
|
|
func (aggregator *Aggregator) addUnrepresented(count int) error {
|
|
if count > maximumInt()-aggregator.unrepresentedOccurrences {
|
|
return errors.New("diagnostic unrepresented occurrence count overflow")
|
|
}
|
|
aggregator.unrepresentedOccurrences += count
|
|
return nil
|
|
}
|
|
|
|
func containsGroupSample(samples []contracts.DiagnosticSample, candidate contracts.DiagnosticSample) bool {
|
|
for _, sample := range samples {
|
|
if sample.Scope != candidate.Scope || sample.Message != candidate.Message || sample.ChunkID != candidate.ChunkID {
|
|
continue
|
|
}
|
|
if sample.ChunkIndex == nil || candidate.ChunkIndex == nil {
|
|
if sample.ChunkIndex == candidate.ChunkIndex {
|
|
return true
|
|
}
|
|
continue
|
|
}
|
|
if *sample.ChunkIndex == *candidate.ChunkIndex {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func cloneGroupSample(sample contracts.DiagnosticSample) contracts.DiagnosticSample {
|
|
if sample.ChunkIndex != nil {
|
|
value := *sample.ChunkIndex
|
|
sample.ChunkIndex = &value
|
|
}
|
|
return sample
|
|
}
|
|
|
|
type groupKey struct {
|
|
disposition contracts.DiagnosticDisposition
|
|
category contracts.DiagnosticCategory
|
|
reasonCode string
|
|
origin contracts.DiagnosticOrigin
|
|
}
|
|
|
|
func groupKeyFromGroup(group contracts.DiagnosticGroup) groupKey {
|
|
return groupKey{disposition: group.Disposition, category: group.Category, reasonCode: group.ReasonCode, origin: group.Origin}
|
|
}
|