366 lines
9.4 KiB
Go
366 lines
9.4 KiB
Go
/*
|
|
Copyright NetFoundry Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package metrics
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/openziti/metrics/metrics_pb"
|
|
cmap "github.com/orcaman/concurrent-map/v2"
|
|
"github.com/rcrowley/go-metrics"
|
|
)
|
|
|
|
// Metric is the base functionality for all metrics types
|
|
type Metric interface {
|
|
Dispose()
|
|
}
|
|
|
|
// Registry allows for configuring and accessing metrics for an application
|
|
type Registry interface {
|
|
// SourceId returns the source id of this Registry
|
|
SourceId() string
|
|
|
|
// Gauge returns a Gauge for the given name. If one does not yet exist, one will be created
|
|
Gauge(name string) Gauge
|
|
|
|
// FuncGauge returns a Gauge for the given name. If one does not yet exist, one will be created using
|
|
// the given function
|
|
FuncGauge(name string, f func() int64) Gauge
|
|
|
|
// Meter returns a Meter for the given name. If one does not yet exist, one will be created
|
|
Meter(name string) Meter
|
|
|
|
// Histogram returns a Histogram for the given name. If one does not yet exist, one will be created
|
|
Histogram(name string) Histogram
|
|
|
|
// Timer returns a Timer for the given name. If one does not yet exist, one will be created
|
|
Timer(name string) Timer
|
|
|
|
// EachMetric calls the given visitor function for each Metric in this registry
|
|
EachMetric(visitor func(name string, metric Metric))
|
|
|
|
// GetGauge returns the Gauge for the given name or nil if a Gauge with that name doesn't exist
|
|
GetGauge(name string) Gauge
|
|
|
|
// GetMeter returns the Meter for the given name or nil if a Meter with that name doesn't exist
|
|
GetMeter(name string) Meter
|
|
|
|
// GetHistogram returns the Histogram for the given name or nil if a Histogram with that name doesn't exist
|
|
GetHistogram(name string) Histogram
|
|
|
|
// GetTimer returns the Timer for the given name or nil if a Timer with that name doesn't exist
|
|
GetTimer(name string) Timer
|
|
|
|
// IsValidMetric returns true if a metric with the given name exists in the registry, false otherwise
|
|
IsValidMetric(name string) bool
|
|
|
|
// Poll returns a MetricsMessage with a snapshot of the metrics in the Registry
|
|
Poll() *metrics_pb.MetricsMessage
|
|
|
|
// DisposeAll removes and cleans up all metrics currently in the Registry
|
|
DisposeAll()
|
|
}
|
|
|
|
func NewRegistry(sourceId string, tags map[string]string) Registry {
|
|
return ®istryImpl{
|
|
sourceId: sourceId,
|
|
tags: tags,
|
|
metricMap: cmap.New[any](),
|
|
}
|
|
}
|
|
|
|
type registryImpl struct {
|
|
sourceId string
|
|
tags map[string]string
|
|
metricMap cmap.ConcurrentMap[string, any]
|
|
}
|
|
|
|
func (registry *registryImpl) dispose(name string) {
|
|
registry.metricMap.Remove(name)
|
|
}
|
|
|
|
func (registry *registryImpl) DisposeAll() {
|
|
registry.EachMetric(func(name string, metric Metric) {
|
|
metric.Dispose()
|
|
})
|
|
}
|
|
|
|
func (registry *registryImpl) IsValidMetric(name string) bool {
|
|
return registry.metricMap.Has(name)
|
|
}
|
|
|
|
func (registry *registryImpl) SourceId() string {
|
|
return registry.sourceId
|
|
}
|
|
|
|
func (registry *registryImpl) GetGauge(name string) Gauge {
|
|
metric, found := registry.metricMap.Get(name)
|
|
if !found {
|
|
return nil
|
|
}
|
|
if gauge, ok := metric.(Gauge); ok {
|
|
return gauge
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (registry *registryImpl) GetMeter(name string) Meter {
|
|
metric, found := registry.metricMap.Get(name)
|
|
if !found {
|
|
return nil
|
|
}
|
|
if meter, ok := metric.(Meter); ok {
|
|
return meter
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (registry *registryImpl) GetHistogram(name string) Histogram {
|
|
metric, found := registry.metricMap.Get(name)
|
|
if !found {
|
|
return nil
|
|
}
|
|
if histogram, ok := metric.(Histogram); ok {
|
|
return histogram
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (registry *registryImpl) GetTimer(name string) Timer {
|
|
metric, found := registry.metricMap.Get(name)
|
|
if !found {
|
|
return nil
|
|
}
|
|
if timer, ok := metric.(Timer); ok {
|
|
return timer
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getOrCreateMetric[T Metric](registry *registryImpl, name string, newMetric func() T) T {
|
|
var result T
|
|
for {
|
|
metric, present := registry.metricMap.Get(name)
|
|
if present {
|
|
var ok bool
|
|
result, ok = metric.(T)
|
|
if !ok {
|
|
panic(fmt.Errorf("metric '%v' already exists and is not a %T. It is a %T", name, new(T), metric))
|
|
}
|
|
return result
|
|
}
|
|
|
|
result = newMetric()
|
|
if registry.metricMap.SetIfAbsent(name, result) {
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
|
|
func (registry *registryImpl) Gauge(name string) Gauge {
|
|
return getOrCreateMetric(registry, name, func() Gauge {
|
|
return &gaugeImpl{
|
|
Gauge: metrics.NewGauge(),
|
|
dispose: func() {
|
|
registry.dispose(name)
|
|
},
|
|
}
|
|
})
|
|
}
|
|
|
|
func (registry *registryImpl) FuncGauge(name string, f func() int64) Gauge {
|
|
return getOrCreateMetric(registry, name, func() Gauge {
|
|
return &gaugeImpl{
|
|
Gauge: metrics.NewFunctionalGauge(f),
|
|
dispose: func() {
|
|
registry.dispose(name)
|
|
},
|
|
}
|
|
})
|
|
}
|
|
|
|
func (registry *registryImpl) newMeter(name string) *meterImpl {
|
|
return &meterImpl{
|
|
Meter: metrics.NewMeter(),
|
|
registry: registry,
|
|
name: name,
|
|
}
|
|
}
|
|
|
|
func (registry *registryImpl) Meter(name string) Meter {
|
|
metric := registry.getRefCounted(name, func() refCounted {
|
|
return registry.newMeter(name)
|
|
})
|
|
|
|
meter, ok := metric.(Meter)
|
|
if !ok {
|
|
panic(fmt.Errorf("metric '%v' already exists and is not a meter. It is a %v", name, reflect.TypeOf(metric).Name()))
|
|
}
|
|
return meter
|
|
}
|
|
|
|
func (registry *registryImpl) newHistogram(name string) *histogramImpl {
|
|
return &histogramImpl{
|
|
Histogram: metrics.NewHistogram(metrics.NewExpDecaySample(128, 0.015)),
|
|
registry: registry,
|
|
name: name,
|
|
}
|
|
}
|
|
|
|
func (registry *registryImpl) Histogram(name string) Histogram {
|
|
metric := registry.getRefCounted(name, func() refCounted {
|
|
return registry.newHistogram(name)
|
|
})
|
|
|
|
histogram, ok := metric.(Histogram)
|
|
if !ok {
|
|
panic(fmt.Errorf("metric '%v' already exists and is not a histogram. It is a %v", name, reflect.TypeOf(metric).Name()))
|
|
}
|
|
return histogram
|
|
}
|
|
|
|
func (registry *registryImpl) getRefCounted(name string, factory func() refCounted) refCounted {
|
|
metric := registry.metricMap.Upsert(name, nil, func(exist bool, valueInMap interface{}, newValue interface{}) interface{} {
|
|
if exist {
|
|
if h, ok := valueInMap.(refCounted); ok {
|
|
h.IncrRefCount()
|
|
}
|
|
return valueInMap
|
|
}
|
|
|
|
newVal := factory()
|
|
newVal.IncrRefCount()
|
|
return newVal
|
|
})
|
|
|
|
histogram, ok := metric.(refCounted)
|
|
if !ok {
|
|
panic(fmt.Errorf("metric '%v' already exists and is not an instance of refCouted. It is a %v", name, reflect.TypeOf(metric).Name()))
|
|
}
|
|
return histogram
|
|
}
|
|
|
|
func (registry *registryImpl) disposeRefCounted(metric refCounted) {
|
|
registry.metricMap.RemoveCb(metric.Name(), func(key string, v interface{}, exists bool) bool {
|
|
if !exists {
|
|
return true
|
|
}
|
|
return v == metric && metric.DecrRefCount() < 1
|
|
})
|
|
}
|
|
|
|
func (registry *registryImpl) Timer(name string) Timer {
|
|
return getOrCreateMetric(registry, name, func() Timer {
|
|
return &timerImpl{
|
|
Timer: metrics.NewTimer(),
|
|
dispose: func() {
|
|
registry.dispose(name)
|
|
},
|
|
}
|
|
})
|
|
}
|
|
|
|
func (registry *registryImpl) EachMetric(visitor func(name string, metric Metric)) {
|
|
for entry := range registry.metricMap.IterBuffered() {
|
|
visitor(entry.Key, entry.Val.(Metric))
|
|
}
|
|
}
|
|
|
|
func (registry *registryImpl) Each(visitor func(string, interface{})) {
|
|
for entry := range registry.metricMap.IterBuffered() {
|
|
visitor(entry.Key, entry.Val)
|
|
}
|
|
}
|
|
|
|
// Provide rest of go-metrics Registry interface, so we can use go-metrics reporters if desired
|
|
func (registry *registryImpl) Get(s string) interface{} {
|
|
val, _ := registry.metricMap.Get(s)
|
|
return val
|
|
}
|
|
|
|
func (registry *registryImpl) GetAll() map[string]map[string]interface{} {
|
|
return nil
|
|
}
|
|
|
|
func (registry *registryImpl) GetOrRegister(s string, i interface{}) interface{} {
|
|
return registry.metricMap.Upsert(s, i, func(exist bool, valueInMap interface{}, newValue interface{}) interface{} {
|
|
if exist {
|
|
return valueInMap
|
|
}
|
|
return newValue
|
|
})
|
|
}
|
|
|
|
func (registry *registryImpl) Register(s string, i interface{}) error {
|
|
if registry.metricMap.SetIfAbsent(s, i) {
|
|
return fmt.Errorf("duplicate metric %v", s)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (registry *registryImpl) RunHealthchecks() {
|
|
}
|
|
|
|
func (registry *registryImpl) Unregister(s string) {
|
|
registry.metricMap.Remove(s)
|
|
}
|
|
|
|
func (registry *registryImpl) UnregisterAll() {
|
|
for _, key := range registry.metricMap.Keys() {
|
|
registry.Unregister(key)
|
|
}
|
|
}
|
|
|
|
func (registry *registryImpl) Poll() *metrics_pb.MetricsMessage {
|
|
// If there's nothing to report, skip it
|
|
if registry.metricMap.Count() == 0 {
|
|
return nil
|
|
}
|
|
|
|
builder := newMessageBuilder(registry.sourceId, registry.tags)
|
|
|
|
registry.EachMetric(func(name string, i Metric) {
|
|
switch metric := i.(type) {
|
|
case *gaugeImpl:
|
|
builder.addIntGauge(name, metric.Snapshot())
|
|
case *meterImpl:
|
|
builder.addMeter(name, metric.Snapshot())
|
|
case *histogramImpl:
|
|
builder.addHistogram(name, metric.Snapshot())
|
|
case *timerImpl:
|
|
builder.addTimer(name, metric.Snapshot())
|
|
case *intervalCounterImpl:
|
|
// ignore, handled below
|
|
case *usageCounterImpl:
|
|
// ignore, handled below
|
|
default:
|
|
pfxlog.Logger().Errorf("Unsupported metric type %v", reflect.TypeOf(i))
|
|
}
|
|
})
|
|
|
|
return (*metrics_pb.MetricsMessage)(builder)
|
|
}
|
|
|
|
type refCounted interface {
|
|
IncrRefCount() int32
|
|
DecrRefCount() int32
|
|
Name() string
|
|
stop()
|
|
}
|