open-consul/agent/consul/usagemetrics/usagemetrics.go

298 lines
8.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package usagemetrics
import (
"context"
"errors"
"time"
"github.com/armon/go-metrics/prometheus"
"github.com/armon/go-metrics"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/serf/serf"
"github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/logging"
"github.com/hashicorp/consul/version"
)
var Gauges = []prometheus.GaugeDefinition{
{
Name: []string{"consul", "state", "nodes"},
Help: "Deprecated - please use state_nodes instead.",
},
{
Name: []string{"state", "nodes"},
Help: "Measures the current number of nodes registered with Consul. It is only emitted by Consul servers. Added in v1.9.0.",
},
{
Name: []string{"consul", "state", "peerings"},
Help: "Deprecated - please use state_peerings instead.",
},
{
Name: []string{"state", "peerings"},
Help: "Measures the current number of peerings registered with Consul. It is only emitted by Consul servers. Added in v1.13.0.",
},
{
Name: []string{"consul", "state", "services"},
Help: "Deprecated - please use state_services instead.",
},
{
Name: []string{"state", "services"},
Help: "Measures the current number of unique services registered with Consul, based on service name. It is only emitted by Consul servers. Added in v1.9.0.",
},
{
Name: []string{"consul", "state", "service_instances"},
Help: "Deprecated - please use state_service_instances instead.",
},
{
Name: []string{"state", "service_instances"},
Help: "Measures the current number of unique services registered with Consul, based on service name. It is only emitted by Consul servers. Added in v1.9.0.",
},
{
Name: []string{"consul", "members", "clients"},
Help: "Deprecated - please use members_clients instead.",
},
{
Name: []string{"members", "clients"},
Help: "Measures the current number of client agents registered with Consul. It is only emitted by Consul servers. Added in v1.9.6.",
},
{
Name: []string{"consul", "members", "servers"},
Help: "Deprecated - please use members_servers instead.",
},
{
Name: []string{"members", "servers"},
Help: "Measures the current number of server agents registered with Consul. It is only emitted by Consul servers. Added in v1.9.6.",
},
{
Name: []string{"consul", "state", "kv_entries"},
Help: "Deprecated - please use kv_entries instead.",
},
{
Name: []string{"state", "kv_entries"},
Help: "Measures the current number of entries in the Consul KV store. It is only emitted by Consul servers. Added in v1.10.3.",
},
{
Name: []string{"consul", "state", "connect_instances"},
Help: "Deprecated - please use state_connect_instances instead.",
},
{
Name: []string{"state", "connect_instances"},
Help: "Measures the current number of unique connect service instances registered with Consul, labeled by Kind. It is only emitted by Consul servers. Added in v1.10.4.",
},
{
Name: []string{"consul", "state", "config_entries"},
Help: "Deprecated - please use state_config_entries instead.",
},
{
Name: []string{"state", "config_entries"},
Help: "Measures the current number of unique configuration entries registered with Consul, labeled by Kind. It is only emitted by Consul servers. Added in v1.10.4.",
},
{
Name: []string{"state", "billable_service_instances"},
Help: "Total number of billable service instances in the local datacenter.",
},
{
Name: []string{"version"},
Help: "Represents the Consul version.",
},
}
type getMembersFunc func() []serf.Member
// Config holds the settings for various parameters for the
// UsageMetricsReporter
type Config struct {
logger hclog.Logger
metricLabels []metrics.Label
stateProvider StateProvider
tickerInterval time.Duration
getMembersFunc getMembersFunc
}
// WithDatacenter adds the datacenter as a label to all metrics emitted by the
// UsageMetricsReporter
func (c *Config) WithDatacenter(dc string) *Config {
c.metricLabels = append(c.metricLabels, metrics.Label{Name: "datacenter", Value: dc})
return c
}
// WithLogger takes a logger and creates a new, named sub-logger to use when
// running
func (c *Config) WithLogger(logger hclog.Logger) *Config {
c.logger = logger.Named(logging.UsageMetrics)
return c
}
// WithReportingInterval specifies the interval on which UsageMetricsReporter
// should emit metrics
func (c *Config) WithReportingInterval(dur time.Duration) *Config {
c.tickerInterval = dur
return c
}
func (c *Config) WithStateProvider(sp StateProvider) *Config {
c.stateProvider = sp
return c
}
// WithGetMembersFunc specifies the function used to identify cluster members
func (c *Config) WithGetMembersFunc(fn getMembersFunc) *Config {
c.getMembersFunc = fn
return c
}
// StateProvider defines an inteface for retrieving a state.Store handle. In
// non-test code, this is satisfied by the fsm.FSM struct.
type StateProvider interface {
State() *state.Store
}
// UsageMetricsReporter provides functionality for emitting usage metrics into
// the metrics stream. This makes it essentially a translation layer
// between the state store and metrics stream.
type UsageMetricsReporter struct {
logger hclog.Logger
metricLabels []metrics.Label
stateProvider StateProvider
tickerInterval time.Duration
getMembersFunc getMembersFunc
}
func NewUsageMetricsReporter(cfg *Config) (*UsageMetricsReporter, error) {
if cfg.stateProvider == nil {
return nil, errors.New("must provide a StateProvider to usage reporter")
}
if cfg.getMembersFunc == nil {
return nil, errors.New("must provide a getMembersFunc to usage reporter")
}
if cfg.logger == nil {
cfg.logger = hclog.NewNullLogger()
}
if cfg.tickerInterval == 0 {
// Metrics are aggregated every 10 seconds, so we default to that.
cfg.tickerInterval = 10 * time.Second
}
u := &UsageMetricsReporter{
logger: cfg.logger,
stateProvider: cfg.stateProvider,
metricLabels: cfg.metricLabels,
tickerInterval: cfg.tickerInterval,
getMembersFunc: cfg.getMembersFunc,
}
return u, nil
}
// Run must be run in a goroutine, and can be stopped by closing or sending
// data to the passed in shutdownCh
func (u *UsageMetricsReporter) Run(ctx context.Context) {
ticker := time.NewTicker(u.tickerInterval)
for {
select {
case <-ctx.Done():
u.logger.Debug("usage metrics reporter shutting down")
ticker.Stop()
return
case <-ticker.C:
u.runOnce()
}
}
}
func (u *UsageMetricsReporter) runOnce() {
u.logger.Trace("Starting usage run")
state := u.stateProvider.State()
_, nodeUsage, err := state.NodeUsage()
if err != nil {
u.logger.Warn("failed to retrieve nodes from state store", "error", err)
}
u.emitNodeUsage(nodeUsage)
_, peeringUsage, err := state.PeeringUsage()
if err != nil {
u.logger.Warn("failed to retrieve peerings from state store", "error", err)
}
u.emitPeeringUsage(peeringUsage)
_, serviceUsage, err := state.ServiceUsage(nil)
if err != nil {
u.logger.Warn("failed to retrieve services from state store", "error", err)
}
u.emitServiceUsage(serviceUsage)
members := u.memberUsage()
u.emitMemberUsage(members)
_, kvUsage, err := state.KVUsage()
if err != nil {
u.logger.Warn("failed to retrieve kv entry usage from state store", "error", err)
}
u.emitKVUsage(kvUsage)
_, configUsage, err := state.ConfigEntryUsage()
if err != nil {
u.logger.Warn("failed to retrieve config usage from state store", "error", err)
}
u.emitConfigEntryUsage(configUsage)
u.emitVersion()
}
func (u *UsageMetricsReporter) memberUsage() []serf.Member {
if u.getMembersFunc == nil {
return nil
}
mems := u.getMembersFunc()
if len(mems) <= 0 {
u.logger.Warn("cluster reported zero members")
}
out := make([]serf.Member, 0, len(mems))
for _, m := range mems {
if m.Status != serf.StatusAlive {
continue
}
out = append(out, m)
}
return out
}
func (u *UsageMetricsReporter) emitVersion() {
// consul version metric with labels
metrics.SetGaugeWithLabels(
[]string{"version"},
1,
[]metrics.Label{
{Name: "version", Value: versionWithMetadata()},
{Name: "pre_release", Value: version.VersionPrerelease},
},
)
}
func versionWithMetadata() string {
vsn := version.Version
metadata := version.VersionMetadata
if metadata != "" {
vsn += "+" + metadata
}
return vsn
}