mirror of
https://github.com/luxfi/vm.git
synced 2026-10-30 15:13:40 +00:00
104 lines
2.5 KiB
Go
104 lines
2.5 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package metrics
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/metric"
|
|
)
|
|
|
|
var (
|
|
_ MultiGatherer = (*prefixGatherer)(nil)
|
|
|
|
errOverlappingNamespaces = errors.New("prefix could create overlapping namespaces")
|
|
)
|
|
|
|
// NewPrefixGatherer returns a new MultiGatherer that merges metrics by adding a
|
|
// prefix to their names.
|
|
func NewPrefixGatherer() MultiGatherer {
|
|
return &prefixGatherer{}
|
|
}
|
|
|
|
type prefixGatherer struct {
|
|
multiGatherer
|
|
}
|
|
|
|
func (g *prefixGatherer) Register(prefix string, gatherer metric.Gatherer) error {
|
|
g.lock.Lock()
|
|
defer g.lock.Unlock()
|
|
|
|
for _, existingPrefix := range g.names {
|
|
if eitherIsPrefix(prefix, existingPrefix) {
|
|
return fmt.Errorf("%w: %q conflicts with %q",
|
|
errOverlappingNamespaces,
|
|
prefix,
|
|
existingPrefix,
|
|
)
|
|
}
|
|
}
|
|
|
|
g.register(
|
|
prefix,
|
|
&prefixedGatherer{
|
|
prefix: prefix,
|
|
gatherer: gatherer,
|
|
},
|
|
)
|
|
return nil
|
|
}
|
|
|
|
func (g *prefixGatherer) Deregister(prefix string) bool {
|
|
g.lock.Lock()
|
|
defer g.lock.Unlock()
|
|
|
|
for i, existingPrefix := range g.names {
|
|
if existingPrefix == prefix {
|
|
// Remove the gatherer and prefix
|
|
g.names = append(g.names[:i], g.names[i+1:]...)
|
|
g.gatherers = append(g.gatherers[:i], g.gatherers[i+1:]...)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type prefixedGatherer struct {
|
|
prefix string
|
|
gatherer metric.Gatherer
|
|
}
|
|
|
|
func (g *prefixedGatherer) Gather() ([]*metric.MetricFamily, error) {
|
|
// Gather returns partially filled metrics in the case of an error. So, it
|
|
// is expected to still return the metrics in the case an error is returned.
|
|
metricFamilies, err := g.gatherer.Gather()
|
|
for _, metricFamily := range metricFamilies {
|
|
originalName := metricFamily.Name
|
|
if originalName == "" {
|
|
// When the original name is empty, just use the prefix
|
|
metricFamily.Name = g.prefix
|
|
} else {
|
|
metricFamily.Name = metric.AppendNamespace(g.prefix, originalName)
|
|
}
|
|
}
|
|
return metricFamilies, err
|
|
}
|
|
|
|
// eitherIsPrefix returns true if either [a] is a prefix of [b] or [b] is a
|
|
// prefix of [a].
|
|
//
|
|
// This function accounts for the usage of the namespace boundary, so "hello" is
|
|
// not considered a prefix of "helloworld". However, "hello" is considered a
|
|
// prefix of "hello_world".
|
|
func eitherIsPrefix(a, b string) bool {
|
|
if len(a) > len(b) {
|
|
a, b = b, a
|
|
}
|
|
return a == b[:len(a)] && // a is a prefix of b
|
|
(len(a) == 0 || // a is empty
|
|
len(a) == len(b) || // a is equal to b
|
|
b[len(a)] == metric.NamespaceSeparatorByte) // a ends at a namespace boundary of b
|
|
}
|