2022-02-06 07:06:32 +00:00

139 lines
3.6 KiB
Go

// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.
// Package metrics defines a concurrently-accessible metrics collector.
//
// A *metrics.M value exports methods to track integer counters and maximum
// values. A metric has a caller-assigned string name that is not interpreted
// by the collector except to locate its stored value.
package metrics
import "sync"
// An M collects counters and maximum value trackers. A nil *M is valid, and
// discards all metrics. The methods of an *M are safe for concurrent use by
// multiple goroutines.
type M struct {
mu sync.Mutex
counter map[string]int64
maxVal map[string]int64
label map[string]interface{}
}
// New creates a new, empty metrics collector.
func New() *M {
return &M{
counter: make(map[string]int64),
maxVal: make(map[string]int64),
label: make(map[string]interface{}),
}
}
// Count adds n to the current value of the counter named, defining the counter
// if it does not already exist.
func (m *M) Count(name string, n int64) {
if m != nil {
m.mu.Lock()
defer m.mu.Unlock()
m.counter[name] += n
}
}
// SetMaxValue sets the maximum value metric named to the greater of n and its
// current value, defining the value if it does not already exist.
func (m *M) SetMaxValue(name string, n int64) {
if m != nil {
m.mu.Lock()
defer m.mu.Unlock()
if old, ok := m.maxVal[name]; !ok || n > old {
m.maxVal[name] = n
}
}
}
// CountAndSetMax adds n to the current value of the counter named, and also
// updates a max value tracker with the same name in a single step.
func (m *M) CountAndSetMax(name string, n int64) {
if m != nil {
m.mu.Lock()
defer m.mu.Unlock()
if old, ok := m.maxVal[name]; !ok || n > old {
m.maxVal[name] = n
}
m.counter[name] += n
}
}
// SetLabel sets the specified label to value. If value == nil the label is
// removed from the set.
//
// As a special case, if value has the concrete type
//
// func() interface{}
//
// then the value of the label is obtained by calling that function.
func (m *M) SetLabel(name string, value interface{}) {
if m != nil {
m.mu.Lock()
defer m.mu.Unlock()
if value == nil {
delete(m.label, name)
} else {
m.label[name] = value
}
}
}
// EditLabel calls edit with the current value of the specified label.
// The value returned by edit replaces the contents of the label.
// If edit returns nil, the label is removed from the set.
// If the label did not exist, the argument to edit is nil.
func (m *M) EditLabel(name string, edit func(interface{}) interface{}) {
if m != nil {
m.mu.Lock()
defer m.mu.Unlock()
newValue := edit(m.label[name])
if newValue == nil {
delete(m.label, name)
} else {
m.label[name] = newValue
}
}
}
// Snapshot copies an atomic snapshot of the collected metrics into the non-nil
// fields of the provided snapshot value. Only the fields of snap that are not
// nil are snapshotted.
func (m *M) Snapshot(snap Snapshot) {
if m != nil {
m.mu.Lock()
defer m.mu.Unlock()
if c := snap.Counter; c != nil {
for name, val := range m.counter {
c[name] = val
}
}
if v := snap.MaxValue; v != nil {
for name, val := range m.maxVal {
v[name] = val
}
}
if v := snap.Label; v != nil {
for name, val := range m.label {
if fn, ok := val.(func() interface{}); ok {
v[name] = fn()
} else {
v[name] = val
}
}
}
}
}
// A Snapshot represents a point-in-time snapshot of a metrics collector. The
// fields of this type are filled in by the Snapshot method of *M.
type Snapshot struct {
Counter map[string]int64
MaxValue map[string]int64
Label map[string]interface{}
}