diff --git a/command/agent/http.go b/command/agent/http.go index a0fa287b9a..cd36204544 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/armon/go-metrics" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/tlsutil" "github.com/mitchellh/mapstructure" @@ -191,93 +192,124 @@ func (s *HTTPServer) Shutdown() { } } +// handleFuncMetrics takes the given pattern and handler and wraps to produce +// metrics based on the pattern and request. +func (s *HTTPServer) handleFuncMetrics(pattern string, handler func(http.ResponseWriter, *http.Request)) { + // Get the parts of the pattern. We omit any initial empty for the + // leading slash, and put an underscore as a "thing" placeholder if we + // see a trailing slash, which means the part after is parsed. This lets + // us distinguish from things like /v1/query and /v1/query/. + var parts []string + for i, part := range strings.Split(pattern, "/") { + if part == "" { + if i == 0 { + continue + } else { + part = "_" + } + } + parts = append(parts, part) + } + + // Register the wrapper, which will close over the expensive-to-compute + // parts from above. + wrapper := func(resp http.ResponseWriter, req *http.Request) { + start := time.Now() + handler(resp, req) + + key := append([]string{"consul", "http", req.Method}, parts...) + metrics.MeasureSince(key, start) + } + s.mux.HandleFunc(pattern, wrapper) +} + // registerHandlers is used to attach our handlers to the mux func (s *HTTPServer) registerHandlers(enableDebug bool) { s.mux.HandleFunc("/", s.Index) - s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeader)) - s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeers)) + s.handleFuncMetrics("/v1/status/leader", s.wrap(s.StatusLeader)) + s.handleFuncMetrics("/v1/status/peers", s.wrap(s.StatusPeers)) - s.mux.HandleFunc("/v1/catalog/register", s.wrap(s.CatalogRegister)) - s.mux.HandleFunc("/v1/catalog/deregister", s.wrap(s.CatalogDeregister)) - s.mux.HandleFunc("/v1/catalog/datacenters", s.wrap(s.CatalogDatacenters)) - s.mux.HandleFunc("/v1/catalog/nodes", s.wrap(s.CatalogNodes)) - s.mux.HandleFunc("/v1/catalog/services", s.wrap(s.CatalogServices)) - s.mux.HandleFunc("/v1/catalog/service/", s.wrap(s.CatalogServiceNodes)) - s.mux.HandleFunc("/v1/catalog/node/", s.wrap(s.CatalogNodeServices)) + s.handleFuncMetrics("/v1/catalog/register", s.wrap(s.CatalogRegister)) + s.handleFuncMetrics("/v1/catalog/deregister", s.wrap(s.CatalogDeregister)) + s.handleFuncMetrics("/v1/catalog/datacenters", s.wrap(s.CatalogDatacenters)) + s.handleFuncMetrics("/v1/catalog/nodes", s.wrap(s.CatalogNodes)) + s.handleFuncMetrics("/v1/catalog/services", s.wrap(s.CatalogServices)) + s.handleFuncMetrics("/v1/catalog/service/", s.wrap(s.CatalogServiceNodes)) + s.handleFuncMetrics("/v1/catalog/node/", s.wrap(s.CatalogNodeServices)) if !s.agent.config.DisableCoordinates { - s.mux.HandleFunc("/v1/coordinate/datacenters", s.wrap(s.CoordinateDatacenters)) - s.mux.HandleFunc("/v1/coordinate/nodes", s.wrap(s.CoordinateNodes)) + s.handleFuncMetrics("/v1/coordinate/datacenters", s.wrap(s.CoordinateDatacenters)) + s.handleFuncMetrics("/v1/coordinate/nodes", s.wrap(s.CoordinateNodes)) } else { - s.mux.HandleFunc("/v1/coordinate/datacenters", s.wrap(coordinateDisabled)) - s.mux.HandleFunc("/v1/coordinate/nodes", s.wrap(coordinateDisabled)) + s.handleFuncMetrics("/v1/coordinate/datacenters", s.wrap(coordinateDisabled)) + s.handleFuncMetrics("/v1/coordinate/nodes", s.wrap(coordinateDisabled)) } - s.mux.HandleFunc("/v1/health/node/", s.wrap(s.HealthNodeChecks)) - s.mux.HandleFunc("/v1/health/checks/", s.wrap(s.HealthServiceChecks)) - s.mux.HandleFunc("/v1/health/state/", s.wrap(s.HealthChecksInState)) - s.mux.HandleFunc("/v1/health/service/", s.wrap(s.HealthServiceNodes)) + s.handleFuncMetrics("/v1/health/node/", s.wrap(s.HealthNodeChecks)) + s.handleFuncMetrics("/v1/health/checks/", s.wrap(s.HealthServiceChecks)) + s.handleFuncMetrics("/v1/health/state/", s.wrap(s.HealthChecksInState)) + s.handleFuncMetrics("/v1/health/service/", s.wrap(s.HealthServiceNodes)) - s.mux.HandleFunc("/v1/agent/self", s.wrap(s.AgentSelf)) - s.mux.HandleFunc("/v1/agent/maintenance", s.wrap(s.AgentNodeMaintenance)) - s.mux.HandleFunc("/v1/agent/services", s.wrap(s.AgentServices)) - s.mux.HandleFunc("/v1/agent/checks", s.wrap(s.AgentChecks)) - s.mux.HandleFunc("/v1/agent/members", s.wrap(s.AgentMembers)) - s.mux.HandleFunc("/v1/agent/join/", s.wrap(s.AgentJoin)) - s.mux.HandleFunc("/v1/agent/force-leave/", s.wrap(s.AgentForceLeave)) + s.handleFuncMetrics("/v1/agent/self", s.wrap(s.AgentSelf)) + s.handleFuncMetrics("/v1/agent/maintenance", s.wrap(s.AgentNodeMaintenance)) + s.handleFuncMetrics("/v1/agent/services", s.wrap(s.AgentServices)) + s.handleFuncMetrics("/v1/agent/checks", s.wrap(s.AgentChecks)) + s.handleFuncMetrics("/v1/agent/members", s.wrap(s.AgentMembers)) + s.handleFuncMetrics("/v1/agent/join/", s.wrap(s.AgentJoin)) + s.handleFuncMetrics("/v1/agent/force-leave/", s.wrap(s.AgentForceLeave)) - s.mux.HandleFunc("/v1/agent/check/register", s.wrap(s.AgentRegisterCheck)) - s.mux.HandleFunc("/v1/agent/check/deregister/", s.wrap(s.AgentDeregisterCheck)) - s.mux.HandleFunc("/v1/agent/check/pass/", s.wrap(s.AgentCheckPass)) - s.mux.HandleFunc("/v1/agent/check/warn/", s.wrap(s.AgentCheckWarn)) - s.mux.HandleFunc("/v1/agent/check/fail/", s.wrap(s.AgentCheckFail)) - s.mux.HandleFunc("/v1/agent/check/update/", s.wrap(s.AgentCheckUpdate)) + s.handleFuncMetrics("/v1/agent/check/register", s.wrap(s.AgentRegisterCheck)) + s.handleFuncMetrics("/v1/agent/check/deregister/", s.wrap(s.AgentDeregisterCheck)) + s.handleFuncMetrics("/v1/agent/check/pass/", s.wrap(s.AgentCheckPass)) + s.handleFuncMetrics("/v1/agent/check/warn/", s.wrap(s.AgentCheckWarn)) + s.handleFuncMetrics("/v1/agent/check/fail/", s.wrap(s.AgentCheckFail)) + s.handleFuncMetrics("/v1/agent/check/update/", s.wrap(s.AgentCheckUpdate)) - s.mux.HandleFunc("/v1/agent/service/register", s.wrap(s.AgentRegisterService)) - s.mux.HandleFunc("/v1/agent/service/deregister/", s.wrap(s.AgentDeregisterService)) - s.mux.HandleFunc("/v1/agent/service/maintenance/", s.wrap(s.AgentServiceMaintenance)) + s.handleFuncMetrics("/v1/agent/service/register", s.wrap(s.AgentRegisterService)) + s.handleFuncMetrics("/v1/agent/service/deregister/", s.wrap(s.AgentDeregisterService)) + s.handleFuncMetrics("/v1/agent/service/maintenance/", s.wrap(s.AgentServiceMaintenance)) - s.mux.HandleFunc("/v1/event/fire/", s.wrap(s.EventFire)) - s.mux.HandleFunc("/v1/event/list", s.wrap(s.EventList)) + s.handleFuncMetrics("/v1/event/fire/", s.wrap(s.EventFire)) + s.handleFuncMetrics("/v1/event/list", s.wrap(s.EventList)) - s.mux.HandleFunc("/v1/kv/", s.wrap(s.KVSEndpoint)) + s.handleFuncMetrics("/v1/kv/", s.wrap(s.KVSEndpoint)) - s.mux.HandleFunc("/v1/session/create", s.wrap(s.SessionCreate)) - s.mux.HandleFunc("/v1/session/destroy/", s.wrap(s.SessionDestroy)) - s.mux.HandleFunc("/v1/session/renew/", s.wrap(s.SessionRenew)) - s.mux.HandleFunc("/v1/session/info/", s.wrap(s.SessionGet)) - s.mux.HandleFunc("/v1/session/node/", s.wrap(s.SessionsForNode)) - s.mux.HandleFunc("/v1/session/list", s.wrap(s.SessionList)) + s.handleFuncMetrics("/v1/session/create", s.wrap(s.SessionCreate)) + s.handleFuncMetrics("/v1/session/destroy/", s.wrap(s.SessionDestroy)) + s.handleFuncMetrics("/v1/session/renew/", s.wrap(s.SessionRenew)) + s.handleFuncMetrics("/v1/session/info/", s.wrap(s.SessionGet)) + s.handleFuncMetrics("/v1/session/node/", s.wrap(s.SessionsForNode)) + s.handleFuncMetrics("/v1/session/list", s.wrap(s.SessionList)) if s.agent.config.ACLDatacenter != "" { - s.mux.HandleFunc("/v1/acl/create", s.wrap(s.ACLCreate)) - s.mux.HandleFunc("/v1/acl/update", s.wrap(s.ACLUpdate)) - s.mux.HandleFunc("/v1/acl/destroy/", s.wrap(s.ACLDestroy)) - s.mux.HandleFunc("/v1/acl/info/", s.wrap(s.ACLGet)) - s.mux.HandleFunc("/v1/acl/clone/", s.wrap(s.ACLClone)) - s.mux.HandleFunc("/v1/acl/list", s.wrap(s.ACLList)) - s.mux.HandleFunc("/v1/acl/replication", s.wrap(s.ACLReplicationStatus)) + s.handleFuncMetrics("/v1/acl/create", s.wrap(s.ACLCreate)) + s.handleFuncMetrics("/v1/acl/update", s.wrap(s.ACLUpdate)) + s.handleFuncMetrics("/v1/acl/destroy/", s.wrap(s.ACLDestroy)) + s.handleFuncMetrics("/v1/acl/info/", s.wrap(s.ACLGet)) + s.handleFuncMetrics("/v1/acl/clone/", s.wrap(s.ACLClone)) + s.handleFuncMetrics("/v1/acl/list", s.wrap(s.ACLList)) + s.handleFuncMetrics("/v1/acl/replication", s.wrap(s.ACLReplicationStatus)) } else { - s.mux.HandleFunc("/v1/acl/create", s.wrap(aclDisabled)) - s.mux.HandleFunc("/v1/acl/update", s.wrap(aclDisabled)) - s.mux.HandleFunc("/v1/acl/destroy/", s.wrap(aclDisabled)) - s.mux.HandleFunc("/v1/acl/info/", s.wrap(aclDisabled)) - s.mux.HandleFunc("/v1/acl/clone/", s.wrap(aclDisabled)) - s.mux.HandleFunc("/v1/acl/list", s.wrap(aclDisabled)) - s.mux.HandleFunc("/v1/acl/replication", s.wrap(aclDisabled)) + s.handleFuncMetrics("/v1/acl/create", s.wrap(aclDisabled)) + s.handleFuncMetrics("/v1/acl/update", s.wrap(aclDisabled)) + s.handleFuncMetrics("/v1/acl/destroy/", s.wrap(aclDisabled)) + s.handleFuncMetrics("/v1/acl/info/", s.wrap(aclDisabled)) + s.handleFuncMetrics("/v1/acl/clone/", s.wrap(aclDisabled)) + s.handleFuncMetrics("/v1/acl/list", s.wrap(aclDisabled)) + s.handleFuncMetrics("/v1/acl/replication", s.wrap(aclDisabled)) } - s.mux.HandleFunc("/v1/query", s.wrap(s.PreparedQueryGeneral)) - s.mux.HandleFunc("/v1/query/", s.wrap(s.PreparedQuerySpecific)) + s.handleFuncMetrics("/v1/query", s.wrap(s.PreparedQueryGeneral)) + s.handleFuncMetrics("/v1/query/", s.wrap(s.PreparedQuerySpecific)) - s.mux.HandleFunc("/v1/txn", s.wrap(s.Txn)) + s.handleFuncMetrics("/v1/txn", s.wrap(s.Txn)) if enableDebug { - s.mux.HandleFunc("/debug/pprof/", pprof.Index) - s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) - s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + s.handleFuncMetrics("/debug/pprof/", pprof.Index) + s.handleFuncMetrics("/debug/pprof/cmdline", pprof.Cmdline) + s.handleFuncMetrics("/debug/pprof/profile", pprof.Profile) + s.handleFuncMetrics("/debug/pprof/symbol", pprof.Symbol) } // Use the custom UI dir if provided. @@ -288,9 +320,9 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) { } // API's are under /internal/ui/ to avoid conflict - s.mux.HandleFunc("/v1/internal/ui/nodes", s.wrap(s.UINodes)) - s.mux.HandleFunc("/v1/internal/ui/node/", s.wrap(s.UINodeInfo)) - s.mux.HandleFunc("/v1/internal/ui/services", s.wrap(s.UIServices)) + s.handleFuncMetrics("/v1/internal/ui/nodes", s.wrap(s.UINodes)) + s.handleFuncMetrics("/v1/internal/ui/node/", s.wrap(s.UINodeInfo)) + s.handleFuncMetrics("/v1/internal/ui/services", s.wrap(s.UIServices)) } // wrap is used to wrap functions to make them more convenient