Merge pull request #3369 from hashicorp/metrics-enhancements
Add support for labels/filters from go-metrics
This commit is contained in:
commit
8c2e422074
|
@ -18,6 +18,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
"github.com/hashicorp/consul/agent/consul"
|
||||
"github.com/hashicorp/consul/agent/consul/structs"
|
||||
"github.com/hashicorp/consul/agent/systemd"
|
||||
|
@ -91,6 +92,9 @@ type Agent struct {
|
|||
// Used for streaming logs to
|
||||
LogWriter *logger.LogWriter
|
||||
|
||||
// In-memory sink used for collecting metrics
|
||||
MemSink *metrics.InmemSink
|
||||
|
||||
// delegate is either a *consul.Server or *consul.Client
|
||||
// depending on the configuration
|
||||
delegate delegate
|
||||
|
@ -2241,5 +2245,8 @@ func (a *Agent) ReloadConfig(newCfg *Config) error {
|
|||
return fmt.Errorf("Failed reloading watches: %v", err)
|
||||
}
|
||||
|
||||
// Update filtered metrics
|
||||
metrics.UpdateFilter(newCfg.Telemetry.AllowedPrefixes, newCfg.Telemetry.BlockedPrefixes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -54,6 +54,21 @@ func (s *HTTPServer) AgentSelf(resp http.ResponseWriter, req *http.Request) (int
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) AgentMetrics(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
// Fetch the ACL token, if any, and enforce agent policy.
|
||||
var token string
|
||||
s.parseToken(req, &token)
|
||||
acl, err := s.agent.resolveToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if acl != nil && !acl.AgentRead(s.agent.config.NodeName) {
|
||||
return nil, errPermissionDenied
|
||||
}
|
||||
|
||||
return s.agent.MemSink.DisplayMetrics(resp, req)
|
||||
}
|
||||
|
||||
func (s *HTTPServer) AgentReload(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if req.Method != "PUT" {
|
||||
resp.WriteHeader(http.StatusMethodNotAllowed)
|
||||
|
|
|
@ -239,6 +239,34 @@ func TestAgent_Self_ACLDeny(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAgent_Metrics_ACLDeny(t *testing.T) {
|
||||
t.Parallel()
|
||||
a := NewTestAgent(t.Name(), TestACLConfig())
|
||||
defer a.Shutdown()
|
||||
|
||||
t.Run("no token", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/v1/agent/metrics", nil)
|
||||
if _, err := a.srv.AgentSelf(nil, req); !isPermissionDenied(err) {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("agent master token", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/v1/agent/metrics?token=towel", nil)
|
||||
if _, err := a.srv.AgentSelf(nil, req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("read-only token", func(t *testing.T) {
|
||||
ro := makeReadOnlyAgentACL(t, a.srv)
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("/v1/agent/metrics?token=%s", ro), nil)
|
||||
if _, err := a.srv.AgentSelf(nil, req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAgent_Reload(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg := TestConfig()
|
||||
|
|
|
@ -219,6 +219,16 @@ type Telemetry struct {
|
|||
// DisableHostname will disable hostname prefixing for all metrics
|
||||
DisableHostname bool `mapstructure:"disable_hostname"`
|
||||
|
||||
// PrefixFilter is a list of filter rules to apply for allowing/blocking metrics
|
||||
// by prefix.
|
||||
PrefixFilter []string `mapstructure:"prefix_filter"`
|
||||
AllowedPrefixes []string `mapstructure:"-" json:"-"`
|
||||
BlockedPrefixes []string `mapstructure:"-" json:"-"`
|
||||
|
||||
// FilterDefault is the default for whether to allow a metric that's not
|
||||
// covered by the filter.
|
||||
FilterDefault *bool `mapstructure:"filter_default"`
|
||||
|
||||
// DogStatsdAddr is the address of a dogstatsd instance. If provided,
|
||||
// metrics will be sent to that instance
|
||||
DogStatsdAddr string `mapstructure:"dogstatsd_addr"`
|
||||
|
@ -937,6 +947,7 @@ func DefaultConfig() *Config {
|
|||
},
|
||||
Telemetry: Telemetry{
|
||||
StatsitePrefix: "consul",
|
||||
FilterDefault: Bool(true),
|
||||
},
|
||||
Meta: make(map[string]string),
|
||||
SyslogFacility: "LOCAL0",
|
||||
|
@ -1461,6 +1472,21 @@ func DecodeConfig(r io.Reader) (*Config, error) {
|
|||
result.EnableACLReplication = true
|
||||
}
|
||||
|
||||
// Parse the metric filters
|
||||
for _, rule := range result.Telemetry.PrefixFilter {
|
||||
if rule == "" {
|
||||
return nil, fmt.Errorf("Cannot have empty filter rule in prefix_filter")
|
||||
}
|
||||
switch rule[0] {
|
||||
case '+':
|
||||
result.Telemetry.AllowedPrefixes = append(result.Telemetry.AllowedPrefixes, rule[1:])
|
||||
case '-':
|
||||
result.Telemetry.BlockedPrefixes = append(result.Telemetry.BlockedPrefixes, rule[1:])
|
||||
default:
|
||||
return nil, fmt.Errorf("Filter rule must begin with either '+' or '-': %q", rule)
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
|
@ -1755,6 +1781,12 @@ func MergeConfig(a, b *Config) *Config {
|
|||
if b.Telemetry.DisableHostname == true {
|
||||
result.Telemetry.DisableHostname = true
|
||||
}
|
||||
if len(b.Telemetry.PrefixFilter) != 0 {
|
||||
result.Telemetry.PrefixFilter = append(result.Telemetry.PrefixFilter, b.Telemetry.PrefixFilter...)
|
||||
}
|
||||
if b.Telemetry.FilterDefault != nil {
|
||||
result.Telemetry.FilterDefault = b.Telemetry.FilterDefault
|
||||
}
|
||||
if b.Telemetry.StatsdAddr != "" {
|
||||
result.Telemetry.StatsdAddr = b.Telemetry.StatsdAddr
|
||||
}
|
||||
|
|
|
@ -719,6 +719,18 @@ func TestDecodeConfig(t *testing.T) {
|
|||
in: `{"telemetry":{"dogstatsd_tags":["a","b"]}}`,
|
||||
c: &Config{Telemetry: Telemetry{DogStatsdTags: []string{"a", "b"}}},
|
||||
},
|
||||
{
|
||||
in: `{"telemetry":{"filter_default":true}}`,
|
||||
c: &Config{Telemetry: Telemetry{FilterDefault: Bool(true)}},
|
||||
},
|
||||
{
|
||||
in: `{"telemetry":{"prefix_filter":["+consul.metric","-consul.othermetric"]}}`,
|
||||
c: &Config{Telemetry: Telemetry{
|
||||
PrefixFilter: []string{"+consul.metric", "-consul.othermetric"},
|
||||
AllowedPrefixes: []string{"consul.metric"},
|
||||
BlockedPrefixes: []string{"consul.othermetric"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: `{"telemetry":{"statsd_address":"a"}}`,
|
||||
c: &Config{Telemetry: Telemetry{StatsdAddr: "a"}},
|
||||
|
|
|
@ -268,12 +268,15 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru
|
|||
|
||||
// Provide some metrics
|
||||
if err == nil {
|
||||
metrics.IncrCounter([]string{"consul", "catalog", "service", "query", args.ServiceName}, 1)
|
||||
metrics.IncrCounterWithLabels([]string{"consul", "catalog", "service", "query"}, 1,
|
||||
[]metrics.Label{{Name: "service", Value: args.ServiceName}})
|
||||
if args.ServiceTag != "" {
|
||||
metrics.IncrCounter([]string{"consul", "catalog", "service", "query-tag", args.ServiceName, args.ServiceTag}, 1)
|
||||
metrics.IncrCounterWithLabels([]string{"consul", "catalog", "service", "query-tag"}, 1,
|
||||
[]metrics.Label{{Name: "service", Value: args.ServiceName}, {Name: "tag", Value: args.ServiceTag}})
|
||||
}
|
||||
if len(reply.ServiceNodes) == 0 {
|
||||
metrics.IncrCounter([]string{"consul", "catalog", "service", "not-found", args.ServiceName}, 1)
|
||||
metrics.IncrCounterWithLabels([]string{"consul", "catalog", "service", "not-found"}, 1,
|
||||
[]metrics.Label{{Name: "service", Value: args.ServiceName}})
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
|
|
@ -172,7 +172,8 @@ func (c *consulFSM) applyKVSOperation(buf []byte, index uint64) interface{} {
|
|||
if err := structs.Decode(buf, &req); err != nil {
|
||||
panic(fmt.Errorf("failed to decode request: %v", err))
|
||||
}
|
||||
defer metrics.MeasureSince([]string{"consul", "fsm", "kvs", string(req.Op)}, time.Now())
|
||||
defer metrics.MeasureSinceWithLabels([]string{"consul", "fsm", "kvs"}, time.Now(),
|
||||
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
|
||||
switch req.Op {
|
||||
case api.KVSet:
|
||||
return c.state.KVSSet(index, &req.DirEnt)
|
||||
|
@ -216,7 +217,8 @@ func (c *consulFSM) applySessionOperation(buf []byte, index uint64) interface{}
|
|||
if err := structs.Decode(buf, &req); err != nil {
|
||||
panic(fmt.Errorf("failed to decode request: %v", err))
|
||||
}
|
||||
defer metrics.MeasureSince([]string{"consul", "fsm", "session", string(req.Op)}, time.Now())
|
||||
defer metrics.MeasureSinceWithLabels([]string{"consul", "fsm", "session"}, time.Now(),
|
||||
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
|
||||
switch req.Op {
|
||||
case structs.SessionCreate:
|
||||
if err := c.state.SessionCreate(index, &req.Session); err != nil {
|
||||
|
@ -236,7 +238,8 @@ func (c *consulFSM) applyACLOperation(buf []byte, index uint64) interface{} {
|
|||
if err := structs.Decode(buf, &req); err != nil {
|
||||
panic(fmt.Errorf("failed to decode request: %v", err))
|
||||
}
|
||||
defer metrics.MeasureSince([]string{"consul", "fsm", "acl", string(req.Op)}, time.Now())
|
||||
defer metrics.MeasureSinceWithLabels([]string{"consul", "fsm", "acl"}, time.Now(),
|
||||
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
|
||||
switch req.Op {
|
||||
case structs.ACLBootstrapInit:
|
||||
enabled, err := c.state.ACLBootstrapInit(index)
|
||||
|
@ -267,7 +270,8 @@ func (c *consulFSM) applyTombstoneOperation(buf []byte, index uint64) interface{
|
|||
if err := structs.Decode(buf, &req); err != nil {
|
||||
panic(fmt.Errorf("failed to decode request: %v", err))
|
||||
}
|
||||
defer metrics.MeasureSince([]string{"consul", "fsm", "tombstone", string(req.Op)}, time.Now())
|
||||
defer metrics.MeasureSinceWithLabels([]string{"consul", "fsm", "tombstone"}, time.Now(),
|
||||
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
|
||||
switch req.Op {
|
||||
case structs.TombstoneReap:
|
||||
return c.state.ReapTombstones(req.ReapIndex)
|
||||
|
@ -301,7 +305,8 @@ func (c *consulFSM) applyPreparedQueryOperation(buf []byte, index uint64) interf
|
|||
panic(fmt.Errorf("failed to decode request: %v", err))
|
||||
}
|
||||
|
||||
defer metrics.MeasureSince([]string{"consul", "fsm", "prepared-query", string(req.Op)}, time.Now())
|
||||
defer metrics.MeasureSinceWithLabels([]string{"consul", "fsm", "prepared-query"}, time.Now(),
|
||||
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
|
||||
switch req.Op {
|
||||
case structs.PreparedQueryCreate, structs.PreparedQueryUpdate:
|
||||
return c.state.PreparedQuerySet(index, req.Query)
|
||||
|
|
|
@ -139,12 +139,15 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc
|
|||
|
||||
// Provide some metrics
|
||||
if err == nil {
|
||||
metrics.IncrCounter([]string{"consul", "health", "service", "query", args.ServiceName}, 1)
|
||||
metrics.IncrCounterWithLabels([]string{"consul", "health", "service", "query"}, 1,
|
||||
[]metrics.Label{{Name: "service", Value: args.ServiceName}})
|
||||
if args.ServiceTag != "" {
|
||||
metrics.IncrCounter([]string{"consul", "health", "service", "query-tag", args.ServiceName, args.ServiceTag}, 1)
|
||||
metrics.IncrCounterWithLabels([]string{"consul", "health", "service", "query-tag"}, 1,
|
||||
[]metrics.Label{{Name: "service", Value: args.ServiceName}, {Name: "tag", Value: args.ServiceTag}})
|
||||
}
|
||||
if len(reply.Nodes) == 0 {
|
||||
metrics.IncrCounter([]string{"consul", "health", "service", "not-found", args.ServiceName}, 1)
|
||||
metrics.IncrCounterWithLabels([]string{"consul", "health", "service", "not-found"}, 1,
|
||||
[]metrics.Label{{Name: "service", Value: args.ServiceName}})
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
|
|
@ -263,7 +263,8 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{
|
|||
return structs.ErrNoDCPath
|
||||
}
|
||||
|
||||
metrics.IncrCounter([]string{"consul", "rpc", "cross-dc", dc}, 1)
|
||||
metrics.IncrCounterWithLabels([]string{"consul", "rpc", "cross-dc"}, 1,
|
||||
[]metrics.Label{{Name: "datacenter", Value: dc}})
|
||||
if err := s.connPool.RPC(dc, server.Addr, server.Version, method, server.UseTLS, args, reply); err != nil {
|
||||
manager.NotifyFailedServer(server)
|
||||
s.logger.Printf("[ERR] consul: RPC failed to server %s in DC %q: %v", server.Addr, dc, err)
|
||||
|
|
|
@ -122,7 +122,8 @@ START:
|
|||
func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) {
|
||||
q := req.Question[0]
|
||||
defer func(s time.Time) {
|
||||
metrics.MeasureSince([]string{"consul", "dns", "ptr_query", d.agent.config.NodeName}, s)
|
||||
metrics.MeasureSinceWithLabels([]string{"consul", "dns", "ptr_query"}, s,
|
||||
[]metrics.Label{{Name: "node", Value: d.agent.config.NodeName}})
|
||||
d.logger.Printf("[DEBUG] dns: request for %v (%v) from client %s (%s)",
|
||||
q, time.Now().Sub(s), resp.RemoteAddr().String(),
|
||||
resp.RemoteAddr().Network())
|
||||
|
@ -191,7 +192,8 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) {
|
|||
func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) {
|
||||
q := req.Question[0]
|
||||
defer func(s time.Time) {
|
||||
metrics.MeasureSince([]string{"consul", "dns", "domain_query", d.agent.config.NodeName}, s)
|
||||
metrics.MeasureSinceWithLabels([]string{"consul", "dns", "domain_query"}, s,
|
||||
[]metrics.Label{{Name: "node", Value: d.agent.config.NodeName}})
|
||||
d.logger.Printf("[DEBUG] dns: request for %v (%v) from client %s (%s)",
|
||||
q, time.Now().Sub(s), resp.RemoteAddr().String(),
|
||||
resp.RemoteAddr().Network())
|
||||
|
|
|
@ -60,6 +60,7 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
|
|||
|
||||
// Register the wrapper, which will close over the expensive-to-compute
|
||||
// parts from above.
|
||||
// TODO (kyhavlov): Convert this to utilize metric labels in a major release
|
||||
wrapper := func(resp http.ResponseWriter, req *http.Request) {
|
||||
start := time.Now()
|
||||
handler(resp, req)
|
||||
|
@ -97,6 +98,7 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
|
|||
handleFuncMetrics("/v1/agent/maintenance", s.wrap(s.AgentNodeMaintenance))
|
||||
handleFuncMetrics("/v1/agent/reload", s.wrap(s.AgentReload))
|
||||
handleFuncMetrics("/v1/agent/monitor", s.wrap(s.AgentMonitor))
|
||||
handleFuncMetrics("/v1/agent/metrics", s.wrap(s.AgentMetrics))
|
||||
handleFuncMetrics("/v1/agent/services", s.wrap(s.AgentServices))
|
||||
handleFuncMetrics("/v1/agent/checks", s.wrap(s.AgentChecks))
|
||||
handleFuncMetrics("/v1/agent/members", s.wrap(s.AgentMembers))
|
||||
|
|
53
api/agent.go
53
api/agent.go
|
@ -96,6 +96,42 @@ type AgentToken struct {
|
|||
Token string
|
||||
}
|
||||
|
||||
// Metrics info is used to store different types of metric values from the agent.
|
||||
type MetricsInfo struct {
|
||||
Timestamp string
|
||||
Gauges []GaugeValue
|
||||
Points []PointValue
|
||||
Counters []SampledValue
|
||||
Samples []SampledValue
|
||||
}
|
||||
|
||||
// GaugeValue stores one value that is updated as time goes on, such as
|
||||
// the amount of memory allocated.
|
||||
type GaugeValue struct {
|
||||
Name string
|
||||
Value float32
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// PointValue holds a series of points for a metric.
|
||||
type PointValue struct {
|
||||
Name string
|
||||
Points []float32
|
||||
}
|
||||
|
||||
// SampledValue stores info about a metric that is incremented over time,
|
||||
// such as the number of requests to an HTTP endpoint.
|
||||
type SampledValue struct {
|
||||
Name string
|
||||
Count int
|
||||
Sum float64
|
||||
Min float64
|
||||
Max float64
|
||||
Mean float64
|
||||
Stddev float64
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// Agent can be used to query the Agent endpoints
|
||||
type Agent struct {
|
||||
c *Client
|
||||
|
@ -126,6 +162,23 @@ func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// Metrics is used to query the agent we are speaking to for
|
||||
// its current internal metric data
|
||||
func (a *Agent) Metrics() (*MetricsInfo, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/metrics")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out *MetricsInfo
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Reload triggers a configuration reload for the agent we are connected to.
|
||||
func (a *Agent) Reload() error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/reload")
|
||||
|
|
|
@ -28,6 +28,27 @@ func TestAPI_AgentSelf(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAPI_AgentMetrics(t *testing.T) {
|
||||
t.Parallel()
|
||||
c, s := makeClient(t)
|
||||
defer s.Stop()
|
||||
|
||||
agent := c.Agent()
|
||||
|
||||
metrics, err := agent.Metrics()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if len(metrics.Gauges) < 0 {
|
||||
t.Fatalf("bad: %v", metrics)
|
||||
}
|
||||
|
||||
if metrics.Gauges[0].Name != "consul.runtime.alloc_bytes" {
|
||||
t.Fatalf("bad: %v", metrics.Gauges[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPI_AgentReload(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -601,7 +601,7 @@ func circonusSink(config *agent.Config, hostname string) (metrics.MetricSink, er
|
|||
return sink, nil
|
||||
}
|
||||
|
||||
func startupTelemetry(config *agent.Config) error {
|
||||
func startupTelemetry(config *agent.Config) (*metrics.InmemSink, error) {
|
||||
// Setup telemetry
|
||||
// Aggregate on 10 second intervals for 1 minute. Expose the
|
||||
// metrics over stderr when there is a SIGUSR1 received.
|
||||
|
@ -609,6 +609,7 @@ func startupTelemetry(config *agent.Config) error {
|
|||
metrics.DefaultInmemSignal(memSink)
|
||||
metricsConf := metrics.DefaultConfig(config.Telemetry.StatsitePrefix)
|
||||
metricsConf.EnableHostname = !config.Telemetry.DisableHostname
|
||||
metricsConf.FilterDefault = *config.Telemetry.FilterDefault
|
||||
|
||||
var sinks metrics.FanoutSink
|
||||
addSink := func(name string, fn func(*agent.Config, string) (metrics.MetricSink, error)) error {
|
||||
|
@ -623,16 +624,16 @@ func startupTelemetry(config *agent.Config) error {
|
|||
}
|
||||
|
||||
if err := addSink("statsite", statsiteSink); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := addSink("statsd", statsdSink); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := addSink("dogstatd", dogstatdSink); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := addSink("circonus", circonusSink); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(sinks) > 0 {
|
||||
|
@ -642,7 +643,7 @@ func startupTelemetry(config *agent.Config) error {
|
|||
metricsConf.EnableHostname = false
|
||||
metrics.NewGlobal(metricsConf, memSink)
|
||||
}
|
||||
return nil
|
||||
return memSink, nil
|
||||
}
|
||||
|
||||
func (cmd *AgentCommand) Run(args []string) int {
|
||||
|
@ -682,7 +683,8 @@ func (cmd *AgentCommand) run(args []string) int {
|
|||
cmd.logOutput = logOutput
|
||||
cmd.logger = log.New(logOutput, "", log.LstdFlags)
|
||||
|
||||
if err := startupTelemetry(config); err != nil {
|
||||
memSink, err := startupTelemetry(config)
|
||||
if err != nil {
|
||||
cmd.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
@ -696,6 +698,7 @@ func (cmd *AgentCommand) run(args []string) int {
|
|||
}
|
||||
agent.LogOutput = logOutput
|
||||
agent.LogWriter = logWriter
|
||||
agent.MemSink = memSink
|
||||
|
||||
if err := agent.Start(); err != nil {
|
||||
cmd.UI.Error(fmt.Sprintf("Error starting agent: %s", err))
|
||||
|
|
|
@ -5,6 +5,7 @@ package circonus
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
cgm "github.com/circonus-labs/circonus-gometrics"
|
||||
)
|
||||
|
||||
|
@ -61,6 +62,12 @@ func (s *CirconusSink) SetGauge(key []string, val float32) {
|
|||
s.metrics.SetGauge(flatKey, int64(val))
|
||||
}
|
||||
|
||||
// SetGaugeWithLabels sets value for a gauge metric with the given labels
|
||||
func (s *CirconusSink) SetGaugeWithLabels(key []string, val float32, labels []metrics.Label) {
|
||||
flatKey := s.flattenKeyLabels(key, labels)
|
||||
s.metrics.SetGauge(flatKey, int64(val))
|
||||
}
|
||||
|
||||
// EmitKey is not implemented in circonus
|
||||
func (s *CirconusSink) EmitKey(key []string, val float32) {
|
||||
// NOP
|
||||
|
@ -72,12 +79,24 @@ func (s *CirconusSink) IncrCounter(key []string, val float32) {
|
|||
s.metrics.IncrementByValue(flatKey, uint64(val))
|
||||
}
|
||||
|
||||
// IncrCounterWithLabels increments a counter metric with the given labels
|
||||
func (s *CirconusSink) IncrCounterWithLabels(key []string, val float32, labels []metrics.Label) {
|
||||
flatKey := s.flattenKeyLabels(key, labels)
|
||||
s.metrics.IncrementByValue(flatKey, uint64(val))
|
||||
}
|
||||
|
||||
// AddSample adds a sample to a histogram metric
|
||||
func (s *CirconusSink) AddSample(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.metrics.RecordValue(flatKey, float64(val))
|
||||
}
|
||||
|
||||
// AddSampleWithLabels adds a sample to a histogram metric with the given labels
|
||||
func (s *CirconusSink) AddSampleWithLabels(key []string, val float32, labels []metrics.Label) {
|
||||
flatKey := s.flattenKeyLabels(key, labels)
|
||||
s.metrics.RecordValue(flatKey, float64(val))
|
||||
}
|
||||
|
||||
// Flattens key to Circonus metric name
|
||||
func (s *CirconusSink) flattenKey(parts []string) string {
|
||||
joined := strings.Join(parts, "`")
|
||||
|
@ -90,3 +109,11 @@ func (s *CirconusSink) flattenKey(parts []string) string {
|
|||
}
|
||||
}, joined)
|
||||
}
|
||||
|
||||
// Flattens the key along with labels for formatting, removes spaces
|
||||
func (s *CirconusSink) flattenKeyLabels(parts []string, labels []metrics.Label) string {
|
||||
for _, label := range labels {
|
||||
parts = append(parts, label.Value)
|
||||
}
|
||||
return s.flattenKey(parts)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/DataDog/datadog-go/statsd"
|
||||
"github.com/armon/go-metrics"
|
||||
)
|
||||
|
||||
// DogStatsdSink provides a MetricSink that can be used
|
||||
|
@ -45,46 +46,49 @@ func (s *DogStatsdSink) EnableHostNamePropagation() {
|
|||
|
||||
func (s *DogStatsdSink) flattenKey(parts []string) string {
|
||||
joined := strings.Join(parts, ".")
|
||||
return strings.Map(func(r rune) rune {
|
||||
switch r {
|
||||
case ':':
|
||||
fallthrough
|
||||
case ' ':
|
||||
return '_'
|
||||
default:
|
||||
return r
|
||||
}
|
||||
}, joined)
|
||||
return strings.Map(sanitize, joined)
|
||||
}
|
||||
|
||||
func (s *DogStatsdSink) parseKey(key []string) ([]string, []string) {
|
||||
func sanitize(r rune) rune {
|
||||
switch r {
|
||||
case ':':
|
||||
fallthrough
|
||||
case ' ':
|
||||
return '_'
|
||||
default:
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DogStatsdSink) parseKey(key []string) ([]string, []metrics.Label) {
|
||||
// Since DogStatsd supports dimensionality via tags on metric keys, this sink's approach is to splice the hostname out of the key in favor of a `host` tag
|
||||
// The `host` tag is either forced here, or set downstream by the DogStatsd server
|
||||
|
||||
var tags []string
|
||||
var labels []metrics.Label
|
||||
hostName := s.hostName
|
||||
|
||||
//Splice the hostname out of the key
|
||||
// Splice the hostname out of the key
|
||||
for i, el := range key {
|
||||
if el == hostName {
|
||||
key = append(key[:i], key[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if s.propagateHostname {
|
||||
tags = append(tags, fmt.Sprintf("host:%s", hostName))
|
||||
labels = append(labels, metrics.Label{"host", hostName})
|
||||
}
|
||||
return key, tags
|
||||
return key, labels
|
||||
}
|
||||
|
||||
// Implementation of methods in the MetricSink interface
|
||||
|
||||
func (s *DogStatsdSink) SetGauge(key []string, val float32) {
|
||||
s.SetGaugeWithTags(key, val, []string{})
|
||||
s.SetGaugeWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (s *DogStatsdSink) IncrCounter(key []string, val float32) {
|
||||
s.IncrCounterWithTags(key, val, []string{})
|
||||
s.IncrCounterWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
// EmitKey is not implemented since DogStatsd does not provide a metric type that holds an
|
||||
|
@ -93,33 +97,44 @@ func (s *DogStatsdSink) EmitKey(key []string, val float32) {
|
|||
}
|
||||
|
||||
func (s *DogStatsdSink) AddSample(key []string, val float32) {
|
||||
s.AddSampleWithTags(key, val, []string{})
|
||||
s.AddSampleWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
// The following ...WithTags methods correspond to Datadog's Tag extension to Statsd.
|
||||
// The following ...WithLabels methods correspond to Datadog's Tag extension to Statsd.
|
||||
// http://docs.datadoghq.com/guides/dogstatsd/#tags
|
||||
|
||||
func (s *DogStatsdSink) SetGaugeWithTags(key []string, val float32, tags []string) {
|
||||
flatKey, tags := s.getFlatkeyAndCombinedTags(key, tags)
|
||||
func (s *DogStatsdSink) SetGaugeWithLabels(key []string, val float32, labels []metrics.Label) {
|
||||
flatKey, tags := s.getFlatkeyAndCombinedLabels(key, labels)
|
||||
rate := 1.0
|
||||
s.client.Gauge(flatKey, float64(val), tags, rate)
|
||||
}
|
||||
|
||||
func (s *DogStatsdSink) IncrCounterWithTags(key []string, val float32, tags []string) {
|
||||
flatKey, tags := s.getFlatkeyAndCombinedTags(key, tags)
|
||||
func (s *DogStatsdSink) IncrCounterWithLabels(key []string, val float32, labels []metrics.Label) {
|
||||
flatKey, tags := s.getFlatkeyAndCombinedLabels(key, labels)
|
||||
rate := 1.0
|
||||
s.client.Count(flatKey, int64(val), tags, rate)
|
||||
}
|
||||
|
||||
func (s *DogStatsdSink) AddSampleWithTags(key []string, val float32, tags []string) {
|
||||
flatKey, tags := s.getFlatkeyAndCombinedTags(key, tags)
|
||||
func (s *DogStatsdSink) AddSampleWithLabels(key []string, val float32, labels []metrics.Label) {
|
||||
flatKey, tags := s.getFlatkeyAndCombinedLabels(key, labels)
|
||||
rate := 1.0
|
||||
s.client.TimeInMilliseconds(flatKey, float64(val), tags, rate)
|
||||
}
|
||||
|
||||
func (s *DogStatsdSink) getFlatkeyAndCombinedTags(key []string, tags []string) (flattenedKey string, combinedTags []string) {
|
||||
key, hostTags := s.parseKey(key)
|
||||
func (s *DogStatsdSink) getFlatkeyAndCombinedLabels(key []string, labels []metrics.Label) (string, []string) {
|
||||
key, parsedLabels := s.parseKey(key)
|
||||
flatKey := s.flattenKey(key)
|
||||
tags = append(tags, hostTags...)
|
||||
labels = append(labels, parsedLabels...)
|
||||
|
||||
var tags []string
|
||||
for _, label := range labels {
|
||||
label.Name = strings.Map(sanitize, label.Name)
|
||||
label.Value = strings.Map(sanitize, label.Value)
|
||||
if label.Value != "" {
|
||||
tags = append(tags, fmt.Sprintf("%s:%s", label.Name, label.Value))
|
||||
} else {
|
||||
tags = append(tags, label.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return flatKey, tags
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
|
@ -39,7 +40,7 @@ type IntervalMetrics struct {
|
|||
Interval time.Time
|
||||
|
||||
// Gauges maps the key to the last set value
|
||||
Gauges map[string]float32
|
||||
Gauges map[string]GaugeValue
|
||||
|
||||
// Points maps the string to the list of emitted values
|
||||
// from EmitKey
|
||||
|
@ -47,21 +48,21 @@ type IntervalMetrics struct {
|
|||
|
||||
// Counters maps the string key to a sum of the counter
|
||||
// values
|
||||
Counters map[string]*AggregateSample
|
||||
Counters map[string]SampledValue
|
||||
|
||||
// Samples maps the key to an AggregateSample,
|
||||
// which has the rolled up view of a sample
|
||||
Samples map[string]*AggregateSample
|
||||
Samples map[string]SampledValue
|
||||
}
|
||||
|
||||
// NewIntervalMetrics creates a new IntervalMetrics for a given interval
|
||||
func NewIntervalMetrics(intv time.Time) *IntervalMetrics {
|
||||
return &IntervalMetrics{
|
||||
Interval: intv,
|
||||
Gauges: make(map[string]float32),
|
||||
Gauges: make(map[string]GaugeValue),
|
||||
Points: make(map[string][]float32),
|
||||
Counters: make(map[string]*AggregateSample),
|
||||
Samples: make(map[string]*AggregateSample),
|
||||
Counters: make(map[string]SampledValue),
|
||||
Samples: make(map[string]SampledValue),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,12 +70,12 @@ func NewIntervalMetrics(intv time.Time) *IntervalMetrics {
|
|||
// about a sample
|
||||
type AggregateSample struct {
|
||||
Count int // The count of emitted pairs
|
||||
Rate float64 // The count of emitted pairs per time unit (usually 1 second)
|
||||
Rate float64 `json:"-"` // The count of emitted pairs per time unit (usually 1 second)
|
||||
Sum float64 // The sum of values
|
||||
SumSq float64 // The sum of squared values
|
||||
SumSq float64 `json:"-"` // The sum of squared values
|
||||
Min float64 // Minimum value
|
||||
Max float64 // Maximum value
|
||||
LastUpdated time.Time // When value was last updated
|
||||
LastUpdated time.Time `json:"-"` // When value was last updated
|
||||
}
|
||||
|
||||
// Computes a Stddev of the values
|
||||
|
@ -154,12 +155,16 @@ func NewInmemSink(interval, retain time.Duration) *InmemSink {
|
|||
}
|
||||
|
||||
func (i *InmemSink) SetGauge(key []string, val float32) {
|
||||
k := i.flattenKey(key)
|
||||
i.SetGaugeWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (i *InmemSink) SetGaugeWithLabels(key []string, val float32, labels []Label) {
|
||||
k, name := i.flattenKeyLabels(key, labels)
|
||||
intv := i.getInterval()
|
||||
|
||||
intv.Lock()
|
||||
defer intv.Unlock()
|
||||
intv.Gauges[k] = val
|
||||
intv.Gauges[k] = GaugeValue{Name: name, Value: val, Labels: labels}
|
||||
}
|
||||
|
||||
func (i *InmemSink) EmitKey(key []string, val float32) {
|
||||
|
@ -173,30 +178,46 @@ func (i *InmemSink) EmitKey(key []string, val float32) {
|
|||
}
|
||||
|
||||
func (i *InmemSink) IncrCounter(key []string, val float32) {
|
||||
k := i.flattenKey(key)
|
||||
i.IncrCounterWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (i *InmemSink) IncrCounterWithLabels(key []string, val float32, labels []Label) {
|
||||
k, name := i.flattenKeyLabels(key, labels)
|
||||
intv := i.getInterval()
|
||||
|
||||
intv.Lock()
|
||||
defer intv.Unlock()
|
||||
|
||||
agg := intv.Counters[k]
|
||||
if agg == nil {
|
||||
agg = &AggregateSample{}
|
||||
agg, ok := intv.Counters[k]
|
||||
if !ok {
|
||||
agg = SampledValue{
|
||||
Name: name,
|
||||
AggregateSample: &AggregateSample{},
|
||||
Labels: labels,
|
||||
}
|
||||
intv.Counters[k] = agg
|
||||
}
|
||||
agg.Ingest(float64(val), i.rateDenom)
|
||||
}
|
||||
|
||||
func (i *InmemSink) AddSample(key []string, val float32) {
|
||||
k := i.flattenKey(key)
|
||||
i.AddSampleWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (i *InmemSink) AddSampleWithLabels(key []string, val float32, labels []Label) {
|
||||
k, name := i.flattenKeyLabels(key, labels)
|
||||
intv := i.getInterval()
|
||||
|
||||
intv.Lock()
|
||||
defer intv.Unlock()
|
||||
|
||||
agg := intv.Samples[k]
|
||||
if agg == nil {
|
||||
agg = &AggregateSample{}
|
||||
agg, ok := intv.Samples[k]
|
||||
if !ok {
|
||||
agg = SampledValue{
|
||||
Name: name,
|
||||
AggregateSample: &AggregateSample{},
|
||||
Labels: labels,
|
||||
}
|
||||
intv.Samples[k] = agg
|
||||
}
|
||||
agg.Ingest(float64(val), i.rateDenom)
|
||||
|
@ -261,6 +282,38 @@ func (i *InmemSink) getInterval() *IntervalMetrics {
|
|||
|
||||
// Flattens the key for formatting, removes spaces
|
||||
func (i *InmemSink) flattenKey(parts []string) string {
|
||||
joined := strings.Join(parts, ".")
|
||||
return strings.Replace(joined, " ", "_", -1)
|
||||
buf := &bytes.Buffer{}
|
||||
replacer := strings.NewReplacer(" ", "_")
|
||||
|
||||
if len(parts) > 0 {
|
||||
replacer.WriteString(buf, parts[0])
|
||||
}
|
||||
for _, part := range parts[1:] {
|
||||
replacer.WriteString(buf, ".")
|
||||
replacer.WriteString(buf, part)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Flattens the key for formatting along with its labels, removes spaces
|
||||
func (i *InmemSink) flattenKeyLabels(parts []string, labels []Label) (string, string) {
|
||||
buf := &bytes.Buffer{}
|
||||
replacer := strings.NewReplacer(" ", "_")
|
||||
|
||||
if len(parts) > 0 {
|
||||
replacer.WriteString(buf, parts[0])
|
||||
}
|
||||
for _, part := range parts[1:] {
|
||||
replacer.WriteString(buf, ".")
|
||||
replacer.WriteString(buf, part)
|
||||
}
|
||||
|
||||
key := buf.String()
|
||||
|
||||
for _, label := range labels {
|
||||
replacer.WriteString(buf, fmt.Sprintf(";%s=%s", label.Name, label.Value))
|
||||
}
|
||||
|
||||
return buf.String(), key
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MetricsSummary holds a roll-up of metrics info for a given interval
|
||||
type MetricsSummary struct {
|
||||
Timestamp string
|
||||
Gauges []GaugeValue
|
||||
Points []PointValue
|
||||
Counters []SampledValue
|
||||
Samples []SampledValue
|
||||
}
|
||||
|
||||
type GaugeValue struct {
|
||||
Name string
|
||||
Hash string `json:"-"`
|
||||
Value float32
|
||||
|
||||
Labels []Label `json:"-"`
|
||||
DisplayLabels map[string]string `json:"Labels"`
|
||||
}
|
||||
|
||||
type PointValue struct {
|
||||
Name string
|
||||
Points []float32
|
||||
}
|
||||
|
||||
type SampledValue struct {
|
||||
Name string
|
||||
Hash string `json:"-"`
|
||||
*AggregateSample
|
||||
Mean float64
|
||||
Stddev float64
|
||||
|
||||
Labels []Label `json:"-"`
|
||||
DisplayLabels map[string]string `json:"Labels"`
|
||||
}
|
||||
|
||||
// DisplayMetrics returns a summary of the metrics from the most recent finished interval.
|
||||
func (i *InmemSink) DisplayMetrics(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
data := i.Data()
|
||||
|
||||
var interval *IntervalMetrics
|
||||
n := len(data)
|
||||
switch {
|
||||
case n == 0:
|
||||
return nil, fmt.Errorf("no metric intervals have been initialized yet")
|
||||
case n == 1:
|
||||
// Show the current interval if it's all we have
|
||||
interval = i.intervals[0]
|
||||
default:
|
||||
// Show the most recent finished interval if we have one
|
||||
interval = i.intervals[n-2]
|
||||
}
|
||||
|
||||
summary := MetricsSummary{
|
||||
Timestamp: interval.Interval.Round(time.Second).UTC().String(),
|
||||
Gauges: make([]GaugeValue, 0, len(interval.Gauges)),
|
||||
Points: make([]PointValue, 0, len(interval.Points)),
|
||||
}
|
||||
|
||||
// Format and sort the output of each metric type, so it gets displayed in a
|
||||
// deterministic order.
|
||||
for name, points := range interval.Points {
|
||||
summary.Points = append(summary.Points, PointValue{name, points})
|
||||
}
|
||||
sort.Slice(summary.Points, func(i, j int) bool {
|
||||
return summary.Points[i].Name < summary.Points[j].Name
|
||||
})
|
||||
|
||||
for hash, value := range interval.Gauges {
|
||||
value.Hash = hash
|
||||
value.DisplayLabels = make(map[string]string)
|
||||
for _, label := range value.Labels {
|
||||
value.DisplayLabels[label.Name] = label.Value
|
||||
}
|
||||
value.Labels = nil
|
||||
|
||||
summary.Gauges = append(summary.Gauges, value)
|
||||
}
|
||||
sort.Slice(summary.Gauges, func(i, j int) bool {
|
||||
return summary.Gauges[i].Hash < summary.Gauges[j].Hash
|
||||
})
|
||||
|
||||
summary.Counters = formatSamples(interval.Counters)
|
||||
summary.Samples = formatSamples(interval.Samples)
|
||||
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func formatSamples(source map[string]SampledValue) []SampledValue {
|
||||
output := make([]SampledValue, 0, len(source))
|
||||
for hash, sample := range source {
|
||||
displayLabels := make(map[string]string)
|
||||
for _, label := range sample.Labels {
|
||||
displayLabels[label.Name] = label.Value
|
||||
}
|
||||
|
||||
output = append(output, SampledValue{
|
||||
Name: sample.Name,
|
||||
Hash: hash,
|
||||
AggregateSample: sample.AggregateSample,
|
||||
Mean: sample.AggregateSample.Mean(),
|
||||
Stddev: sample.AggregateSample.Stddev(),
|
||||
DisplayLabels: displayLabels,
|
||||
})
|
||||
}
|
||||
sort.Slice(output, func(i, j int) bool {
|
||||
return output[i].Hash < output[j].Hash
|
||||
})
|
||||
|
||||
return output
|
||||
}
|
|
@ -79,7 +79,7 @@ func (i *InmemSignal) dumpStats() {
|
|||
intv := data[i]
|
||||
intv.RLock()
|
||||
for name, val := range intv.Gauges {
|
||||
fmt.Fprintf(buf, "[%v][G] '%s': %0.3f\n", intv.Interval, name, val)
|
||||
fmt.Fprintf(buf, "[%v][G] '%s': %0.3f\n", intv.Interval, name, val.Value)
|
||||
}
|
||||
for name, vals := range intv.Points {
|
||||
for _, val := range vals {
|
||||
|
@ -87,10 +87,10 @@ func (i *InmemSignal) dumpStats() {
|
|||
}
|
||||
}
|
||||
for name, agg := range intv.Counters {
|
||||
fmt.Fprintf(buf, "[%v][C] '%s': %s\n", intv.Interval, name, agg)
|
||||
fmt.Fprintf(buf, "[%v][C] '%s': %s\n", intv.Interval, name, agg.AggregateSample)
|
||||
}
|
||||
for name, agg := range intv.Samples {
|
||||
fmt.Fprintf(buf, "[%v][S] '%s': %s\n", intv.Interval, name, agg)
|
||||
fmt.Fprintf(buf, "[%v][S] '%s': %s\n", intv.Interval, name, agg.AggregateSample)
|
||||
}
|
||||
intv.RUnlock()
|
||||
}
|
||||
|
|
|
@ -2,20 +2,43 @@ package metrics
|
|||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-immutable-radix"
|
||||
)
|
||||
|
||||
type Label struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (m *Metrics) SetGauge(key []string, val float32) {
|
||||
if m.HostName != "" && m.EnableHostname {
|
||||
key = insert(0, m.HostName, key)
|
||||
m.SetGaugeWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (m *Metrics) SetGaugeWithLabels(key []string, val float32, labels []Label) {
|
||||
if m.HostName != "" {
|
||||
if m.EnableHostnameLabel {
|
||||
labels = append(labels, Label{"host", m.HostName})
|
||||
} else if m.EnableHostname {
|
||||
key = insert(0, m.HostName, key)
|
||||
}
|
||||
}
|
||||
if m.EnableTypePrefix {
|
||||
key = insert(0, "gauge", key)
|
||||
}
|
||||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
if m.EnableServiceLabel {
|
||||
labels = append(labels, Label{"service", m.ServiceName})
|
||||
} else {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
}
|
||||
m.sink.SetGauge(key, val)
|
||||
if !m.allowMetric(key) {
|
||||
return
|
||||
}
|
||||
m.sink.SetGaugeWithLabels(key, val, labels)
|
||||
}
|
||||
|
||||
func (m *Metrics) EmitKey(key []string, val float32) {
|
||||
|
@ -25,40 +48,118 @@ func (m *Metrics) EmitKey(key []string, val float32) {
|
|||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
if !m.allowMetric(key) {
|
||||
return
|
||||
}
|
||||
m.sink.EmitKey(key, val)
|
||||
}
|
||||
|
||||
func (m *Metrics) IncrCounter(key []string, val float32) {
|
||||
m.IncrCounterWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (m *Metrics) IncrCounterWithLabels(key []string, val float32, labels []Label) {
|
||||
if m.HostName != "" && m.EnableHostnameLabel {
|
||||
labels = append(labels, Label{"host", m.HostName})
|
||||
}
|
||||
if m.EnableTypePrefix {
|
||||
key = insert(0, "counter", key)
|
||||
}
|
||||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
if m.EnableServiceLabel {
|
||||
labels = append(labels, Label{"service", m.ServiceName})
|
||||
} else {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
}
|
||||
m.sink.IncrCounter(key, val)
|
||||
if !m.allowMetric(key) {
|
||||
return
|
||||
}
|
||||
m.sink.IncrCounterWithLabels(key, val, labels)
|
||||
}
|
||||
|
||||
func (m *Metrics) AddSample(key []string, val float32) {
|
||||
m.AddSampleWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (m *Metrics) AddSampleWithLabels(key []string, val float32, labels []Label) {
|
||||
if m.HostName != "" && m.EnableHostnameLabel {
|
||||
labels = append(labels, Label{"host", m.HostName})
|
||||
}
|
||||
if m.EnableTypePrefix {
|
||||
key = insert(0, "sample", key)
|
||||
}
|
||||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
if m.EnableServiceLabel {
|
||||
labels = append(labels, Label{"service", m.ServiceName})
|
||||
} else {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
}
|
||||
m.sink.AddSample(key, val)
|
||||
if !m.allowMetric(key) {
|
||||
return
|
||||
}
|
||||
m.sink.AddSampleWithLabels(key, val, labels)
|
||||
}
|
||||
|
||||
func (m *Metrics) MeasureSince(key []string, start time.Time) {
|
||||
m.MeasureSinceWithLabels(key, start, nil)
|
||||
}
|
||||
|
||||
func (m *Metrics) MeasureSinceWithLabels(key []string, start time.Time, labels []Label) {
|
||||
if m.HostName != "" && m.EnableHostnameLabel {
|
||||
labels = append(labels, Label{"host", m.HostName})
|
||||
}
|
||||
if m.EnableTypePrefix {
|
||||
key = insert(0, "timer", key)
|
||||
}
|
||||
if m.ServiceName != "" {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
if m.EnableServiceLabel {
|
||||
labels = append(labels, Label{"service", m.ServiceName})
|
||||
} else {
|
||||
key = insert(0, m.ServiceName, key)
|
||||
}
|
||||
}
|
||||
if !m.allowMetric(key) {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
elapsed := now.Sub(start)
|
||||
msec := float32(elapsed.Nanoseconds()) / float32(m.TimerGranularity)
|
||||
m.sink.AddSample(key, msec)
|
||||
m.sink.AddSampleWithLabels(key, msec, labels)
|
||||
}
|
||||
|
||||
// UpdateFilter overwrites the existing filter with the given rules.
|
||||
func (m *Metrics) UpdateFilter(allow, block []string) {
|
||||
m.filterLock.Lock()
|
||||
defer m.filterLock.Unlock()
|
||||
|
||||
m.AllowedPrefixes = allow
|
||||
m.BlockedPrefixes = block
|
||||
|
||||
m.filter = iradix.New()
|
||||
for _, prefix := range m.AllowedPrefixes {
|
||||
m.filter, _, _ = m.filter.Insert([]byte(prefix), true)
|
||||
}
|
||||
for _, prefix := range m.BlockedPrefixes {
|
||||
m.filter, _, _ = m.filter.Insert([]byte(prefix), false)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whether the metric should be allowed based on configured prefix filters
|
||||
func (m *Metrics) allowMetric(key []string) bool {
|
||||
m.filterLock.RLock()
|
||||
defer m.filterLock.RUnlock()
|
||||
|
||||
if m.filter == nil || m.filter.Len() == 0 {
|
||||
return m.Config.FilterDefault
|
||||
}
|
||||
|
||||
_, allowed, ok := m.filter.Root().LongestPrefix([]byte(strings.Join(key, ".")))
|
||||
if !ok {
|
||||
return m.Config.FilterDefault
|
||||
}
|
||||
return allowed.(bool)
|
||||
}
|
||||
|
||||
// Periodically collects runtime stats to publish
|
||||
|
|
|
@ -10,31 +10,41 @@ import (
|
|||
type MetricSink interface {
|
||||
// A Gauge should retain the last value it is set to
|
||||
SetGauge(key []string, val float32)
|
||||
SetGaugeWithLabels(key []string, val float32, labels []Label)
|
||||
|
||||
// Should emit a Key/Value pair for each call
|
||||
EmitKey(key []string, val float32)
|
||||
|
||||
// Counters should accumulate values
|
||||
IncrCounter(key []string, val float32)
|
||||
IncrCounterWithLabels(key []string, val float32, labels []Label)
|
||||
|
||||
// Samples are for timing information, where quantiles are used
|
||||
AddSample(key []string, val float32)
|
||||
AddSampleWithLabels(key []string, val float32, labels []Label)
|
||||
}
|
||||
|
||||
// BlackholeSink is used to just blackhole messages
|
||||
type BlackholeSink struct{}
|
||||
|
||||
func (*BlackholeSink) SetGauge(key []string, val float32) {}
|
||||
func (*BlackholeSink) EmitKey(key []string, val float32) {}
|
||||
func (*BlackholeSink) IncrCounter(key []string, val float32) {}
|
||||
func (*BlackholeSink) AddSample(key []string, val float32) {}
|
||||
func (*BlackholeSink) SetGauge(key []string, val float32) {}
|
||||
func (*BlackholeSink) SetGaugeWithLabels(key []string, val float32, labels []Label) {}
|
||||
func (*BlackholeSink) EmitKey(key []string, val float32) {}
|
||||
func (*BlackholeSink) IncrCounter(key []string, val float32) {}
|
||||
func (*BlackholeSink) IncrCounterWithLabels(key []string, val float32, labels []Label) {}
|
||||
func (*BlackholeSink) AddSample(key []string, val float32) {}
|
||||
func (*BlackholeSink) AddSampleWithLabels(key []string, val float32, labels []Label) {}
|
||||
|
||||
// FanoutSink is used to sink to fanout values to multiple sinks
|
||||
type FanoutSink []MetricSink
|
||||
|
||||
func (fh FanoutSink) SetGauge(key []string, val float32) {
|
||||
fh.SetGaugeWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (fh FanoutSink) SetGaugeWithLabels(key []string, val float32, labels []Label) {
|
||||
for _, s := range fh {
|
||||
s.SetGauge(key, val)
|
||||
s.SetGaugeWithLabels(key, val, labels)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,14 +55,22 @@ func (fh FanoutSink) EmitKey(key []string, val float32) {
|
|||
}
|
||||
|
||||
func (fh FanoutSink) IncrCounter(key []string, val float32) {
|
||||
fh.IncrCounterWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (fh FanoutSink) IncrCounterWithLabels(key []string, val float32, labels []Label) {
|
||||
for _, s := range fh {
|
||||
s.IncrCounter(key, val)
|
||||
s.IncrCounterWithLabels(key, val, labels)
|
||||
}
|
||||
}
|
||||
|
||||
func (fh FanoutSink) AddSample(key []string, val float32) {
|
||||
fh.AddSampleWithLabels(key, val, nil)
|
||||
}
|
||||
|
||||
func (fh FanoutSink) AddSampleWithLabels(key []string, val float32, labels []Label) {
|
||||
for _, s := range fh {
|
||||
s.AddSample(key, val)
|
||||
s.AddSampleWithLabels(key, val, labels)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,11 @@ package metrics
|
|||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-immutable-radix"
|
||||
)
|
||||
|
||||
// Config is used to configure metrics settings
|
||||
|
@ -11,18 +14,26 @@ type Config struct {
|
|||
ServiceName string // Prefixed with keys to seperate services
|
||||
HostName string // Hostname to use. If not provided and EnableHostname, it will be os.Hostname
|
||||
EnableHostname bool // Enable prefixing gauge values with hostname
|
||||
EnableHostnameLabel bool // Enable adding hostname to labels
|
||||
EnableServiceLabel bool // Enable adding service to labels
|
||||
EnableRuntimeMetrics bool // Enables profiling of runtime metrics (GC, Goroutines, Memory)
|
||||
EnableTypePrefix bool // Prefixes key with a type ("counter", "gauge", "timer")
|
||||
TimerGranularity time.Duration // Granularity of timers.
|
||||
ProfileInterval time.Duration // Interval to profile runtime metrics
|
||||
|
||||
AllowedPrefixes []string // A list of metric prefixes to allow, with '.' as the separator
|
||||
BlockedPrefixes []string // A list of metric prefixes to block, with '.' as the separator
|
||||
FilterDefault bool // Whether to allow metrics by default
|
||||
}
|
||||
|
||||
// Metrics represents an instance of a metrics sink that can
|
||||
// be used to emit
|
||||
type Metrics struct {
|
||||
Config
|
||||
lastNumGC uint32
|
||||
sink MetricSink
|
||||
lastNumGC uint32
|
||||
sink MetricSink
|
||||
filter *iradix.Tree
|
||||
filterLock sync.RWMutex
|
||||
}
|
||||
|
||||
// Shared global metrics instance
|
||||
|
@ -43,6 +54,7 @@ func DefaultConfig(serviceName string) *Config {
|
|||
EnableTypePrefix: false, // Disable type prefix
|
||||
TimerGranularity: time.Millisecond, // Timers are in milliseconds
|
||||
ProfileInterval: time.Second, // Poll runtime every second
|
||||
FilterDefault: true, // Don't filter metrics by default
|
||||
}
|
||||
|
||||
// Try to get the hostname
|
||||
|
@ -56,6 +68,7 @@ func New(conf *Config, sink MetricSink) (*Metrics, error) {
|
|||
met := &Metrics{}
|
||||
met.Config = *conf
|
||||
met.sink = sink
|
||||
met.UpdateFilter(conf.AllowedPrefixes, conf.BlockedPrefixes)
|
||||
|
||||
// Start the runtime collector
|
||||
if conf.EnableRuntimeMetrics {
|
||||
|
@ -79,6 +92,10 @@ func SetGauge(key []string, val float32) {
|
|||
globalMetrics.Load().(*Metrics).SetGauge(key, val)
|
||||
}
|
||||
|
||||
func SetGaugeWithLabels(key []string, val float32, labels []Label) {
|
||||
globalMetrics.Load().(*Metrics).SetGaugeWithLabels(key, val, labels)
|
||||
}
|
||||
|
||||
func EmitKey(key []string, val float32) {
|
||||
globalMetrics.Load().(*Metrics).EmitKey(key, val)
|
||||
}
|
||||
|
@ -87,10 +104,26 @@ func IncrCounter(key []string, val float32) {
|
|||
globalMetrics.Load().(*Metrics).IncrCounter(key, val)
|
||||
}
|
||||
|
||||
func IncrCounterWithLabels(key []string, val float32, labels []Label) {
|
||||
globalMetrics.Load().(*Metrics).IncrCounterWithLabels(key, val, labels)
|
||||
}
|
||||
|
||||
func AddSample(key []string, val float32) {
|
||||
globalMetrics.Load().(*Metrics).AddSample(key, val)
|
||||
}
|
||||
|
||||
func AddSampleWithLabels(key []string, val float32, labels []Label) {
|
||||
globalMetrics.Load().(*Metrics).AddSampleWithLabels(key, val, labels)
|
||||
}
|
||||
|
||||
func MeasureSince(key []string, start time.Time) {
|
||||
globalMetrics.Load().(*Metrics).MeasureSince(key, start)
|
||||
}
|
||||
|
||||
func MeasureSinceWithLabels(key []string, start time.Time, labels []Label) {
|
||||
globalMetrics.Load().(*Metrics).MeasureSinceWithLabels(key, start, labels)
|
||||
}
|
||||
|
||||
func UpdateFilter(allow, block []string) {
|
||||
globalMetrics.Load().(*Metrics).UpdateFilter(allow, block)
|
||||
}
|
||||
|
|
|
@ -50,6 +50,11 @@ func (s *StatsdSink) SetGauge(key []string, val float32) {
|
|||
s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsdSink) SetGaugeWithLabels(key []string, val float32, labels []Label) {
|
||||
flatKey := s.flattenKeyLabels(key, labels)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsdSink) EmitKey(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|kv\n", flatKey, val))
|
||||
|
@ -60,11 +65,21 @@ func (s *StatsdSink) IncrCounter(key []string, val float32) {
|
|||
s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsdSink) IncrCounterWithLabels(key []string, val float32, labels []Label) {
|
||||
flatKey := s.flattenKeyLabels(key, labels)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsdSink) AddSample(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsdSink) AddSampleWithLabels(key []string, val float32, labels []Label) {
|
||||
flatKey := s.flattenKeyLabels(key, labels)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val))
|
||||
}
|
||||
|
||||
// Flattens the key for formatting, removes spaces
|
||||
func (s *StatsdSink) flattenKey(parts []string) string {
|
||||
joined := strings.Join(parts, ".")
|
||||
|
@ -80,6 +95,15 @@ func (s *StatsdSink) flattenKey(parts []string) string {
|
|||
}, joined)
|
||||
}
|
||||
|
||||
// Flattens the key along with labels for formatting, removes spaces
|
||||
func (s *StatsdSink) flattenKeyLabels(parts []string, labels []Label) string {
|
||||
fullName := parts
|
||||
for _, label := range labels {
|
||||
fullName = append(parts, label.Value)
|
||||
}
|
||||
return s.flattenKey(fullName)
|
||||
}
|
||||
|
||||
// Does a non-blocking push to the metrics queue
|
||||
func (s *StatsdSink) pushMetric(m string) {
|
||||
select {
|
||||
|
|
|
@ -50,6 +50,11 @@ func (s *StatsiteSink) SetGauge(key []string, val float32) {
|
|||
s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsiteSink) SetGaugeWithLabels(key []string, val float32, labels []Label) {
|
||||
flatKey := s.flattenKeyLabels(key, labels)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|g\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsiteSink) EmitKey(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|kv\n", flatKey, val))
|
||||
|
@ -60,11 +65,21 @@ func (s *StatsiteSink) IncrCounter(key []string, val float32) {
|
|||
s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsiteSink) IncrCounterWithLabels(key []string, val float32, labels []Label) {
|
||||
flatKey := s.flattenKeyLabels(key, labels)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|c\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsiteSink) AddSample(key []string, val float32) {
|
||||
flatKey := s.flattenKey(key)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val))
|
||||
}
|
||||
|
||||
func (s *StatsiteSink) AddSampleWithLabels(key []string, val float32, labels []Label) {
|
||||
flatKey := s.flattenKeyLabels(key, labels)
|
||||
s.pushMetric(fmt.Sprintf("%s:%f|ms\n", flatKey, val))
|
||||
}
|
||||
|
||||
// Flattens the key for formatting, removes spaces
|
||||
func (s *StatsiteSink) flattenKey(parts []string) string {
|
||||
joined := strings.Join(parts, ".")
|
||||
|
@ -80,6 +95,14 @@ func (s *StatsiteSink) flattenKey(parts []string) string {
|
|||
}, joined)
|
||||
}
|
||||
|
||||
// Flattens the key along with labels for formatting, removes spaces
|
||||
func (s *StatsiteSink) flattenKeyLabels(parts []string, labels []Label) string {
|
||||
for _, label := range labels {
|
||||
parts = append(parts, label.Value)
|
||||
}
|
||||
return s.flattenKey(parts)
|
||||
}
|
||||
|
||||
// Does a non-blocking push to the metrics queue
|
||||
func (s *StatsiteSink) pushMetric(m string) {
|
||||
select {
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
{"checksumSHA1":"AzjRkOQtVBTwIw4RJLTygFhJs3s=","path":"github.com/Microsoft/go-winio","revision":"c4dc1301f1dc0307acd38e611aa375a64dfe0642","revisionTime":"2017-07-12T04:46:15Z"},
|
||||
{"checksumSHA1":"9NR0rrcAT5J76C5xMS4AVksS9o0=","path":"github.com/StackExchange/wmi","revision":"e54cbda6595d7293a7a468ccf9525f6bc8887f99","revisionTime":"2016-08-11T21:45:55Z"},
|
||||
{"checksumSHA1":"l0iFqayYAaEip6Olaq3/LCOa/Sg=","path":"github.com/armon/circbuf","revision":"bbbad097214e2918d8543d5201d12bfd7bca254d","revisionTime":"2015-08-27T00:49:46Z"},
|
||||
{"checksumSHA1":"M+ZeYktTT2wak9ZvQ0OZBbIHAGo=","path":"github.com/armon/go-metrics","revision":"f036747b9d0e8590f175a5d654a2194a7d9df4b5","revisionTime":"2017-06-01T21:44:32Z"},
|
||||
{"checksumSHA1":"OmqT9Y1mAHvlAKeJh0jBHC9SH78=","path":"github.com/armon/go-metrics/circonus","revision":"3df31a1ada83e310c2e24b267c8e8b68836547b4","revisionTime":"2016-07-17T04:34:58Z"},
|
||||
{"checksumSHA1":"mAzNU3zeZGEwqjDT4ZkspFvx3TI=","path":"github.com/armon/go-metrics/datadog","revision":"3df31a1ada83e310c2e24b267c8e8b68836547b4","revisionTime":"2016-07-17T04:34:58Z"},
|
||||
{"checksumSHA1":"gXAf+SOZfdKaXaO03vL5LH12g8s=","path":"github.com/armon/go-metrics","revision":"2e4f2be0fe4f6b7096471aa85f2c342bff3b8f4f","revisionTime":"2017-08-08T19:31:08Z"},
|
||||
{"checksumSHA1":"xCsGGM9TKBogZDfSN536KtQdLko=","path":"github.com/armon/go-metrics/circonus","revision":"ded85ed431a7aee3f3af79f082b704d948058f64","revisionTime":"2017-08-07T19:17:41Z"},
|
||||
{"checksumSHA1":"Dt0n1sSivvvdZQdzc4Hu/yOG+T0=","path":"github.com/armon/go-metrics/datadog","revision":"ded85ed431a7aee3f3af79f082b704d948058f64","revisionTime":"2017-08-07T19:17:41Z"},
|
||||
{"checksumSHA1":"gNO0JNpLzYOdInGeq7HqMZUzx9M=","path":"github.com/armon/go-radix","revision":"4239b77079c7b5d1243b7b4736304ce8ddb6f0f2","revisionTime":"2016-01-15T23:47:25Z"},
|
||||
{"checksumSHA1":"dvd7Su+WNmHRP1+w1HezrPUCDsc=","path":"github.com/bgentry/speakeasy","revision":"e1439544d8ecd0f3e9373a636d447668096a8f81","revisionTime":"2016-05-20T23:26:10Z"},
|
||||
{"checksumSHA1":"twtRfb6484vfr2qqjiFkLThTjcQ=","path":"github.com/bgentry/speakeasy/example","revision":"e1439544d8ecd0f3e9373a636d447668096a8f81","revisionTime":"2016-05-20T23:26:10Z"},
|
||||
|
|
|
@ -249,6 +249,127 @@ $ curl \
|
|||
https://consul.rocks/v1/agent/maintenance?enable=true&reason=For+API+docs
|
||||
```
|
||||
|
||||
## View Metrics
|
||||
|
||||
This endpoint returns the configuration and member information of the local
|
||||
agent.
|
||||
|
||||
| Method | Path | Produces |
|
||||
| ------ | ---------------------------- | -------------------------- |
|
||||
| `GET` | `/agent/metrics` | `application/json` |
|
||||
|
||||
This endpoint will dump the metrics for the most recent finished interval.
|
||||
For more information about metrics, see the [telemetry](/docs/agent/telemetry.html)
|
||||
page.
|
||||
|
||||
| Blocking Queries | Consistency Modes | ACL Required |
|
||||
| ---------------- | ----------------- | ------------ |
|
||||
| `NO` | `none` | `agent:read` |
|
||||
|
||||
### Sample Request
|
||||
|
||||
```text
|
||||
$ curl \
|
||||
https://consul.rocks/v1/agent/metrics
|
||||
```
|
||||
|
||||
### Sample Response
|
||||
|
||||
```json
|
||||
{
|
||||
"Timestamp": "2017-08-08 02:55:10 +0000 UTC",
|
||||
"Gauges": [
|
||||
{
|
||||
"Name": "consul.consul.session_ttl.active",
|
||||
"Value": 0,
|
||||
"Labels": {}
|
||||
},
|
||||
{
|
||||
"Name": "consul.runtime.alloc_bytes",
|
||||
"Value": 4704344,
|
||||
"Labels": {}
|
||||
},
|
||||
{
|
||||
"Name": "consul.runtime.free_count",
|
||||
"Value": 74063,
|
||||
"Labels": {}
|
||||
}
|
||||
],
|
||||
"Points": [],
|
||||
"Counters": [
|
||||
{
|
||||
"Name": "consul.consul.catalog.service.query",
|
||||
"Count": 1,
|
||||
"Sum": 1,
|
||||
"Min": 1,
|
||||
"Max": 1,
|
||||
"Mean": 1,
|
||||
"Stddev": 0,
|
||||
"Labels": {
|
||||
"service": "consul"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "consul.raft.apply",
|
||||
"Count": 1,
|
||||
"Sum": 1,
|
||||
"Min": 1,
|
||||
"Max": 1,
|
||||
"Mean": 1,
|
||||
"Stddev": 0,
|
||||
"Labels": {}
|
||||
}
|
||||
],
|
||||
"Samples": [
|
||||
{
|
||||
"Name": "consul.consul.http.GET.v1.agent.metrics",
|
||||
"Count": 1,
|
||||
"Sum": 0.1817069947719574,
|
||||
"Min": 0.1817069947719574,
|
||||
"Max": 0.1817069947719574,
|
||||
"Mean": 0.1817069947719574,
|
||||
"Stddev": 0,
|
||||
"Labels": {}
|
||||
},
|
||||
{
|
||||
"Name": "consul.consul.http.GET.v1.catalog.service._",
|
||||
"Count": 1,
|
||||
"Sum": 0.23342099785804749,
|
||||
"Min": 0.23342099785804749,
|
||||
"Max": 0.23342099785804749,
|
||||
"Mean": 0.23342099785804749,
|
||||
"Stddev": 0,
|
||||
"Labels": {}
|
||||
},
|
||||
{
|
||||
"Name": "consul.serf.queue.Query",
|
||||
"Count": 20,
|
||||
"Sum": 0,
|
||||
"Min": 0,
|
||||
"Max": 0,
|
||||
"Mean": 0,
|
||||
"Stddev": 0,
|
||||
"Labels": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `Timestamp` is the timestamp of the interval for the displayed metrics. Metrics are
|
||||
aggregated on a ten second interval, so this value (along with the displayed metrics)
|
||||
will change every ten seconds.
|
||||
|
||||
- `Gauges` is a list of gauges which store one value that is updated as time goes on,
|
||||
such as the amount of memory allocated.
|
||||
|
||||
- `Points` is a list of point metrics, which each store a series of points under a given name.
|
||||
|
||||
- `Counters` is a list of counters, which store info about a metric that is incremented
|
||||
over time such as the number of requests to an HTTP endpoint.
|
||||
|
||||
- `Samples` is a list of samples, which store info about the amount of time spent on an
|
||||
operation, such as the time taken to serve a request to a specific http endpoint.
|
||||
|
||||
## Stream Logs
|
||||
|
||||
This endpoint streams logs from the local agent until the connection is closed.
|
||||
|
|
|
@ -1130,6 +1130,24 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
|
|||
* <a name="telemetry-disable_hostname"></a><a href="#telemetry-disable_hostname">`disable_hostname`</a>
|
||||
This controls whether or not to prepend runtime telemetry with the machine's hostname, defaults to false.
|
||||
|
||||
* <a name="telemetry-prefix_filter"></a><a href="#telemetry-prefix_filter">`prefix_filter`</a>
|
||||
This is a list of filter rules to apply for allowing/blocking metrics by prefix in the following format:
|
||||
|
||||
```javascript
|
||||
[
|
||||
"+consul.raft.apply",
|
||||
"-consul.http",
|
||||
"+consul.http.GET"
|
||||
]
|
||||
```
|
||||
A leading "<b>+</b>" will enable any metrics with the given prefix, and a leading "<b>-</b>" will block them. If there
|
||||
is overlap between two rules, the more specific rule will take precedence. Blocking will take priority if the same
|
||||
prefix is listed multiple times.
|
||||
|
||||
* <a name="telemetry-filter_default"></a><a href="#telemetry-filter_default">`filter_default`</a>
|
||||
This controls whether to allow metrics that have not been specified by the filter. Defaults to `true`, which will
|
||||
allow all metrics when no filters are provided. When set to `false` with no filters, no metrics will be sent.
|
||||
|
||||
* <a name="telemetry-circonus_api_token"></a><a href="#telemetry-circonus_api_token">`circonus_api_token`</a>
|
||||
A valid API Token used to create/manage check. If provided, metric management is enabled.
|
||||
|
||||
|
@ -1327,3 +1345,4 @@ items which are reloaded include:
|
|||
* Watches
|
||||
* HTTP Client Address
|
||||
* <a href="#node_meta">Node Metadata</a>
|
||||
* <a href="#telemetry-prefix_filter">Metric Prefix Filter</a>
|
||||
|
|
|
@ -22,7 +22,8 @@ getting a better view of what Consul is doing.
|
|||
Additionally, if the [`telemetry` configuration options](/docs/agent/options.html#telemetry)
|
||||
are provided, the telemetry information will be streamed to a
|
||||
[statsite](http://github.com/armon/statsite) or [statsd](http://github.com/etsy/statsd) server where
|
||||
it can be aggregated and flushed to Graphite or any other metrics store.
|
||||
it can be aggregated and flushed to Graphite or any other metrics store. This
|
||||
information can also be viewed with the [metrics endpoint](/api/agent.html#view-metrics)
|
||||
|
||||
Below is sample output of a telemetry dump:
|
||||
|
||||
|
|
Loading…
Reference in New Issue