EdgexAgent/device-ble-go/vendor/github.com/openziti/metrics/usage_registry.go
2025-07-10 20:40:32 +08:00

313 lines
8.6 KiB
Go

package metrics
import (
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/metrics/metrics_pb"
cmap "github.com/orcaman/concurrent-map/v2"
"reflect"
"sort"
"sync"
"time"
)
const (
DefaultIntervalAgeThreshold = 80 * time.Second
MinEventQueueSize = 16
DefaultEventQueueSize = 256
)
// Handler represents a sink for metric events
type Handler interface {
// AcceptMetrics is called when new metrics become available
AcceptMetrics(message *metrics_pb.MetricsMessage)
}
// UsageRegistry extends registry to allow collecting usage metrics
type UsageRegistry interface {
Registry
PollWithoutUsageMetrics() *metrics_pb.MetricsMessage
IntervalCounter(name string, intervalSize time.Duration) IntervalCounter
UsageCounter(name string, intervalSize time.Duration) UsageCounter
FlushToHandler(handler Handler)
StartReporting(eventSink Handler, reportInterval time.Duration, msgQueueSize int)
}
type UsageRegistryConfig struct {
SourceId string
Tags map[string]string
EventQueueSize int
CloseNotify <-chan struct{}
IntervalAgeThreshold time.Duration
}
func DefaultUsageRegistryConfig(sourceId string, closeNotify <-chan struct{}) UsageRegistryConfig {
return UsageRegistryConfig{
SourceId: sourceId,
Tags: map[string]string{},
CloseNotify: closeNotify,
EventQueueSize: DefaultEventQueueSize,
IntervalAgeThreshold: DefaultIntervalAgeThreshold,
}
}
func NewUsageRegistry(config UsageRegistryConfig) UsageRegistry {
if config.EventQueueSize < MinEventQueueSize {
config.EventQueueSize = MinEventQueueSize
}
if config.IntervalAgeThreshold <= 0 {
config.IntervalAgeThreshold = DefaultIntervalAgeThreshold
}
registry := &usageRegistryImpl{
registryImpl: registryImpl{
sourceId: config.SourceId,
tags: config.Tags,
metricMap: cmap.New[Metric](),
},
intervalMetrics: cmap.New[intervalMetric](),
eventChan: make(chan func(), config.EventQueueSize),
closeNotify: config.CloseNotify,
}
return registry
}
type bucketEvent struct {
interval *metrics_pb.MetricsMessage_IntervalCounter
name string
}
type intervalMetric interface {
flushIntervals()
}
type usageRegistryImpl struct {
registryImpl
intervalMetrics cmap.ConcurrentMap[string, intervalMetric]
eventChan chan func()
intervalBuckets []*bucketEvent
usageBuckets []*metrics_pb.MetricsMessage_UsageCounter
closeNotify <-chan struct{}
lock sync.Mutex
intervalAgeThreshold time.Duration
}
func (self *usageRegistryImpl) StartReporting(eventSink Handler, reportInterval time.Duration, msgQueueSize int) {
msgEvents := make(chan *messageBuilder, msgQueueSize)
go self.run(reportInterval, msgEvents)
go self.sendMsgs(eventSink, msgEvents)
}
// IntervalCounter creates an IntervalCounter
func (self *usageRegistryImpl) IntervalCounter(name string, intervalSize time.Duration) IntervalCounter {
self.lock.Lock()
defer self.lock.Unlock()
metric, present := self.metricMap.Get(name)
if present {
intervalCounter, ok := metric.(IntervalCounter)
if !ok {
panic(fmt.Errorf("metric '%v' already exists and is not an interval counter. It is a %v", name, reflect.TypeOf(metric).Name()))
}
return intervalCounter
}
disposeF := func() {
self.dispose(name)
self.intervalMetrics.Remove(name)
}
intervalCounter := newIntervalCounter(name, intervalSize, self.intervalAgeThreshold, self.eventChan, self, disposeF)
self.metricMap.Set(name, intervalCounter)
self.intervalMetrics.Set(name, intervalCounter)
return intervalCounter
}
// UsageCounter creates a UsageCounter
func (self *usageRegistryImpl) UsageCounter(name string, intervalSize time.Duration) UsageCounter {
self.lock.Lock()
defer self.lock.Unlock()
metric, present := self.metricMap.Get(name)
if present {
counter, ok := metric.(UsageCounter)
if !ok {
panic(fmt.Errorf("metric '%v' already exists and is not a usage counter. It is a %v", name, reflect.TypeOf(metric).Name()))
}
return counter
}
disposeF := func() { self.dispose(name) }
usageCounter := newUsageCounter(name, intervalSize, self.intervalAgeThreshold, self, disposeF, self.eventChan)
self.metricMap.Set(name, usageCounter)
self.intervalMetrics.Set(name, usageCounter)
return usageCounter
}
func (self *usageRegistryImpl) Poll() *metrics_pb.MetricsMessage {
base := self.registryImpl.Poll()
if base == nil && self.intervalBuckets == nil {
return nil
}
var builder *messageBuilder
if base == nil {
builder = newMessageBuilder(self.sourceId, self.tags)
} else {
builder = (*messageBuilder)(base)
}
builder.addIntervalBucketEvents(self.intervalBuckets)
self.intervalBuckets = nil
builder.UsageCounters = self.usageBuckets
self.usageBuckets = nil
sort.Slice(builder.UsageCounters, func(i, j int) bool {
return builder.UsageCounters[i].IntervalStartUTC < builder.UsageCounters[j].IntervalStartUTC
})
return (*metrics_pb.MetricsMessage)(builder)
}
func (self *usageRegistryImpl) PollWithoutUsageMetrics() *metrics_pb.MetricsMessage {
return self.registryImpl.Poll()
}
func (self *usageRegistryImpl) reportInterval(counter *intervalCounterImpl, intervalStartUTC int64, values map[string]uint64) {
bucket := &metrics_pb.MetricsMessage_IntervalBucket{
IntervalStartUTC: intervalStartUTC,
Values: values,
}
interval := &metrics_pb.MetricsMessage_IntervalCounter{
IntervalLength: uint64(counter.intervalSize.Seconds()),
Buckets: []*metrics_pb.MetricsMessage_IntervalBucket{bucket},
}
self.intervalBuckets = append(self.intervalBuckets, &bucketEvent{
interval: interval,
name: counter.name,
})
}
func (self *usageRegistryImpl) reportUsage(intervalStartUTC int64, intervalLength time.Duration, values map[string]*usageSet) {
counter := &metrics_pb.MetricsMessage_UsageCounter{
IntervalStartUTC: intervalStartUTC,
IntervalLength: uint64(intervalLength.Seconds()),
Buckets: map[string]*metrics_pb.MetricsMessage_UsageBucket{},
}
for k, v := range values {
counter.Buckets[k] = &metrics_pb.MetricsMessage_UsageBucket{
Values: v.values,
Tags: v.tags,
}
}
self.usageBuckets = append(self.usageBuckets, counter)
}
func (self *usageRegistryImpl) run(reportInterval time.Duration, msgEvents chan *messageBuilder) {
ticker := time.NewTicker(reportInterval)
defer ticker.Stop()
for {
select {
case event := <-self.eventChan:
event()
case <-ticker.C:
select {
case msgEvents <- self.flushAndPoll():
case <-self.closeNotify:
return
}
case <-self.closeNotify:
self.DisposeAll()
return
}
}
}
func (self *usageRegistryImpl) flushAndPoll() *messageBuilder {
for entry := range self.intervalMetrics.IterBuffered() {
entry.Val.flushIntervals()
}
builder := newMessageBuilder(self.sourceId, self.tags)
if len(self.intervalBuckets) > 0 {
builder.addIntervalBucketEvents(self.intervalBuckets)
self.intervalBuckets = nil
}
if len(self.usageBuckets) > 0 {
builder.UsageCounters = self.usageBuckets
self.usageBuckets = nil
sort.Slice(builder.UsageCounters, func(i, j int) bool {
return builder.UsageCounters[i].IntervalStartUTC < builder.UsageCounters[j].IntervalStartUTC
})
}
return builder
}
func (registry *usageRegistryImpl) pollAppend(builder *messageBuilder) *metrics_pb.MetricsMessage {
if len(builder.IntervalCounters) == 0 && len(builder.UsageCounters) == 0 && registry.metricMap.Count() == 0 {
return nil
}
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)
}
func (self *usageRegistryImpl) sendMsgs(eventSink Handler, msgEvents chan *messageBuilder) {
for {
select {
case builder := <-msgEvents:
msg := self.pollAppend(builder)
eventSink.AcceptMetrics(msg)
case <-self.closeNotify:
return
}
}
}
func (self *usageRegistryImpl) FlushAndPoll() *metrics_pb.MetricsMessage {
msgC := make(chan *metrics_pb.MetricsMessage, 1)
self.eventChan <- func() {
builder := self.flushAndPoll()
msg := self.pollAppend(builder)
msgC <- msg
}
msg := <-msgC
return msg
}
func (self *usageRegistryImpl) FlushToHandler(handler Handler) {
if msg := self.FlushAndPoll(); msg != nil {
handler.AcceptMetrics(msg)
}
}