mirror of https://github.com/hashicorp/consul
Add support for labels/filters from go-metrics
parent
ce572546e5
commit
d5634fe2a8
|
@ -19,6 +19,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/armon/go-metrics"
|
||||||
"github.com/hashicorp/consul/agent/consul"
|
"github.com/hashicorp/consul/agent/consul"
|
||||||
"github.com/hashicorp/consul/agent/consul/structs"
|
"github.com/hashicorp/consul/agent/consul/structs"
|
||||||
"github.com/hashicorp/consul/agent/systemd"
|
"github.com/hashicorp/consul/agent/systemd"
|
||||||
|
@ -94,6 +95,9 @@ type Agent struct {
|
||||||
// Used for streaming logs to
|
// Used for streaming logs to
|
||||||
LogWriter *logger.LogWriter
|
LogWriter *logger.LogWriter
|
||||||
|
|
||||||
|
// In-memory sink used for collecting metrics
|
||||||
|
MemSink *metrics.InmemSink
|
||||||
|
|
||||||
// delegate is either a *consul.Server or *consul.Client
|
// delegate is either a *consul.Server or *consul.Client
|
||||||
// depending on the configuration
|
// depending on the configuration
|
||||||
delegate delegate
|
delegate delegate
|
||||||
|
|
|
@ -219,6 +219,16 @@ type Telemetry struct {
|
||||||
// DisableHostname will disable hostname prefixing for all metrics
|
// DisableHostname will disable hostname prefixing for all metrics
|
||||||
DisableHostname bool `mapstructure:"disable_hostname"`
|
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,
|
// DogStatsdAddr is the address of a dogstatsd instance. If provided,
|
||||||
// metrics will be sent to that instance
|
// metrics will be sent to that instance
|
||||||
DogStatsdAddr string `mapstructure:"dogstatsd_addr"`
|
DogStatsdAddr string `mapstructure:"dogstatsd_addr"`
|
||||||
|
@ -937,6 +947,7 @@ func DefaultConfig() *Config {
|
||||||
},
|
},
|
||||||
Telemetry: Telemetry{
|
Telemetry: Telemetry{
|
||||||
StatsitePrefix: "consul",
|
StatsitePrefix: "consul",
|
||||||
|
FilterDefault: Bool(true),
|
||||||
},
|
},
|
||||||
Meta: make(map[string]string),
|
Meta: make(map[string]string),
|
||||||
SyslogFacility: "LOCAL0",
|
SyslogFacility: "LOCAL0",
|
||||||
|
@ -1461,6 +1472,21 @@ func DecodeConfig(r io.Reader) (*Config, error) {
|
||||||
result.EnableACLReplication = true
|
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 '-': %s", rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1755,6 +1781,12 @@ func MergeConfig(a, b *Config) *Config {
|
||||||
if b.Telemetry.DisableHostname == true {
|
if b.Telemetry.DisableHostname == true {
|
||||||
result.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 != "" {
|
if b.Telemetry.StatsdAddr != "" {
|
||||||
result.Telemetry.StatsdAddr = b.Telemetry.StatsdAddr
|
result.Telemetry.StatsdAddr = b.Telemetry.StatsdAddr
|
||||||
}
|
}
|
||||||
|
|
|
@ -719,6 +719,18 @@ func TestDecodeConfig(t *testing.T) {
|
||||||
in: `{"telemetry":{"dogstatsd_tags":["a","b"]}}`,
|
in: `{"telemetry":{"dogstatsd_tags":["a","b"]}}`,
|
||||||
c: &Config{Telemetry: Telemetry{DogStatsdTags: []string{"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"}}`,
|
in: `{"telemetry":{"statsd_address":"a"}}`,
|
||||||
c: &Config{Telemetry: Telemetry{StatsdAddr: "a"}},
|
c: &Config{Telemetry: Telemetry{StatsdAddr: "a"}},
|
||||||
|
|
|
@ -268,12 +268,15 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru
|
||||||
|
|
||||||
// Provide some metrics
|
// Provide some metrics
|
||||||
if err == nil {
|
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 != "" {
|
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 {
|
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
|
return err
|
||||||
|
|
|
@ -172,7 +172,8 @@ func (c *consulFSM) applyKVSOperation(buf []byte, index uint64) interface{} {
|
||||||
if err := structs.Decode(buf, &req); err != nil {
|
if err := structs.Decode(buf, &req); err != nil {
|
||||||
panic(fmt.Errorf("failed to decode request: %v", err))
|
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 {
|
switch req.Op {
|
||||||
case api.KVSet:
|
case api.KVSet:
|
||||||
return c.state.KVSSet(index, &req.DirEnt)
|
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 {
|
if err := structs.Decode(buf, &req); err != nil {
|
||||||
panic(fmt.Errorf("failed to decode request: %v", err))
|
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 {
|
switch req.Op {
|
||||||
case structs.SessionCreate:
|
case structs.SessionCreate:
|
||||||
if err := c.state.SessionCreate(index, &req.Session); err != nil {
|
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 {
|
if err := structs.Decode(buf, &req); err != nil {
|
||||||
panic(fmt.Errorf("failed to decode request: %v", err))
|
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 {
|
switch req.Op {
|
||||||
case structs.ACLBootstrapInit:
|
case structs.ACLBootstrapInit:
|
||||||
enabled, err := c.state.ACLBootstrapInit(index)
|
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 {
|
if err := structs.Decode(buf, &req); err != nil {
|
||||||
panic(fmt.Errorf("failed to decode request: %v", err))
|
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 {
|
switch req.Op {
|
||||||
case structs.TombstoneReap:
|
case structs.TombstoneReap:
|
||||||
return c.state.ReapTombstones(req.ReapIndex)
|
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))
|
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 {
|
switch req.Op {
|
||||||
case structs.PreparedQueryCreate, structs.PreparedQueryUpdate:
|
case structs.PreparedQueryCreate, structs.PreparedQueryUpdate:
|
||||||
return c.state.PreparedQuerySet(index, req.Query)
|
return c.state.PreparedQuerySet(index, req.Query)
|
||||||
|
|
|
@ -139,12 +139,15 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc
|
||||||
|
|
||||||
// Provide some metrics
|
// Provide some metrics
|
||||||
if err == nil {
|
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 != "" {
|
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 {
|
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
|
return err
|
||||||
|
|
|
@ -263,7 +263,8 @@ func (s *Server) forwardDC(method, dc string, args interface{}, reply interface{
|
||||||
return structs.ErrNoDCPath
|
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 {
|
if err := s.connPool.RPC(dc, server.Addr, server.Version, method, server.UseTLS, args, reply); err != nil {
|
||||||
manager.NotifyFailedServer(server)
|
manager.NotifyFailedServer(server)
|
||||||
s.logger.Printf("[ERR] consul: RPC failed to server %s in DC %q: %v", server.Addr, dc, err)
|
s.logger.Printf("[ERR] consul: RPC failed to server %s in DC %q: %v", server.Addr, dc, err)
|
||||||
|
|
|
@ -118,7 +118,8 @@ START:
|
||||||
func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) {
|
func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) {
|
||||||
q := req.Question[0]
|
q := req.Question[0]
|
||||||
defer func(s time.Time) {
|
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)",
|
d.logger.Printf("[DEBUG] dns: request for %v (%v) from client %s (%s)",
|
||||||
q, time.Now().Sub(s), resp.RemoteAddr().String(),
|
q, time.Now().Sub(s), resp.RemoteAddr().String(),
|
||||||
resp.RemoteAddr().Network())
|
resp.RemoteAddr().Network())
|
||||||
|
@ -187,7 +188,8 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) {
|
||||||
func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) {
|
func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) {
|
||||||
q := req.Question[0]
|
q := req.Question[0]
|
||||||
defer func(s time.Time) {
|
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)",
|
d.logger.Printf("[DEBUG] dns: request for %v (%v) from client %s (%s)",
|
||||||
q, time.Now().Sub(s), resp.RemoteAddr().String(),
|
q, time.Now().Sub(s), resp.RemoteAddr().String(),
|
||||||
resp.RemoteAddr().Network())
|
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
|
// Register the wrapper, which will close over the expensive-to-compute
|
||||||
// parts from above.
|
// parts from above.
|
||||||
|
// TODO (kyhavlov): Convert this to utilize metric labels in a major release
|
||||||
wrapper := func(resp http.ResponseWriter, req *http.Request) {
|
wrapper := func(resp http.ResponseWriter, req *http.Request) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
handler(resp, req)
|
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/maintenance", s.wrap(s.AgentNodeMaintenance))
|
||||||
handleFuncMetrics("/v1/agent/reload", s.wrap(s.AgentReload))
|
handleFuncMetrics("/v1/agent/reload", s.wrap(s.AgentReload))
|
||||||
handleFuncMetrics("/v1/agent/monitor", s.wrap(s.AgentMonitor))
|
handleFuncMetrics("/v1/agent/monitor", s.wrap(s.AgentMonitor))
|
||||||
|
handleFuncMetrics("/v1/agent/metrics", s.wrap(s.agent.MemSink.DisplayMetrics))
|
||||||
handleFuncMetrics("/v1/agent/services", s.wrap(s.AgentServices))
|
handleFuncMetrics("/v1/agent/services", s.wrap(s.AgentServices))
|
||||||
handleFuncMetrics("/v1/agent/checks", s.wrap(s.AgentChecks))
|
handleFuncMetrics("/v1/agent/checks", s.wrap(s.AgentChecks))
|
||||||
handleFuncMetrics("/v1/agent/members", s.wrap(s.AgentMembers))
|
handleFuncMetrics("/v1/agent/members", s.wrap(s.AgentMembers))
|
||||||
|
|
26
api/agent.go
26
api/agent.go
|
@ -96,6 +96,15 @@ type AgentToken struct {
|
||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metrics info is used to store different types of metric values from the agent.
|
||||||
|
type MetricsInfo struct {
|
||||||
|
Timestamp string
|
||||||
|
Gauges []map[string]interface{}
|
||||||
|
Points []map[string]interface{}
|
||||||
|
Counters []map[string]interface{}
|
||||||
|
Samples []map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
// Agent can be used to query the Agent endpoints
|
// Agent can be used to query the Agent endpoints
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
c *Client
|
c *Client
|
||||||
|
@ -126,6 +135,23 @@ func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
||||||
return out, nil
|
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.
|
// Reload triggers a configuration reload for the agent we are connected to.
|
||||||
func (a *Agent) Reload() error {
|
func (a *Agent) Reload() error {
|
||||||
r := a.c.newRequest("PUT", "/v1/agent/reload")
|
r := a.c.newRequest("PUT", "/v1/agent/reload")
|
||||||
|
|
|
@ -28,6 +28,28 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := metrics.Gauges[0]["Name"]
|
||||||
|
if name != "consul.runtime.alloc_bytes" {
|
||||||
|
t.Fatalf("bad: %v", metrics.Gauges[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPI_AgentReload(t *testing.T) {
|
func TestAPI_AgentReload(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -601,7 +601,7 @@ func circonusSink(config *agent.Config, hostname string) (metrics.MetricSink, er
|
||||||
return sink, nil
|
return sink, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startupTelemetry(config *agent.Config) error {
|
func startupTelemetry(config *agent.Config) (*metrics.InmemSink, error) {
|
||||||
// Setup telemetry
|
// Setup telemetry
|
||||||
// Aggregate on 10 second intervals for 1 minute. Expose the
|
// Aggregate on 10 second intervals for 1 minute. Expose the
|
||||||
// metrics over stderr when there is a SIGUSR1 received.
|
// metrics over stderr when there is a SIGUSR1 received.
|
||||||
|
@ -609,6 +609,7 @@ func startupTelemetry(config *agent.Config) error {
|
||||||
metrics.DefaultInmemSignal(memSink)
|
metrics.DefaultInmemSignal(memSink)
|
||||||
metricsConf := metrics.DefaultConfig(config.Telemetry.StatsitePrefix)
|
metricsConf := metrics.DefaultConfig(config.Telemetry.StatsitePrefix)
|
||||||
metricsConf.EnableHostname = !config.Telemetry.DisableHostname
|
metricsConf.EnableHostname = !config.Telemetry.DisableHostname
|
||||||
|
metricsConf.FilterDefault = *config.Telemetry.FilterDefault
|
||||||
|
|
||||||
var sinks metrics.FanoutSink
|
var sinks metrics.FanoutSink
|
||||||
addSink := func(name string, fn func(*agent.Config, string) (metrics.MetricSink, error)) error {
|
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 {
|
if err := addSink("statsite", statsiteSink); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := addSink("statsd", statsdSink); err != nil {
|
if err := addSink("statsd", statsdSink); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := addSink("dogstatd", dogstatdSink); err != nil {
|
if err := addSink("dogstatd", dogstatdSink); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := addSink("circonus", circonusSink); err != nil {
|
if err := addSink("circonus", circonusSink); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sinks) > 0 {
|
if len(sinks) > 0 {
|
||||||
|
@ -642,7 +643,7 @@ func startupTelemetry(config *agent.Config) error {
|
||||||
metricsConf.EnableHostname = false
|
metricsConf.EnableHostname = false
|
||||||
metrics.NewGlobal(metricsConf, memSink)
|
metrics.NewGlobal(metricsConf, memSink)
|
||||||
}
|
}
|
||||||
return nil
|
return memSink, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *AgentCommand) Run(args []string) int {
|
func (cmd *AgentCommand) Run(args []string) int {
|
||||||
|
@ -682,7 +683,8 @@ func (cmd *AgentCommand) run(args []string) int {
|
||||||
cmd.logOutput = logOutput
|
cmd.logOutput = logOutput
|
||||||
cmd.logger = log.New(logOutput, "", log.LstdFlags)
|
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())
|
cmd.UI.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -696,6 +698,7 @@ func (cmd *AgentCommand) run(args []string) int {
|
||||||
}
|
}
|
||||||
agent.LogOutput = logOutput
|
agent.LogOutput = logOutput
|
||||||
agent.LogWriter = logWriter
|
agent.LogWriter = logWriter
|
||||||
|
agent.MemSink = memSink
|
||||||
|
|
||||||
if err := agent.Start(); err != nil {
|
if err := agent.Start(); err != nil {
|
||||||
cmd.UI.Error(fmt.Sprintf("Error starting agent: %s", err))
|
cmd.UI.Error(fmt.Sprintf("Error starting agent: %s", err))
|
||||||
|
|
|
@ -249,6 +249,112 @@ $ curl \
|
||||||
https://consul.rocks/v1/agent/maintenance?enable=true&reason=For+API+docs
|
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": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Stream Logs
|
## Stream Logs
|
||||||
|
|
||||||
This endpoint streams logs from the local agent until the connection is closed.
|
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>
|
* <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.
|
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 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.
|
A valid API Token used to create/manage check. If provided, metric management is enabled.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue