creating better system for stats
- goal is to have near realtime stats being recorded - allows for different objects with stats (host, VM, etc.) to create it's own stat provideres, and register them with a collector that gathers the stats on an interval
This commit is contained in:
parent
48bdc94351
commit
7945a32ac1
10
lib/host/stats.go
Normal file
10
lib/host/stats.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package host
|
||||||
|
|
||||||
|
import "git.staur.ca/stobbsm/clustvirt/lib/stats"
|
||||||
|
|
||||||
|
// CollectMemUsage sends the current used memory of the host
|
||||||
|
// over the given channel
|
||||||
|
func (h *Host) CollectMemUsage(u chan<- stats.StatValue) error {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
146
lib/stats/stats.go
Normal file
146
lib/stats/stats.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package stats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Number interface {
|
||||||
|
constraints.Integer | constraints.Float
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatFetcher represents a data collector. It is given a channel to send a Number
|
||||||
|
// to, being the metric that is being collected.
|
||||||
|
type StatFetcher[T Number] func(chan<- Stat[T])
|
||||||
|
|
||||||
|
// StatFetcherList is a map of collector types, where the key is the label to use
|
||||||
|
// for the statistic
|
||||||
|
type StatFetcherList[T Number] map[string]StatFetcher[T]
|
||||||
|
|
||||||
|
// StatProvider is something that has the Stats method and provides stats
|
||||||
|
type StatProvider[T Number] interface {
|
||||||
|
Stats() StatFetcherList[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatCollector is something that has the Collect method, recieving a StatProvider to collect from
|
||||||
|
type StatCollector[T Number] interface {
|
||||||
|
Collect(StatProvider[T])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat represents a single statistic
|
||||||
|
type Stat[T Number] struct {
|
||||||
|
Name string
|
||||||
|
Value T
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatsCollector controls running stats
|
||||||
|
type StatsCollector[T Number] struct {
|
||||||
|
ctx context.Context
|
||||||
|
ctxCancel context.CancelFunc
|
||||||
|
collectors StatFetcherList[T]
|
||||||
|
interval time.Duration
|
||||||
|
rchan chan Stat[T]
|
||||||
|
running bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCollector adds a collector to the existing list of collectors. If `name` is already defined,
|
||||||
|
// will return an error but still replace the collector. If the StatsCollector is running, it is restarted.
|
||||||
|
func (sc *StatsCollector[T]) AddCollector(name string, collector StatFetcher[T], restart bool) error {
|
||||||
|
var err error
|
||||||
|
if _, ok := sc.collectors[name]; !ok {
|
||||||
|
err = fmt.Errorf("replacing collector %s", name)
|
||||||
|
}
|
||||||
|
sc.collectors[name] = collector
|
||||||
|
if sc.IsRunning() {
|
||||||
|
sc.Restart()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeCollectors is a utility function to merge a Collectors map with another one, allowing for
|
||||||
|
// better localization of code and avoiding repeating the same function all over the place when
|
||||||
|
// an object with Collector functions wants to add them
|
||||||
|
func (sc *StatsCollector[T]) MergeCollectors(collectors StatFetcherList[T]) error {
|
||||||
|
var errList []error
|
||||||
|
for k, c := range collectors {
|
||||||
|
errList = append(errList, sc.AddCollector(k, c, false))
|
||||||
|
}
|
||||||
|
sc.Restart()
|
||||||
|
return errors.Join(errList...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect satisfys the StatCollector interface. It is given a StatProvider, and those stats are then
|
||||||
|
// merged with the existing providers, running on the same interval
|
||||||
|
func (sc *StatsCollector[T]) Collect(sp StatProvider[T]) {
|
||||||
|
sc.MergeCollectors(sp.Stats())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRunning is a utility function that returns true if the StatsCollector is running
|
||||||
|
func (sc *StatsCollector[T]) IsRunning() bool { return sc.running }
|
||||||
|
|
||||||
|
// RunningStats returns a channel of type Stat, and receives a context and a list
|
||||||
|
// of collectors that get run every 'd' duration
|
||||||
|
func (sc *StatsCollector[T]) Start() {
|
||||||
|
sc.ctx, sc.ctxCancel = context.WithCancel(context.Background())
|
||||||
|
sc.rchan = make(chan Stat[T])
|
||||||
|
ticker := time.NewTicker(sc.interval)
|
||||||
|
|
||||||
|
// start the goroutine that does the actual work
|
||||||
|
go func() {
|
||||||
|
defer func() { sc.running = false }()
|
||||||
|
select {
|
||||||
|
case <-sc.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
for _, f := range sc.collectors {
|
||||||
|
f(sc.rchan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
sc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the collection of stats
|
||||||
|
func (sc *StatsCollector[T]) Stop() {
|
||||||
|
defer close(sc.rchan)
|
||||||
|
sc.ctxCancel()
|
||||||
|
<-sc.ctx.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart stops the StatsCollector,
|
||||||
|
func (sc *StatsCollector[T]) Restart() {
|
||||||
|
if sc.IsRunning() {
|
||||||
|
sc.Stop()
|
||||||
|
}
|
||||||
|
if !sc.IsRunning() {
|
||||||
|
sc.Start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatList is a simple map[string]Stat tht allows for easy lookup of stats by name
|
||||||
|
// Getting all stats can still be done using a range loop
|
||||||
|
type StatList[T Number] map[string]Stat[T]
|
||||||
|
|
||||||
|
func (sl StatList[T]) Get(name string) Stat[T] {
|
||||||
|
if v, ok := sl[name]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return Stat[T]{
|
||||||
|
Name: "UNDEFINED",
|
||||||
|
Value: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl StatList[T]) Set(name string, s Stat[T]) {
|
||||||
|
sl[name] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrometheusMetrics writes the stats in a format prometheus understands to an io.Writer
|
||||||
|
func (sl StatList[T]) PrometheusMetrics(w io.Writer) {
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user