Files
node/service/metrics/prefix_gatherer.go
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

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
}