From e8b1531d50ee0c4b8317e8387903cb55a55060df Mon Sep 17 00:00:00 2001 From: Frank Schroeder Date: Sun, 11 Feb 2018 20:16:05 +0100 Subject: [PATCH 01/30] Refactor metrics This patch refactors the metrics support within fabio to achieve several goals: * support labels for DogstatD, Prometheus and others * support raw events for statsd and others without aggregation * support multiple backends simultaneously to support migration * make integration of new metrics backends and/or libraries simple * being fully backwards compatible to not break existing setups One of the main challenges is that fabio generates names for the metrics targets through templates and that these templates are configurable. A metrics backend which supports labels needs to generate a different name for the same metric than one that does not support labels. Combining this with the need to support multiple different metrics backends at runtime means that none of the existing metrics libraries like go-kit/metrics and hashicorp/go-metrics is a good fit. However, they can be used as drivers which reduces the need for testing and makes integration of new backends simpler since fabio does not need to rely on a single metrics library. This patch is work in progress with the new metrics code in the metrics4 package and the old metrics code in the 'metrics_old' package where it is kept for reference until it has been migrated or removed. The basic architecture should be good enough to add more providers and functionality so that the community can help. Right now there are two providers "flat" and "label" to demonstrate the concepts. They provide counter and timers in statsd/dogstatd format and can be configured with -metrics.target=flat[,label]. Other providers should be added in the same way. The metrics_old code should be converted to the new provider scheme. The go-kit/metrics library currently supports most of the necessary drivers and is the preferred way of integrating new drivers. The list below is a probably incomplete list of things that need to be done: * The configuration is not used except for metrics.target * The drivers from the metrics_old package need to be migrated * Additional drivers from go-kit need to be added * The old rcrowley/go-metrics code relies on 'registries' for namespacing. After the routing table has changed the registry needs to be pruned of services which no longer exist so that go-metrics stops reporting them. * The RX/TX counters in the TCP and TCP+SNI proxy are probably sub-optimal or broken. * Some counters may not have been initialized properly, e.g. WSConn * WSConn should probably be a gauge which also needs to be set to 0 on startup. * The approach of injecting a noop metrics provider if none has been set has already bitten me once since some metrics are reported while others are not. I need to think about this a bit more. Fixes #126 Fixes #165 Fixes #211 Fixes #253 Fixes #326 Fixes #327 Fixes #371 Closes #329 Closes #331 Closes #334 Closes #335 --- admin/api/routes.go | 4 +- main.go | 122 +++++++++++++--------- metrics4/flat/metrics.go | 37 +++++++ metrics4/label/metrics.go | 39 +++++++ metrics4/metrics.go | 77 ++++++++++++++ metrics4/names/names.go | 43 ++++++++ {metrics => metrics_old}/circonus.go | 0 {metrics => metrics_old}/circonus_test.go | 0 {metrics => metrics_old}/gometrics.go | 0 {metrics => metrics_old}/metrics.go | 0 {metrics => metrics_old}/metrics_test.go | 0 {metrics => metrics_old}/noop.go | 0 {metrics => metrics_old}/registry.go | 0 proxy/http_proxy.go | 29 +++-- proxy/http_raw_handler.go | 13 ++- proxy/tcp/copy_buffer.go | 6 +- proxy/tcp/sni_proxy.go | 42 +++++--- proxy/tcp/tcp_proxy.go | 28 +++-- route/route.go | 17 ++- route/table.go | 43 -------- route/table_registry_test.go | 114 ++++++++++---------- route/target.go | 7 +- 22 files changed, 409 insertions(+), 212 deletions(-) create mode 100644 metrics4/flat/metrics.go create mode 100644 metrics4/label/metrics.go create mode 100644 metrics4/metrics.go create mode 100644 metrics4/names/names.go rename {metrics => metrics_old}/circonus.go (100%) rename {metrics => metrics_old}/circonus_test.go (100%) rename {metrics => metrics_old}/gometrics.go (100%) rename {metrics => metrics_old}/metrics.go (100%) rename {metrics => metrics_old}/metrics_test.go (100%) rename {metrics => metrics_old}/noop.go (100%) rename {metrics => metrics_old}/registry.go (100%) diff --git a/admin/api/routes.go b/admin/api/routes.go index 10c2ac373..78bc40a63 100644 --- a/admin/api/routes.go +++ b/admin/api/routes.go @@ -59,8 +59,8 @@ func (h *RoutesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { Weight: tg.Weight, Tags: tg.Tags, Cmd: "route add", - Rate1: tg.Timer.Rate1(), - Pct99: tg.Timer.Percentile(0.99), + // Rate1: tg.Timer.Rate1(), + // Pct99: tg.Timer.Percentile(0.99), } routes = append(routes, ar) } diff --git a/main.go b/main.go index 601503c6a..353f21b98 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,9 @@ import ( "github.com/fabiolb/fabio/config" "github.com/fabiolb/fabio/exit" "github.com/fabiolb/fabio/logger" - "github.com/fabiolb/fabio/metrics" + "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/flat" + "github.com/fabiolb/fabio/metrics4/label" "github.com/fabiolb/fabio/noroute" "github.com/fabiolb/fabio/proxy" "github.com/fabiolb/fabio/proxy/tcp" @@ -113,9 +115,7 @@ func main() { registry.Default.DeregisterAll() }) - // init metrics early since that create the global metric registries - // that are used by other parts of the code. - initMetrics(cfg) + metrics := initMetrics(cfg) initRuntime(cfg) initBackend(cfg) startAdmin(cfg) @@ -123,12 +123,12 @@ func main() { go watchNoRouteHTML(cfg) first := make(chan bool) - go watchBackend(cfg, first) + go watchBackend(cfg, metrics, first) log.Print("[INFO] Waiting for first routing table") <-first // create proxies after metrics since they use the metrics registry. - startServers(cfg) + startServers(cfg, metrics) // warn again so that it is visible in the terminal WarnIfRunAsRoot(cfg.Insecure) @@ -137,7 +137,7 @@ func main() { log.Print("[INFO] Down") } -func newHTTPProxy(cfg *config.Config) http.Handler { +func newHTTPProxy(cfg *config.Config, stats metrics4.Provider) http.Handler { var w io.Writer switch cfg.Log.AccessTarget { case "": @@ -164,7 +164,7 @@ func newHTTPProxy(cfg *config.Config) http.Handler { pick := route.Picker[cfg.Proxy.Strategy] match := route.Matcher[cfg.Proxy.Matcher] - notFound := metrics.DefaultRegistry.GetCounter("notfound") + notFound := stats.NewCounter("notfound") log.Printf("[INFO] Using routing strategy %q", cfg.Proxy.Strategy) log.Printf("[INFO] Using route matching %q", cfg.Proxy.Matcher) @@ -187,24 +187,24 @@ func newHTTPProxy(cfg *config.Config) http.Handler { Lookup: func(r *http.Request) *route.Target { t := route.GetTable().Lookup(r, r.Header.Get("trace"), pick, match) if t == nil { - notFound.Inc(1) + notFound.Count(1) log.Print("[WARN] No route for ", r.Host, r.URL) } return t }, - Requests: metrics.DefaultRegistry.GetTimer("requests"), - Noroute: metrics.DefaultRegistry.GetCounter("notfound"), + Requests: stats.NewTimer("requests"), + Noroute: stats.NewCounter("notfound"), + Metrics: stats, Logger: l, } } -func lookupHostFn(cfg *config.Config) func(string) *route.Target { +func lookupHostFn(cfg *config.Config, notFound metrics4.Counter) func(string) *route.Target { pick := route.Picker[cfg.Proxy.Strategy] - notFound := metrics.DefaultRegistry.GetCounter("notfound") return func(host string) *route.Target { t := route.GetTable().LookupHost(host, pick) if t == nil { - notFound.Inc(1) + notFound.Count(1) log.Print("[WARN] No route for ", host) } return t @@ -249,7 +249,7 @@ func startAdmin(cfg *config.Config) { }() } -func startServers(cfg *config.Config) { +func startServers(cfg *config.Config, stats metrics4.Provider) { for _, l := range cfg.Listen { l := l // capture loop var for go routines below tlscfg, err := makeTLSConfig(l) @@ -262,10 +262,11 @@ func startServers(cfg *config.Config) { log.Printf("[INFO] Client certificate authentication enabled on %s", l.Addr) } + notFound := stats.NewCounter("notfound") switch l.Proto { case "http", "https": go func() { - h := newHTTPProxy(cfg) + h := newHTTPProxy(cfg, stats) if err := proxy.ListenAndServeHTTP(l, h, tlscfg); err != nil { exit.Fatal("[FATAL] ", err) } @@ -274,10 +275,11 @@ func startServers(cfg *config.Config) { go func() { h := &tcp.Proxy{ DialTimeout: cfg.Proxy.DialTimeout, - Lookup: lookupHostFn(cfg), - Conn: metrics.DefaultRegistry.GetCounter("tcp.conn"), - ConnFail: metrics.DefaultRegistry.GetCounter("tcp.connfail"), - Noroute: metrics.DefaultRegistry.GetCounter("tcp.noroute"), + Lookup: lookupHostFn(cfg, notFound), + Conn: stats.NewCounter("tcp.conn"), + ConnFail: stats.NewCounter("tcp.connfail"), + Noroute: stats.NewCounter("tcp.noroute"), + Metrics: stats, } if err := proxy.ListenAndServeTCP(l, h, tlscfg); err != nil { exit.Fatal("[FATAL] ", err) @@ -287,10 +289,11 @@ func startServers(cfg *config.Config) { go func() { h := &tcp.SNIProxy{ DialTimeout: cfg.Proxy.DialTimeout, - Lookup: lookupHostFn(cfg), - Conn: metrics.DefaultRegistry.GetCounter("tcp_sni.conn"), - ConnFail: metrics.DefaultRegistry.GetCounter("tcp_sni.connfail"), - Noroute: metrics.DefaultRegistry.GetCounter("tcp_sni.noroute"), + Lookup: lookupHostFn(cfg, notFound), + Conn: stats.NewCounter("tcp_sni.conn"), + ConnFail: stats.NewCounter("tcp_sni.connfail"), + Noroute: stats.NewCounter("tcp_sni.noroute"), + Metrics: stats, } if err := proxy.ListenAndServeTCP(l, h, tlscfg); err != nil { exit.Fatal("[FATAL] ", err) @@ -302,31 +305,27 @@ func startServers(cfg *config.Config) { } } -func initMetrics(cfg *config.Config) { +func initMetrics(cfg *config.Config) metrics4.Provider { + mp := &metrics4.MultiProvider{} if cfg.Metrics.Target == "" { log.Printf("[INFO] Metrics disabled") - return } - - var deadline = time.Now().Add(cfg.Metrics.Timeout) - var err error - for { - metrics.DefaultRegistry, err = metrics.NewRegistry(cfg.Metrics) - if err == nil { - route.ServiceRegistry, err = metrics.NewRegistry(cfg.Metrics) - } - if err == nil { - return - } - if time.Now().After(deadline) { - exit.Fatal("[FATAL] ", err) - } - log.Print("[WARN] Error initializing metrics. ", err) - time.Sleep(cfg.Metrics.Retry) - if atomic.LoadInt32(&shuttingDown) > 0 { - exit.Exit(1) + for _, x := range strings.Split(cfg.Metrics.Target, ",") { + x = strings.TrimSpace(x) + var p metrics4.Provider + switch x { + case "flat": + p = &flat.Provider{} + case "label": + p = &label.Provider{} + default: + log.Printf("[WARN] Skipping unknown metrics provider %q", x) + continue } + log.Printf("[INFO] Registering metrics provider %q", x) + mp.Register(p) } + return mp } func initRuntime(cfg *config.Config) { @@ -379,7 +378,7 @@ func initBackend(cfg *config.Config) { } } -func watchBackend(cfg *config.Config, first chan bool) { +func watchBackend(cfg *config.Config, p metrics4.Provider, first chan bool) { var ( last string svccfg string @@ -410,19 +409,46 @@ func watchBackend(cfg *config.Config, first chan bool) { } registry.Default.Register(aliases) - t, err := route.NewTable(next) + newTable, err := route.NewTable(next) if err != nil { log.Printf("[WARN] %s", err) continue } - route.SetTable(t) - logRoutes(t, last, next, cfg.Log.RoutesFormat) + + oldTable := route.GetTable() + route.SetTable(newTable) + unregisterMetrics(p, oldTable, newTable) + logRoutes(newTable, last, next, cfg.Log.RoutesFormat) + last = next once.Do(func() { close(first) }) } } +func unregisterMetrics(p metrics4.Provider, oldTable, newTable route.Table) { + names := func(t route.Table) map[string]bool { + m := map[string]bool{} + for _, routes := range t { + for _, r := range routes { + for _, t := range r.Targets { + m[t.TimerName.String()] = true + } + } + } + return m + } + + oldNames := names(oldTable) + newNames := names(newTable) + for n := range oldNames { + if !newNames[n] { + log.Printf("[INFO] Unregistering metric %s", n) + p.Unregister(n) + } + } +} + func watchNoRouteHTML(cfg *config.Config) { html := registry.Default.WatchNoRouteHTML() for { diff --git a/metrics4/flat/metrics.go b/metrics4/flat/metrics.go new file mode 100644 index 000000000..9260770fa --- /dev/null +++ b/metrics4/flat/metrics.go @@ -0,0 +1,37 @@ +package flat + +import ( + "fmt" + "time" + + "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/names" +) + +type Provider struct{} + +func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { + return &Counter{Name: names.Flatten(name, labels, names.DotSeparator)} +} + +func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { + return &Timer{Name: names.Flatten(name, labels, names.DotSeparator)} +} + +func (p *Provider) Unregister(interface{}) {} + +type Counter struct { + Name string +} + +func (c *Counter) Count(n int) { + fmt.Printf("%s:%d|c\n", c.Name, n) +} + +type Timer struct { + Name string +} + +func (t *Timer) Update(d time.Duration) { + fmt.Printf("%s:%d|ms\n", t.Name, d/time.Millisecond) +} diff --git a/metrics4/label/metrics.go b/metrics4/label/metrics.go new file mode 100644 index 000000000..8fde49924 --- /dev/null +++ b/metrics4/label/metrics.go @@ -0,0 +1,39 @@ +package label + +import ( + "fmt" + "time" + + "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/names" +) + +type Provider struct{} + +func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { + return &Counter{Name: name, Labels: labels} +} + +func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { + return &Timer{Name: name, Labels: labels} +} + +func (p *Provider) Unregister(interface{}) {} + +type Counter struct { + Name string + Labels []string +} + +func (c *Counter) Count(n int) { + fmt.Printf("%s:%d|c%s\n", c.Name, n, names.Labels(c.Labels, "|#", ":", ",")) +} + +type Timer struct { + Name string + Labels []string +} + +func (t *Timer) Update(d time.Duration) { + fmt.Printf("%s:%d|ns%s\n", t.Name, d.Nanoseconds(), names.Labels(t.Labels, "|#", ":", ",")) +} diff --git a/metrics4/metrics.go b/metrics4/metrics.go new file mode 100644 index 000000000..9f0b04442 --- /dev/null +++ b/metrics4/metrics.go @@ -0,0 +1,77 @@ +package metrics4 + +import ( + "time" +) + +type Provider interface { + NewCounter(name string, labels ...string) Counter + NewTimer(name string, labels ...string) Timer + Unregister(v interface{}) +} + +type MultiProvider struct { + p []Provider +} + +func (mp *MultiProvider) Register(p Provider) { + mp.p = append(mp.p, p) +} + +func (mp *MultiProvider) NewCounter(name string, labels ...string) Counter { + m := &MultiCounter{} + for _, p := range mp.p { + m.Register(p.NewCounter(name, labels...)) + } + return m +} + +func (mp *MultiProvider) NewTimer(name string, labels ...string) Timer { + m := &MultiTimer{} + for _, p := range mp.p { + m.Register(p.NewTimer(name, labels...)) + } + return m +} + +func (mp *MultiProvider) Unregister(v interface{}) { + for _, p := range mp.p { + p.Unregister(v) + } +} + +type Counter interface { + Count(int) +} + +type MultiCounter struct { + c []Counter +} + +func (mc *MultiCounter) Register(c Counter) { + mc.c = append(mc.c, c) +} + +func (mc *MultiCounter) Count(n int) { + for _, c := range mc.c { + c.Count(n) + } +} + +type Timer interface { + Update(time.Duration) +} + +type MultiTimer struct { + t []Timer +} + +func (mt *MultiTimer) Register(t Timer) { + mt.t = append(mt.t, t) +} + +func (mt *MultiTimer) Update(d time.Duration) { + for _, t := range mt.t { + t.Update(d) + } +} diff --git a/metrics4/names/names.go b/metrics4/names/names.go new file mode 100644 index 000000000..8ec1b64d1 --- /dev/null +++ b/metrics4/names/names.go @@ -0,0 +1,43 @@ +package names + +import ( + "net/url" + "strings" +) + +type Service struct { + Service string + Host string + Path string + TargetURL *url.URL +} + +func (s Service) String() string { + return s.Service +} + +const DotSeparator = "." +const PipeSeparator = "|" + +func Flatten(name string, labels []string, separator string) string { + if len(labels) == 0 { + return name + } + return name + separator + strings.Join(labels, separator) +} + +// todo(fs): this function probably allocates like crazy. If on the stack then it might be ok. +// todo(fs): otherwise, give some love. +func Labels(labels []string, prefix, fieldsep, recsep string) string { + if len(labels) == 0 { + return "" + } + if len(labels)%2 != 0 { + labels = append(labels, "???") + } + var fields []string + for i := 0; i < len(labels); i += 2 { + fields = append(fields, labels[i]+fieldsep+labels[i+1]) + } + return prefix + strings.Join(fields, recsep) +} diff --git a/metrics/circonus.go b/metrics_old/circonus.go similarity index 100% rename from metrics/circonus.go rename to metrics_old/circonus.go diff --git a/metrics/circonus_test.go b/metrics_old/circonus_test.go similarity index 100% rename from metrics/circonus_test.go rename to metrics_old/circonus_test.go diff --git a/metrics/gometrics.go b/metrics_old/gometrics.go similarity index 100% rename from metrics/gometrics.go rename to metrics_old/gometrics.go diff --git a/metrics/metrics.go b/metrics_old/metrics.go similarity index 100% rename from metrics/metrics.go rename to metrics_old/metrics.go diff --git a/metrics/metrics_test.go b/metrics_old/metrics_test.go similarity index 100% rename from metrics/metrics_test.go rename to metrics_old/metrics_test.go diff --git a/metrics/noop.go b/metrics_old/noop.go similarity index 100% rename from metrics/noop.go rename to metrics_old/noop.go diff --git a/metrics/registry.go b/metrics_old/registry.go similarity index 100% rename from metrics/registry.go rename to metrics_old/registry.go diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index b531373af..d378ee379 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -14,7 +14,7 @@ import ( "github.com/fabiolb/fabio/config" "github.com/fabiolb/fabio/logger" - "github.com/fabiolb/fabio/metrics" + "github.com/fabiolb/fabio/metrics4" "github.com/fabiolb/fabio/noroute" "github.com/fabiolb/fabio/proxy/gzip" "github.com/fabiolb/fabio/route" @@ -44,11 +44,17 @@ type HTTPProxy struct { Lookup func(*http.Request) *route.Target // Requests is a timer metric which is updated for every request. - Requests metrics.Timer + Requests metrics4.Timer // Noroute is a counter metric which is updated for every request // where Lookup() returns nil. - Noroute metrics.Counter + Noroute metrics4.Counter + + // WSConn counts the number of open web socket connections. + WSConn metrics4.Counter + + // Metrics is the configured metrics backend provider. + Metrics metrics4.Provider // Logger is the access logger for the requests. Logger logger.Logger @@ -63,6 +69,11 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { panic("no lookup function") } + metrics := p.Metrics + if metrics == nil { + metrics = &metrics4.MultiProvider{} + } + if p.Config.RequestID != "" { id := p.UUID if id == nil { @@ -104,7 +115,7 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { if t.Timer != nil { t.Timer.Update(0) } - metrics.DefaultRegistry.GetTimer(key(t.RedirectCode)).Update(0) + metrics.NewCounter("http.status", "code", strconv.Itoa(t.RedirectCode)).Count(1) return } @@ -155,13 +166,13 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch { case upgrade == "websocket" || upgrade == "Websocket": r.URL = targetURL + dial := net.Dial if targetURL.Scheme == "https" || targetURL.Scheme == "wss" { - h = newRawProxy(targetURL.Host, func(network, address string) (net.Conn, error) { + dial = func(network, address string) (net.Conn, error) { return tls.Dial(network, address, tr.(*http.Transport).TLSClientConfig) - }) - } else { - h = newRawProxy(targetURL.Host, net.Dial) + } } + h = newRawProxy(targetURL.Host, dial, p.WSConn) case accept == "text/event-stream": // use the flush interval for SSE (server-sent events) @@ -197,7 +208,7 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - metrics.DefaultRegistry.GetTimer(key(rw.code)).Update(dur) + metrics.NewTimer("http.status", "code", strconv.Itoa(rw.code)).Update(dur) // write access log if p.Logger != nil { diff --git a/proxy/http_raw_handler.go b/proxy/http_raw_handler.go index a2cf52e30..ccca8fea1 100644 --- a/proxy/http_raw_handler.go +++ b/proxy/http_raw_handler.go @@ -6,21 +6,20 @@ import ( "net" "net/http" - "github.com/fabiolb/fabio/metrics" + "github.com/fabiolb/fabio/metrics4" ) -// conn measures the number of open web socket connections -var conn = metrics.DefaultRegistry.GetCounter("ws.conn") - type dialFunc func(network, address string) (net.Conn, error) // newRawProxy returns an HTTP handler which forwards data between // an incoming and outgoing TCP connection including the original request. // This handler establishes a new outgoing connection per request. -func newRawProxy(host string, dial dialFunc) http.Handler { +func newRawProxy(host string, dial dialFunc, conn metrics4.Counter) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - conn.Inc(1) - defer func() { conn.Inc(-1) }() + if conn != nil { + conn.Count(1) + defer func() { conn.Count(-1) }() + } hj, ok := w.(http.Hijacker) if !ok { diff --git a/proxy/tcp/copy_buffer.go b/proxy/tcp/copy_buffer.go index c06c50fd1..ce7395b14 100644 --- a/proxy/tcp/copy_buffer.go +++ b/proxy/tcp/copy_buffer.go @@ -3,12 +3,12 @@ package tcp import ( "io" - "github.com/fabiolb/fabio/metrics" + "github.com/fabiolb/fabio/metrics4" ) // copyBuffer is an adapted version of io.copyBuffer which updates a // counter instead of returning the total bytes written. -func copyBuffer(dst io.Writer, src io.Reader, c metrics.Counter) (err error) { +func copyBuffer(dst io.Writer, src io.Reader, c metrics4.Counter) (err error) { buf := make([]byte, 32*1024) for { nr, er := src.Read(buf) @@ -16,7 +16,7 @@ func copyBuffer(dst io.Writer, src io.Reader, c metrics.Counter) (err error) { nw, ew := dst.Write(buf[0:nr]) if nw > 0 { if c != nil { - c.Inc(int64(nw)) + c.Count(nw) } } if ew != nil { diff --git a/proxy/tcp/sni_proxy.go b/proxy/tcp/sni_proxy.go index d9920876c..bea6f1fff 100644 --- a/proxy/tcp/sni_proxy.go +++ b/proxy/tcp/sni_proxy.go @@ -7,7 +7,7 @@ import ( "net" "time" - "github.com/fabiolb/fabio/metrics" + "github.com/fabiolb/fabio/metrics4" "github.com/fabiolb/fabio/route" ) @@ -26,20 +26,28 @@ type SNIProxy struct { Lookup func(host string) *route.Target // Conn counts the number of connections. - Conn metrics.Counter + Conn metrics4.Counter // ConnFail counts the failed upstream connection attempts. - ConnFail metrics.Counter + ConnFail metrics4.Counter // Noroute counts the failed Lookup() calls. - Noroute metrics.Counter + Noroute metrics4.Counter + + // Metrics is the configured metrics backend provider. + Metrics metrics4.Provider } func (p *SNIProxy) ServeTCP(in net.Conn) error { defer in.Close() + metrics := p.Metrics + if metrics == nil { + metrics = &metrics4.MultiProvider{} + } + if p.Conn != nil { - p.Conn.Inc(1) + p.Conn.Count(1) } tlsReader := bufio.NewReader(in) @@ -47,7 +55,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Print("[DEBUG] tcp+sni: TLS handshake failed (failed to peek data)") if p.ConnFail != nil { - p.ConnFail.Inc(1) + p.ConnFail.Count(1) } return err } @@ -56,7 +64,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Printf("[DEBUG] tcp+sni: TLS handshake failed (%s)", err) if p.ConnFail != nil { - p.ConnFail.Inc(1) + p.ConnFail.Count(1) } return err } @@ -66,7 +74,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Printf("[DEBUG] tcp+sni: TLS handshake failed (%s)", err) if p.ConnFail != nil { - p.ConnFail.Inc(1) + p.ConnFail.Count(1) } return err } @@ -77,7 +85,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if !ok { log.Print("[DEBUG] tcp+sni: TLS handshake failed (unable to parse client hello)") if p.ConnFail != nil { - p.ConnFail.Inc(1) + p.ConnFail.Count(1) } return nil } @@ -85,7 +93,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if host == "" { log.Print("[DEBUG] tcp+sni: server_name missing") if p.ConnFail != nil { - p.ConnFail.Inc(1) + p.ConnFail.Count(1) } return nil } @@ -93,7 +101,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { t := p.Lookup(host) if t == nil { if p.Noroute != nil { - p.Noroute.Inc(1) + p.Noroute.Count(1) } return nil } @@ -107,7 +115,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Print("[WARN] tcp+sni: cannot connect to upstream ", addr) if p.ConnFail != nil { - p.ConnFail.Inc(1) + p.ConnFail.Count(1) } return err } @@ -118,23 +126,23 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Print("[WARN] tcp+sni: copy client hello failed. ", err) if p.ConnFail != nil { - p.ConnFail.Inc(1) + p.ConnFail.Count(1) } return err } errc := make(chan error, 2) - cp := func(dst io.Writer, src io.Reader, c metrics.Counter) { + cp := func(dst io.Writer, src io.Reader, c metrics4.Counter) { errc <- copyBuffer(dst, src, c) } // rx measures the traffic to the upstream server (in <- out) // tx measures the traffic from the upstream server (out <- in) - rx := metrics.DefaultRegistry.GetCounter(t.TimerName + ".rx") - tx := metrics.DefaultRegistry.GetCounter(t.TimerName + ".tx") + rx := metrics.NewCounter(t.TimerName.String() + ".rx") + tx := metrics.NewCounter(t.TimerName.String() + ".tx") // we've received the ClientHello already - rx.Inc(int64(n)) + rx.Count(n) go cp(in, out, rx) go cp(out, in, tx) diff --git a/proxy/tcp/tcp_proxy.go b/proxy/tcp/tcp_proxy.go index 17bf69572..494679131 100644 --- a/proxy/tcp/tcp_proxy.go +++ b/proxy/tcp/tcp_proxy.go @@ -6,7 +6,7 @@ import ( "net" "time" - "github.com/fabiolb/fabio/metrics" + "github.com/fabiolb/fabio/metrics4" "github.com/fabiolb/fabio/route" ) @@ -21,20 +21,28 @@ type Proxy struct { Lookup func(host string) *route.Target // Conn counts the number of connections. - Conn metrics.Counter + Conn metrics4.Counter // ConnFail counts the failed upstream connection attempts. - ConnFail metrics.Counter + ConnFail metrics4.Counter // Noroute counts the failed Lookup() calls. - Noroute metrics.Counter + Noroute metrics4.Counter + + // Metrics is the configured metrics backend provider. + Metrics metrics4.Provider } func (p *Proxy) ServeTCP(in net.Conn) error { defer in.Close() + metrics := p.Metrics + if metrics == nil { + metrics = &metrics4.MultiProvider{} + } + if p.Conn != nil { - p.Conn.Inc(1) + p.Conn.Count(1) } _, port, _ := net.SplitHostPort(in.LocalAddr().String()) @@ -42,7 +50,7 @@ func (p *Proxy) ServeTCP(in net.Conn) error { t := p.Lookup(port) if t == nil { if p.Noroute != nil { - p.Noroute.Inc(1) + p.Noroute.Count(1) } return nil } @@ -56,21 +64,21 @@ func (p *Proxy) ServeTCP(in net.Conn) error { if err != nil { log.Print("[WARN] tcp: cannot connect to upstream ", addr) if p.ConnFail != nil { - p.ConnFail.Inc(1) + p.ConnFail.Count(1) } return err } defer out.Close() errc := make(chan error, 2) - cp := func(dst io.Writer, src io.Reader, c metrics.Counter) { + cp := func(dst io.Writer, src io.Reader, c metrics4.Counter) { errc <- copyBuffer(dst, src, c) } // rx measures the traffic to the upstream server (in <- out) // tx measures the traffic from the upstream server (out <- in) - rx := metrics.DefaultRegistry.GetCounter(t.TimerName + ".rx") - tx := metrics.DefaultRegistry.GetCounter(t.TimerName + ".tx") + rx := metrics.NewCounter(t.TimerName.String() + ".rx") + tx := metrics.NewCounter(t.TimerName.String() + ".tx") go cp(in, out, rx) go cp(out, in, tx) diff --git a/route/route.go b/route/route.go index 0f9e35dc9..4b2c1a71e 100644 --- a/route/route.go +++ b/route/route.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - "github.com/fabiolb/fabio/metrics" + "github.com/fabiolb/fabio/metrics4/names" "github.com/gobwas/glob" ) @@ -54,22 +54,21 @@ func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float6 } } - name, err := metrics.TargetName(service, r.Host, r.Path, targetURL) - if err != nil { - log.Printf("[ERROR] Invalid metrics name: %s", err) - name = "unknown" - } - t := &Target{ Service: service, Tags: tags, Opts: opts, URL: targetURL, FixedWeight: fixedWeight, - Timer: ServiceRegistry.GetTimer(name), - TimerName: name, + TimerName: names.Service{ + Service: service, + Host: r.Host, + Path: r.Path, + TargetURL: targetURL, + }, } + var err error if opts != nil { t.StripPath = opts["strip"] t.TLSSkipVerify = opts["tlsskipverify"] == "true" diff --git a/route/table.go b/route/table.go index c417e2c34..3da5a219f 100644 --- a/route/table.go +++ b/route/table.go @@ -9,10 +9,8 @@ import ( "net/url" "sort" "strings" - "sync" "sync/atomic" - "github.com/fabiolb/fabio/metrics" "github.com/gobwas/glob" ) @@ -23,9 +21,6 @@ var errNoMatch = errors.New("route: no target match") // table stores the active routing table. Must never be nil. var table atomic.Value -// ServiceRegistry stores the metrics for the services. -var ServiceRegistry metrics.Registry = metrics.NoopRegistry{} - // init initializes the routing table. func init() { table.Store(make(Table)) @@ -38,9 +33,6 @@ func GetTable() Table { return table.Load().(Table) } -// mu guards table and registry in SetTable. -var mu sync.Mutex - // SetTable sets the active routing table. A nil value // logs a warning and is ignored. The function is safe // to be called from multiple goroutines. @@ -49,42 +41,7 @@ func SetTable(t Table) { log.Print("[WARN] Ignoring nil routing table") return } - mu.Lock() table.Store(t) - syncRegistry(t) - mu.Unlock() -} - -// syncRegistry unregisters all inactive timers. -// It assumes that all timers of the table have -// already been registered. -func syncRegistry(t Table) { - timers := map[string]bool{} - - // get all registered timers - for _, name := range ServiceRegistry.Names() { - timers[name] = false - } - - // mark the ones from this table as active. - // this can also add new entries but we do not - // really care since we are only interested in the - // inactive ones. - for _, routes := range t { - for _, r := range routes { - for _, tg := range r.Targets { - timers[tg.TimerName] = true - } - } - } - - // unregister inactive timers - for name, active := range timers { - if !active { - ServiceRegistry.Unregister(name) - log.Printf("[INFO] Unregistered timer %s", name) - } - } } // Table contains a set of routes grouped by host. diff --git a/route/table_registry_test.go b/route/table_registry_test.go index 3616dd9c5..5607fe3fe 100644 --- a/route/table_registry_test.go +++ b/route/table_registry_test.go @@ -1,63 +1,55 @@ package route -import ( - "reflect" - "sort" - "testing" - - "github.com/fabiolb/fabio/metrics" -) - -func TestSyncRegistry(t *testing.T) { - oldRegistry := ServiceRegistry - ServiceRegistry = newStubRegistry() - defer func() { ServiceRegistry = oldRegistry }() - - tbl := make(Table) - tbl.addRoute(&RouteDef{Service: "svc-a", Src: "/aaa", Dst: "http://localhost:1234", Weight: 1}) - tbl.addRoute(&RouteDef{Service: "svc-b", Src: "/bbb", Dst: "http://localhost:5678", Weight: 1}) - if got, want := ServiceRegistry.Names(), []string{"svc-a._./aaa.localhost_1234", "svc-b._./bbb.localhost_5678"}; !reflect.DeepEqual(got, want) { - t.Fatalf("got %v want %v", got, want) - } - - tbl.delRoute(&RouteDef{Service: "svc-b", Src: "/bbb", Dst: "http://localhost:5678"}) - syncRegistry(tbl) - if got, want := ServiceRegistry.Names(), []string{"svc-a._./aaa.localhost_1234"}; !reflect.DeepEqual(got, want) { - t.Fatalf("got %v want %v", got, want) - } -} - -func newStubRegistry() metrics.Registry { - return &stubRegistry{names: make(map[string]bool)} -} - -type stubRegistry struct { - names map[string]bool -} - -func (p *stubRegistry) Names() []string { - n := []string{} - for k := range p.names { - n = append(n, k) - } - sort.Strings(n) - return n -} - -func (p *stubRegistry) Unregister(name string) { - delete(p.names, name) -} - -func (p *stubRegistry) UnregisterAll() { - p.names = map[string]bool{} -} - -func (p *stubRegistry) GetCounter(name string) metrics.Counter { - p.names[name] = true - return metrics.NoopCounter{} -} - -func (p *stubRegistry) GetTimer(name string) metrics.Timer { - p.names[name] = true - return metrics.NoopTimer{} -} +// func TestSyncRegistry(t *testing.T) { +// oldRegistry := ServiceRegistry +// ServiceRegistry = newStubRegistry() +// defer func() { ServiceRegistry = oldRegistry }() +// +// tbl := make(Table) +// tbl.addRoute(&RouteDef{Service: "svc-a", Src: "/aaa", Dst: "http://localhost:1234", Weight: 1}) +// tbl.addRoute(&RouteDef{Service: "svc-b", Src: "/bbb", Dst: "http://localhost:5678", Weight: 1}) +// if got, want := ServiceRegistry.Names(), []string{"svc-a._./aaa.localhost_1234", "svc-b._./bbb.localhost_5678"}; !reflect.DeepEqual(got, want) { +// t.Fatalf("got %v want %v", got, want) +// } +// +// tbl.delRoute(&RouteDef{Service: "svc-b", Src: "/bbb", Dst: "http://localhost:5678"}) +// syncRegistry(tbl) +// if got, want := ServiceRegistry.Names(), []string{"svc-a._./aaa.localhost_1234"}; !reflect.DeepEqual(got, want) { +// t.Fatalf("got %v want %v", got, want) +// } +// } +// +// func newStubRegistry() metrics.Registry { +// return &stubRegistry{names: make(map[string]bool)} +// } +// +// type stubRegistry struct { +// names map[string]bool +// } +// +// func (p *stubRegistry) Names() []string { +// n := []string{} +// for k := range p.names { +// n = append(n, k) +// } +// sort.Strings(n) +// return n +// } +// +// func (p *stubRegistry) Unregister(name string) { +// delete(p.names, name) +// } +// +// func (p *stubRegistry) UnregisterAll() { +// p.names = map[string]bool{} +// } +// +// func (p *stubRegistry) GetCounter(name string) metrics.Counter { +// p.names[name] = true +// return metrics.NoopCounter{} +// } +// +// func (p *stubRegistry) GetTimer(name string) metrics.Timer { +// p.names[name] = true +// return metrics.NoopTimer{} +// } diff --git a/route/target.go b/route/target.go index db9e5244b..f23cd2172 100644 --- a/route/target.go +++ b/route/target.go @@ -4,7 +4,8 @@ import ( "net/url" "strings" - "github.com/fabiolb/fabio/metrics" + "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/names" ) type Target struct { @@ -50,10 +51,10 @@ type Target struct { Weight float64 // Timer measures throughput and latency of this target - Timer metrics.Timer + Timer metrics4.Timer // TimerName is the name of the timer in the metrics registry - TimerName string + TimerName names.Service // accessRules is map of access information for the target. accessRules map[string][]interface{} From f77435669c48692a2500271dd84a92bf2543ee56 Mon Sep 17 00:00:00 2001 From: Frank Schroeder Date: Sat, 24 Mar 2018 13:41:08 +0100 Subject: [PATCH 02/30] Remove 'Register' methods and create objects directly. --- main.go | 16 +++++++--------- metrics4/metrics.go | 43 +++++++++++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/main.go b/main.go index 353f21b98..8b506aa3e 100644 --- a/main.go +++ b/main.go @@ -306,26 +306,24 @@ func startServers(cfg *config.Config, stats metrics4.Provider) { } func initMetrics(cfg *config.Config) metrics4.Provider { - mp := &metrics4.MultiProvider{} - if cfg.Metrics.Target == "" { - log.Printf("[INFO] Metrics disabled") - } + var p []metrics4.Provider for _, x := range strings.Split(cfg.Metrics.Target, ",") { x = strings.TrimSpace(x) - var p metrics4.Provider switch x { case "flat": - p = &flat.Provider{} + p = append(p, &flat.Provider{}) case "label": - p = &label.Provider{} + p = append(p, &label.Provider{}) default: log.Printf("[WARN] Skipping unknown metrics provider %q", x) continue } log.Printf("[INFO] Registering metrics provider %q", x) - mp.Register(p) } - return mp + if len(p) == 0 { + log.Printf("[INFO] Metrics disabled") + } + return metrics4.NewMultiProvider(p) } func initRuntime(cfg *config.Config) { diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 9f0b04442..3be707b3b 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -4,72 +4,83 @@ import ( "time" ) +// Provider is an abstraction of a metrics backend. type Provider interface { + // NewCounter creates a new counter object. NewCounter(name string, labels ...string) Counter + + // NewTimer creates a new timer object. NewTimer(name string, labels ...string) Timer + + // Unregister removes a previously registered + // name or metric. Required for go-metrics and + // service pruning. This signature is probably not + // correct. Unregister(v interface{}) } +// MultiProvider wraps zero or more providers. type MultiProvider struct { p []Provider } -func (mp *MultiProvider) Register(p Provider) { - mp.p = append(mp.p, p) +func NewMultiProvider(p []Provider) *MultiProvider { + return &MultiProvider{p} } +// NewCounter creates a MultiCounter with counter objects for all registered +// providers. func (mp *MultiProvider) NewCounter(name string, labels ...string) Counter { - m := &MultiCounter{} + var c []Counter for _, p := range mp.p { - m.Register(p.NewCounter(name, labels...)) + c = append(c, p.NewCounter(name, labels...)) } - return m + return &MultiCounter{c} } +// NewTimer creates a MultiTimer with timer objects for all registered +// providers. func (mp *MultiProvider) NewTimer(name string, labels ...string) Timer { - m := &MultiTimer{} + var t []Timer for _, p := range mp.p { - m.Register(p.NewTimer(name, labels...)) + t = append(t, p.NewTimer(name, labels...)) } - return m + return &MultiTimer{t} } +// Unregister removes the metric object from all registered providers. func (mp *MultiProvider) Unregister(v interface{}) { for _, p := range mp.p { p.Unregister(v) } } +// Count measures a number. type Counter interface { Count(int) } +// MultiCounter wraps zero or more counters. type MultiCounter struct { c []Counter } -func (mc *MultiCounter) Register(c Counter) { - mc.c = append(mc.c, c) -} - func (mc *MultiCounter) Count(n int) { for _, c := range mc.c { c.Count(n) } } +// Timer measures the time of an event. type Timer interface { Update(time.Duration) } +// MultTimer wraps zero or more timers. type MultiTimer struct { t []Timer } -func (mt *MultiTimer) Register(t Timer) { - mt.t = append(mt.t, t) -} - func (mt *MultiTimer) Update(d time.Duration) { for _, t := range mt.t { t.Update(d) From 713c450530bea93dc3c5a86727bf52f47fb2b3cf Mon Sep 17 00:00:00 2001 From: Frank Schroeder Date: Sat, 24 Mar 2018 14:58:55 +0100 Subject: [PATCH 03/30] Add Gauge and use it for websocket connections --- main.go | 5 ++++- metrics4/flat/metrics.go | 12 ++++++++++++ metrics4/label/metrics.go | 13 +++++++++++++ metrics4/metrics.go | 29 +++++++++++++++++++++++++++++ proxy/http_proxy.go | 2 +- proxy/http_raw_handler.go | 12 +++++++++--- 6 files changed, 68 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 8b506aa3e..b57b187ef 100644 --- a/main.go +++ b/main.go @@ -137,7 +137,7 @@ func main() { log.Print("[INFO] Down") } -func newHTTPProxy(cfg *config.Config, stats metrics4.Provider) http.Handler { +func newHTTPProxy(cfg *config.Config, stats metrics4.Provider) *proxy.HTTPProxy { var w io.Writer switch cfg.Log.AccessTarget { case "": @@ -194,6 +194,7 @@ func newHTTPProxy(cfg *config.Config, stats metrics4.Provider) http.Handler { }, Requests: stats.NewTimer("requests"), Noroute: stats.NewCounter("notfound"), + WSConn: stats.NewGauge("ws.conn"), Metrics: stats, Logger: l, } @@ -267,6 +268,8 @@ func startServers(cfg *config.Config, stats metrics4.Provider) { case "http", "https": go func() { h := newHTTPProxy(cfg, stats) + // reset the ws.conn gauge + h.WSConn.Update(0) if err := proxy.ListenAndServeHTTP(l, h, tlscfg); err != nil { exit.Fatal("[FATAL] ", err) } diff --git a/metrics4/flat/metrics.go b/metrics4/flat/metrics.go index 9260770fa..5abdaf71e 100644 --- a/metrics4/flat/metrics.go +++ b/metrics4/flat/metrics.go @@ -14,6 +14,10 @@ func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { return &Counter{Name: names.Flatten(name, labels, names.DotSeparator)} } +func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { + return &Gauge{Name: names.Flatten(name, labels, names.DotSeparator)} +} + func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { return &Timer{Name: names.Flatten(name, labels, names.DotSeparator)} } @@ -28,6 +32,14 @@ func (c *Counter) Count(n int) { fmt.Printf("%s:%d|c\n", c.Name, n) } +type Gauge struct { + Name string +} + +func (g *Gauge) Update(n int) { + fmt.Printf("%s:%d|g\n", g.Name, n) +} + type Timer struct { Name string } diff --git a/metrics4/label/metrics.go b/metrics4/label/metrics.go index 8fde49924..4b18356e0 100644 --- a/metrics4/label/metrics.go +++ b/metrics4/label/metrics.go @@ -14,6 +14,10 @@ func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { return &Counter{Name: name, Labels: labels} } +func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { + return &Gauge{Name: name, Labels: labels} +} + func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { return &Timer{Name: name, Labels: labels} } @@ -29,6 +33,15 @@ func (c *Counter) Count(n int) { fmt.Printf("%s:%d|c%s\n", c.Name, n, names.Labels(c.Labels, "|#", ":", ",")) } +type Gauge struct { + Name string + Labels []string +} + +func (g *Gauge) Update(n int) { + fmt.Printf("%s:%d|g%s\n", g.Name, n, names.Labels(g.Labels, "|#", ":", ",")) +} + type Timer struct { Name string Labels []string diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 3be707b3b..bfa79caa9 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -9,6 +9,9 @@ type Provider interface { // NewCounter creates a new counter object. NewCounter(name string, labels ...string) Counter + // NewGauge creates a new gauge object. + NewGauge(name string, labels ...string) Gauge + // NewTimer creates a new timer object. NewTimer(name string, labels ...string) Timer @@ -38,6 +41,16 @@ func (mp *MultiProvider) NewCounter(name string, labels ...string) Counter { return &MultiCounter{c} } +// NewGauge creates a MultiGauge with gauge objects for all registered +// providers. +func (mp *MultiProvider) NewGauge(name string, labels ...string) Gauge { + var v []Gauge + for _, p := range mp.p { + v = append(v, p.NewGauge(name, labels...)) + } + return &MultiGauge{v} +} + // NewTimer creates a MultiTimer with timer objects for all registered // providers. func (mp *MultiProvider) NewTimer(name string, labels ...string) Timer { @@ -71,6 +84,22 @@ func (mc *MultiCounter) Count(n int) { } } +// Gauge measures a value. +type Gauge interface { + Update(int) +} + +// MultiGauge wraps zero or more gauges. +type MultiGauge struct { + v []Gauge +} + +func (m *MultiGauge) Update(n int) { + for _, v := range m.v { + v.Update(n) + } +} + // Timer measures the time of an event. type Timer interface { Update(time.Duration) diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index d378ee379..08756450e 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -51,7 +51,7 @@ type HTTPProxy struct { Noroute metrics4.Counter // WSConn counts the number of open web socket connections. - WSConn metrics4.Counter + WSConn metrics4.Gauge // Metrics is the configured metrics backend provider. Metrics metrics4.Provider diff --git a/proxy/http_raw_handler.go b/proxy/http_raw_handler.go index ccca8fea1..1c4a16045 100644 --- a/proxy/http_raw_handler.go +++ b/proxy/http_raw_handler.go @@ -5,20 +5,26 @@ import ( "log" "net" "net/http" + "sync/atomic" "github.com/fabiolb/fabio/metrics4" ) +// conns keeps track of the number of open ws connections +var conns int64 + type dialFunc func(network, address string) (net.Conn, error) // newRawProxy returns an HTTP handler which forwards data between // an incoming and outgoing TCP connection including the original request. // This handler establishes a new outgoing connection per request. -func newRawProxy(host string, dial dialFunc, conn metrics4.Counter) http.Handler { +func newRawProxy(host string, dial dialFunc, conn metrics4.Gauge) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if conn != nil { - conn.Count(1) - defer func() { conn.Count(-1) }() + conn.Update(int(atomic.AddInt64(&conns, 1))) + defer func() { + conn.Update(int(atomic.AddInt64(&conns, -1))) + }() } hj, ok := w.(http.Hijacker) From 806c4f0581a2577f93af2d23670fb7700863e3cc Mon Sep 17 00:00:00 2001 From: Frank Schroeder Date: Sat, 24 Mar 2018 14:59:51 +0100 Subject: [PATCH 04/30] Vendoring in github.com/alexcesaro/statsd --- .../github.com/alexcesaro/statsd/CHANGELOG.md | 64 +++++ vendor/github.com/alexcesaro/statsd/LICENSE | 20 ++ vendor/github.com/alexcesaro/statsd/README.md | 50 ++++ vendor/github.com/alexcesaro/statsd/conn.go | 270 ++++++++++++++++++ vendor/github.com/alexcesaro/statsd/doc.go | 29 ++ .../github.com/alexcesaro/statsd/options.go | 250 ++++++++++++++++ vendor/github.com/alexcesaro/statsd/statsd.go | 169 +++++++++++ vendor/vendor.json | 1 + 8 files changed, 853 insertions(+) create mode 100644 vendor/github.com/alexcesaro/statsd/CHANGELOG.md create mode 100644 vendor/github.com/alexcesaro/statsd/LICENSE create mode 100644 vendor/github.com/alexcesaro/statsd/README.md create mode 100644 vendor/github.com/alexcesaro/statsd/conn.go create mode 100644 vendor/github.com/alexcesaro/statsd/doc.go create mode 100644 vendor/github.com/alexcesaro/statsd/options.go create mode 100644 vendor/github.com/alexcesaro/statsd/statsd.go diff --git a/vendor/github.com/alexcesaro/statsd/CHANGELOG.md b/vendor/github.com/alexcesaro/statsd/CHANGELOG.md new file mode 100644 index 000000000..04d811b71 --- /dev/null +++ b/vendor/github.com/alexcesaro/statsd/CHANGELOG.md @@ -0,0 +1,64 @@ +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [2.0.0] - 2016-03-20 + +- `New` signature changed. The default address used is now ":8125". To use + another address use the `Address` option: + + Before: + ``` + statsd.New(":8125") + statsd.New(":9000") + ``` + + After + ``` + statsd.New() + statsd.New(statsd.Address(":9000")) + ``` + +- The `rate` parameter has been removed from the `Count` and `Timing` methods. + Use the new `SampleRate` option instead. + +- `Count`, `Gauge` and `Timing` now accept a `interface{}` instead of an int as + the value parameter. So you can now use any type of integer or float in these + functions. + +- The `WithInfluxDBTags` and `WithDatadogTags` options were replaced by the + `TagsFormat` and `Tags` options: + + Before: + ``` + statsd.New(statsd.WithInfluxDBTags("tag", "value")) + statsd.New(statsd.WithDatadogTags("tag", "value")) + ``` + + After + ``` + statsd.New(statsd.TagsFormat(statsd.InfluxDB), statsd.Tags("tag", "value")) + statsd.New(statsd.TagsFormat(statsd.Datadog), statsd.Tags("tag", "value")) + ``` + +- All options whose named began by `With` had the `With` stripped: + + Before: + ``` + statsd.New(statsd.WithMaxPacketSize(65000)) + ``` + + After + ``` + statsd.New(statsd.MaxPacketSize(65000)) + ``` + +- `ChangeGauge` has been removed as it is a bad practice: UDP packets can be + lost so using relative changes can cause unreliable values in the long term. + Use `Gauge` instead which sends an absolute value. + +- The `Histogram` method has been added. + +- The `Clone` method was added to the `Client`, it allows to create a new + `Client` with different rate / prefix / tags parameters while still using the + same connection. diff --git a/vendor/github.com/alexcesaro/statsd/LICENSE b/vendor/github.com/alexcesaro/statsd/LICENSE new file mode 100644 index 000000000..4ec7268d5 --- /dev/null +++ b/vendor/github.com/alexcesaro/statsd/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 Alexandre Cesaro + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/alexcesaro/statsd/README.md b/vendor/github.com/alexcesaro/statsd/README.md new file mode 100644 index 000000000..774a1c687 --- /dev/null +++ b/vendor/github.com/alexcesaro/statsd/README.md @@ -0,0 +1,50 @@ +# statsd +[![Build Status](https://travis-ci.org/alexcesaro/statsd.svg?branch=v2)](https://travis-ci.org/alexcesaro/statsd) [![Code Coverage](http://gocover.io/_badge/gopkg.in/alexcesaro/statsd.v2)](http://gocover.io/gopkg.in/alexcesaro/statsd.v2) [![Documentation](https://godoc.org/gopkg.in/alexcesaro/statsd.v2?status.svg)](https://godoc.org/gopkg.in/alexcesaro/statsd.v2) + +## Introduction + +statsd is a simple and efficient [Statsd](https://github.com/etsy/statsd) +client. + +See the [benchmark](https://github.com/alexcesaro/statsdbench) for a comparison +with other Go StatsD clients. + +## Features + +- Supports all StatsD metrics: counter, gauge, timing and set +- Supports InfluxDB and Datadog tags +- Fast and GC-friendly: all functions for sending metrics do not allocate +- Efficient: metrics are buffered by default +- Simple and clean API +- 100% test coverage +- Versioned API using gopkg.in + + +## Documentation + +https://godoc.org/gopkg.in/alexcesaro/statsd.v2 + + +## Download + + go get gopkg.in/alexcesaro/statsd.v2 + + +## Example + +See the [examples in the documentation](https://godoc.org/gopkg.in/alexcesaro/statsd.v2#example-package). + + +## License + +[MIT](LICENSE) + + +## Contribute + +Do you have any question the documentation does not answer? Is there a use case +that you feel is common and is not well-addressed by the current API? + +If so you are more than welcome to ask questions in the +[thread on golang-nuts](https://groups.google.com/d/topic/golang-nuts/Tz6t4_iLgnw/discussion) +or open an issue or send a pull-request here on Github. diff --git a/vendor/github.com/alexcesaro/statsd/conn.go b/vendor/github.com/alexcesaro/statsd/conn.go new file mode 100644 index 000000000..4dbda6309 --- /dev/null +++ b/vendor/github.com/alexcesaro/statsd/conn.go @@ -0,0 +1,270 @@ +package statsd + +import ( + "io" + "math/rand" + "net" + "strconv" + "sync" + "time" +) + +type conn struct { + // Fields settable with options at Client's creation. + addr string + errorHandler func(error) + flushPeriod time.Duration + maxPacketSize int + network string + tagFormat TagFormat + + mu sync.Mutex + // Fields guarded by the mutex. + closed bool + w io.WriteCloser + buf []byte + rateCache map[float32]string +} + +func newConn(conf connConfig, muted bool) (*conn, error) { + c := &conn{ + addr: conf.Addr, + errorHandler: conf.ErrorHandler, + flushPeriod: conf.FlushPeriod, + maxPacketSize: conf.MaxPacketSize, + network: conf.Network, + tagFormat: conf.TagFormat, + } + + if muted { + return c, nil + } + + var err error + c.w, err = dialTimeout(c.network, c.addr, 5*time.Second) + if err != nil { + return c, err + } + // When using UDP do a quick check to see if something is listening on the + // given port to return an error as soon as possible. + if c.network[:3] == "udp" { + for i := 0; i < 2; i++ { + _, err = c.w.Write(nil) + if err != nil { + _ = c.w.Close() + c.w = nil + return c, err + } + } + } + + // To prevent a buffer overflow add some capacity to the buffer to allow for + // an additional metric. + c.buf = make([]byte, 0, c.maxPacketSize+200) + + if c.flushPeriod > 0 { + go func() { + ticker := time.NewTicker(c.flushPeriod) + for _ = range ticker.C { + c.mu.Lock() + if c.closed { + ticker.Stop() + c.mu.Unlock() + return + } + c.flush(0) + c.mu.Unlock() + } + }() + } + + return c, nil +} + +func (c *conn) metric(prefix, bucket string, n interface{}, typ string, rate float32, tags string) { + c.mu.Lock() + l := len(c.buf) + c.appendBucket(prefix, bucket, tags) + c.appendNumber(n) + c.appendType(typ) + c.appendRate(rate) + c.closeMetric(tags) + c.flushIfBufferFull(l) + c.mu.Unlock() +} + +func (c *conn) gauge(prefix, bucket string, value interface{}, tags string) { + c.mu.Lock() + l := len(c.buf) + // To set a gauge to a negative value we must first set it to 0. + // https://github.com/etsy/statsd/blob/master/docs/metric_types.md#gauges + if isNegative(value) { + c.appendBucket(prefix, bucket, tags) + c.appendGauge(0, tags) + } + c.appendBucket(prefix, bucket, tags) + c.appendGauge(value, tags) + c.flushIfBufferFull(l) + c.mu.Unlock() +} + +func (c *conn) appendGauge(value interface{}, tags string) { + c.appendNumber(value) + c.appendType("g") + c.closeMetric(tags) +} + +func (c *conn) unique(prefix, bucket string, value string, tags string) { + c.mu.Lock() + l := len(c.buf) + c.appendBucket(prefix, bucket, tags) + c.appendString(value) + c.appendType("s") + c.closeMetric(tags) + c.flushIfBufferFull(l) + c.mu.Unlock() +} + +func (c *conn) appendByte(b byte) { + c.buf = append(c.buf, b) +} + +func (c *conn) appendString(s string) { + c.buf = append(c.buf, s...) +} + +func (c *conn) appendNumber(v interface{}) { + switch n := v.(type) { + case int: + c.buf = strconv.AppendInt(c.buf, int64(n), 10) + case uint: + c.buf = strconv.AppendUint(c.buf, uint64(n), 10) + case int64: + c.buf = strconv.AppendInt(c.buf, n, 10) + case uint64: + c.buf = strconv.AppendUint(c.buf, n, 10) + case int32: + c.buf = strconv.AppendInt(c.buf, int64(n), 10) + case uint32: + c.buf = strconv.AppendUint(c.buf, uint64(n), 10) + case int16: + c.buf = strconv.AppendInt(c.buf, int64(n), 10) + case uint16: + c.buf = strconv.AppendUint(c.buf, uint64(n), 10) + case int8: + c.buf = strconv.AppendInt(c.buf, int64(n), 10) + case uint8: + c.buf = strconv.AppendUint(c.buf, uint64(n), 10) + case float64: + c.buf = strconv.AppendFloat(c.buf, n, 'f', -1, 64) + case float32: + c.buf = strconv.AppendFloat(c.buf, float64(n), 'f', -1, 32) + } +} + +func isNegative(v interface{}) bool { + switch n := v.(type) { + case int: + return n < 0 + case uint: + return n < 0 + case int64: + return n < 0 + case uint64: + return n < 0 + case int32: + return n < 0 + case uint32: + return n < 0 + case int16: + return n < 0 + case uint16: + return n < 0 + case int8: + return n < 0 + case uint8: + return n < 0 + case float64: + return n < 0 + case float32: + return n < 0 + } + return false +} + +func (c *conn) appendBucket(prefix, bucket string, tags string) { + c.appendString(prefix) + c.appendString(bucket) + if c.tagFormat == InfluxDB { + c.appendString(tags) + } + c.appendByte(':') +} + +func (c *conn) appendType(t string) { + c.appendByte('|') + c.appendString(t) +} + +func (c *conn) appendRate(rate float32) { + if rate == 1 { + return + } + if c.rateCache == nil { + c.rateCache = make(map[float32]string) + } + + c.appendString("|@") + if s, ok := c.rateCache[rate]; ok { + c.appendString(s) + } else { + s = strconv.FormatFloat(float64(rate), 'f', -1, 32) + c.rateCache[rate] = s + c.appendString(s) + } +} + +func (c *conn) closeMetric(tags string) { + if c.tagFormat == Datadog { + c.appendString(tags) + } + c.appendByte('\n') +} + +func (c *conn) flushIfBufferFull(lastSafeLen int) { + if len(c.buf) > c.maxPacketSize { + c.flush(lastSafeLen) + } +} + +// flush flushes the first n bytes of the buffer. +// If n is 0, the whole buffer is flushed. +func (c *conn) flush(n int) { + if len(c.buf) == 0 { + return + } + if n == 0 { + n = len(c.buf) + } + + // Trim the last \n, StatsD does not like it. + _, err := c.w.Write(c.buf[:n-1]) + c.handleError(err) + if n < len(c.buf) { + copy(c.buf, c.buf[n:]) + } + c.buf = c.buf[:len(c.buf)-n] +} + +func (c *conn) handleError(err error) { + if err != nil && c.errorHandler != nil { + c.errorHandler(err) + } +} + +// Stubbed out for testing. +var ( + dialTimeout = net.DialTimeout + now = time.Now + randFloat = rand.Float32 +) diff --git a/vendor/github.com/alexcesaro/statsd/doc.go b/vendor/github.com/alexcesaro/statsd/doc.go new file mode 100644 index 000000000..bb7b986e3 --- /dev/null +++ b/vendor/github.com/alexcesaro/statsd/doc.go @@ -0,0 +1,29 @@ +/* +Package statsd is a simple and efficient StatsD client. + + +Options + +Use options to configure the Client: target host/port, sampling rate, tags, etc. + +Whenever you want to use different options (e.g. other tags, different sampling +rate), you should use the Clone() method of the Client. + +Because when cloning a Client, the same connection is reused so this is way +cheaper and more efficient than creating another Client using New(). + + +Internals + +Client's methods buffer metrics. The buffer is flushed when either: + - the background goroutine flushes the buffer (every 100ms by default) + - the buffer is full (1440 bytes by default so that IP packets are not + fragmented) + +The background goroutine can be disabled using the FlushPeriod(0) option. + +Buffering can be disabled using the MaxPacketSize(0) option. + +StatsD homepage: https://github.com/etsy/statsd +*/ +package statsd diff --git a/vendor/github.com/alexcesaro/statsd/options.go b/vendor/github.com/alexcesaro/statsd/options.go new file mode 100644 index 000000000..ef95bb8c3 --- /dev/null +++ b/vendor/github.com/alexcesaro/statsd/options.go @@ -0,0 +1,250 @@ +package statsd + +import ( + "bytes" + "strings" + "time" +) + +type config struct { + Conn connConfig + Client clientConfig +} + +type clientConfig struct { + Muted bool + Rate float32 + Prefix string + Tags []tag +} + +type connConfig struct { + Addr string + ErrorHandler func(error) + FlushPeriod time.Duration + MaxPacketSize int + Network string + TagFormat TagFormat +} + +// An Option represents an option for a Client. It must be used as an +// argument to New() or Client.Clone(). +type Option func(*config) + +// Address sets the address of the StatsD daemon. +// +// By default, ":8125" is used. This option is ignored in Client.Clone(). +func Address(addr string) Option { + return Option(func(c *config) { + c.Conn.Addr = addr + }) +} + +// ErrorHandler sets the function called when an error happens when sending +// metrics (e.g. the StatsD daemon is not listening anymore). +// +// By default, these errors are ignored. This option is ignored in +// Client.Clone(). +func ErrorHandler(h func(error)) Option { + return Option(func(c *config) { + c.Conn.ErrorHandler = h + }) +} + +// FlushPeriod sets how often the Client's buffer is flushed. If p is 0, the +// goroutine that periodically flush the buffer is not lauched and the buffer +// is only flushed when it is full. +// +// By default, the flush period is 100 ms. This option is ignored in +// Client.Clone(). +func FlushPeriod(p time.Duration) Option { + return Option(func(c *config) { + c.Conn.FlushPeriod = p + }) +} + +// MaxPacketSize sets the maximum packet size in bytes sent by the Client. +// +// By default, it is 1440 to avoid IP fragmentation. This option is ignored in +// Client.Clone(). +func MaxPacketSize(n int) Option { + return Option(func(c *config) { + c.Conn.MaxPacketSize = n + }) +} + +// Network sets the network (udp, tcp, etc) used by the client. See the +// net.Dial documentation (https://golang.org/pkg/net/#Dial) for the available +// network options. +// +// By default, network is udp. This option is ignored in Client.Clone(). +func Network(network string) Option { + return Option(func(c *config) { + c.Conn.Network = network + }) +} + +// Mute sets whether the Client is muted. All methods of a muted Client do +// nothing and return immedialtly. +// +// This option can be used in Client.Clone() only if the parent Client is not +// muted. The clones of a muted Client are always muted. +func Mute(b bool) Option { + return Option(func(c *config) { + c.Client.Muted = b + }) +} + +// SampleRate sets the sample rate of the Client. It allows sending the metrics +// less often which can be useful for performance intensive code paths. +func SampleRate(rate float32) Option { + return Option(func(c *config) { + c.Client.Rate = rate + }) +} + +// Prefix appends the prefix that will be used in every bucket name. +// +// Note that when used in cloned, the prefix of the parent Client is not +// replaced but is prepended to the given prefix. +func Prefix(p string) Option { + return Option(func(c *config) { + c.Client.Prefix += strings.TrimSuffix(p, ".") + "." + }) +} + +// TagFormat represents the format of tags sent by a Client. +type TagFormat uint8 + +// TagsFormat sets the format of tags. +func TagsFormat(tf TagFormat) Option { + return Option(func(c *config) { + c.Conn.TagFormat = tf + }) +} + +// Tags appends the given tags to the tags sent with every metrics. If a tag +// already exists, it is replaced. +// +// The tags must be set as key-value pairs. If the number of tags is not even, +// Tags panics. +// +// If the format of tags have not been set using the TagsFormat option, the tags +// will be ignored. +func Tags(tags ...string) Option { + if len(tags)%2 != 0 { + panic("statsd: Tags only accepts an even number of arguments") + } + + return Option(func(c *config) { + if len(tags) == 0 { + return + } + + newTags := make([]tag, len(tags)/2) + for i := 0; i < len(tags)/2; i++ { + newTags[i] = tag{K: tags[2*i], V: tags[2*i+1]} + } + + for _, newTag := range newTags { + exists := false + for _, oldTag := range c.Client.Tags { + if newTag.K == oldTag.K { + exists = true + oldTag.V = newTag.V + } + } + if !exists { + c.Client.Tags = append(c.Client.Tags, tag{ + K: newTag.K, + V: newTag.V, + }) + } + } + }) +} + +type tag struct { + K, V string +} + +func joinTags(tf TagFormat, tags []tag) string { + if len(tags) == 0 || tf == 0 { + return "" + } + join := joinFuncs[tf] + return join(tags) +} + +func splitTags(tf TagFormat, tags string) []tag { + if len(tags) == 0 || tf == 0 { + return nil + } + split := splitFuncs[tf] + return split(tags) +} + +const ( + // InfluxDB tag format. + // See https://influxdb.com/blog/2015/11/03/getting_started_with_influx_statsd.html + InfluxDB TagFormat = iota + 1 + // Datadog tag format. + // See http://docs.datadoghq.com/guides/metrics/#tags + Datadog +) + +var ( + joinFuncs = map[TagFormat]func([]tag) string{ + // InfluxDB tag format: ,tag1=payroll,region=us-west + // https://influxdb.com/blog/2015/11/03/getting_started_with_influx_statsd.html + InfluxDB: func(tags []tag) string { + var buf bytes.Buffer + for _, tag := range tags { + _ = buf.WriteByte(',') + _, _ = buf.WriteString(tag.K) + _ = buf.WriteByte('=') + _, _ = buf.WriteString(tag.V) + } + return buf.String() + }, + // Datadog tag format: |#tag1:value1,tag2:value2 + // http://docs.datadoghq.com/guides/dogstatsd/#datagram-format + Datadog: func(tags []tag) string { + buf := bytes.NewBufferString("|#") + first := true + for _, tag := range tags { + if first { + first = false + } else { + _ = buf.WriteByte(',') + } + _, _ = buf.WriteString(tag.K) + _ = buf.WriteByte(':') + _, _ = buf.WriteString(tag.V) + } + return buf.String() + }, + } + splitFuncs = map[TagFormat]func(string) []tag{ + InfluxDB: func(s string) []tag { + s = s[1:] + pairs := strings.Split(s, ",") + tags := make([]tag, len(pairs)) + for i, pair := range pairs { + kv := strings.Split(pair, "=") + tags[i] = tag{K: kv[0], V: kv[1]} + } + return tags + }, + Datadog: func(s string) []tag { + s = s[2:] + pairs := strings.Split(s, ",") + tags := make([]tag, len(pairs)) + for i, pair := range pairs { + kv := strings.Split(pair, ":") + tags[i] = tag{K: kv[0], V: kv[1]} + } + return tags + }, + } +) diff --git a/vendor/github.com/alexcesaro/statsd/statsd.go b/vendor/github.com/alexcesaro/statsd/statsd.go new file mode 100644 index 000000000..f19204d79 --- /dev/null +++ b/vendor/github.com/alexcesaro/statsd/statsd.go @@ -0,0 +1,169 @@ +package statsd + +import "time" + +// A Client represents a StatsD client. +type Client struct { + conn *conn + muted bool + rate float32 + prefix string + tags string +} + +// New returns a new Client. +func New(opts ...Option) (*Client, error) { + // The default configuration. + conf := &config{ + Client: clientConfig{ + Rate: 1, + }, + Conn: connConfig{ + Addr: ":8125", + FlushPeriod: 100 * time.Millisecond, + // Worst-case scenario: + // Ethernet MTU - IPv6 Header - TCP Header = 1500 - 40 - 20 = 1440 + MaxPacketSize: 1440, + Network: "udp", + }, + } + for _, o := range opts { + o(conf) + } + + conn, err := newConn(conf.Conn, conf.Client.Muted) + c := &Client{ + conn: conn, + muted: conf.Client.Muted, + } + if err != nil { + c.muted = true + return c, err + } + c.rate = conf.Client.Rate + c.prefix = conf.Client.Prefix + c.tags = joinTags(conf.Conn.TagFormat, conf.Client.Tags) + return c, nil +} + +// Clone returns a clone of the Client. The cloned Client inherits its +// configuration from its parent. +// +// All cloned Clients share the same connection, so cloning a Client is a cheap +// operation. +func (c *Client) Clone(opts ...Option) *Client { + tf := c.conn.tagFormat + conf := &config{ + Client: clientConfig{ + Rate: c.rate, + Prefix: c.prefix, + Tags: splitTags(tf, c.tags), + }, + } + for _, o := range opts { + o(conf) + } + + clone := &Client{ + conn: c.conn, + muted: c.muted || conf.Client.Muted, + rate: conf.Client.Rate, + prefix: conf.Client.Prefix, + tags: joinTags(tf, conf.Client.Tags), + } + clone.conn = c.conn + return clone +} + +// Count adds n to bucket. +func (c *Client) Count(bucket string, n interface{}) { + if c.skip() { + return + } + c.conn.metric(c.prefix, bucket, n, "c", c.rate, c.tags) +} + +func (c *Client) skip() bool { + return c.muted || (c.rate != 1 && randFloat() > c.rate) +} + +// Increment increment the given bucket. It is equivalent to Count(bucket, 1). +func (c *Client) Increment(bucket string) { + c.Count(bucket, 1) +} + +// Gauge records an absolute value for the given bucket. +func (c *Client) Gauge(bucket string, value interface{}) { + if c.skip() { + return + } + c.conn.gauge(c.prefix, bucket, value, c.tags) +} + +// Timing sends a timing value to a bucket. +func (c *Client) Timing(bucket string, value interface{}) { + if c.skip() { + return + } + c.conn.metric(c.prefix, bucket, value, "ms", c.rate, c.tags) +} + +// Histogram sends an histogram value to a bucket. +func (c *Client) Histogram(bucket string, value interface{}) { + if c.skip() { + return + } + c.conn.metric(c.prefix, bucket, value, "h", c.rate, c.tags) +} + +// A Timing is an helper object that eases sending timing values. +type Timing struct { + start time.Time + c *Client +} + +// NewTiming creates a new Timing. +func (c *Client) NewTiming() Timing { + return Timing{start: now(), c: c} +} + +// Send sends the time elapsed since the creation of the Timing. +func (t Timing) Send(bucket string) { + t.c.Timing(bucket, int(t.Duration()/time.Millisecond)) +} + +// Duration returns the time elapsed since the creation of the Timing. +func (t Timing) Duration() time.Duration { + return now().Sub(t.start) +} + +// Unique sends the given value to a set bucket. +func (c *Client) Unique(bucket string, value string) { + if c.skip() { + return + } + c.conn.unique(c.prefix, bucket, value, c.tags) +} + +// Flush flushes the Client's buffer. +func (c *Client) Flush() { + if c.muted { + return + } + c.conn.mu.Lock() + c.conn.flush(0) + c.conn.mu.Unlock() +} + +// Close flushes the Client's buffer and releases the associated ressources. The +// Client and all the cloned Clients must not be used afterward. +func (c *Client) Close() { + if c.muted { + return + } + c.conn.mu.Lock() + c.conn.flush(0) + c.conn.handleError(c.conn.w.Close()) + c.conn.closed = true + c.conn.mu.Unlock() +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 0625b8844..eae907f7f 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2,6 +2,7 @@ "comment": "", "ignore": "test", "package": [ + {"path":"github.com/alexcesaro/statsd","checksumSHA1":"U3+w41n+fYxSL3FsXo0kR7zuGho=","revision":"7fea3f0d2fab1ad973e641e51dba45443a311a90","revisionTime":"2016-03-20T18:21:10Z"}, {"path":"github.com/armon/go-proxyproto","checksumSHA1":"eAall4ACaMG40mzSJ5Oc95GiF1A=","revision":"609d6338d3a76ec26ac3fe7045a164d9a58436e7","revisionTime":"2015-02-06T18:58:55-08:00"}, {"path":"github.com/circonus-labs/circonus-gometrics","checksumSHA1":"ZAdLZ0e/hin4AXxdS9F8y0yi/bg=","revision":"f8c68ed96a065c10344dcaf802f608781fc0a981","revisionTime":"2016-08-30T16:47:25Z"}, {"path":"github.com/circonus-labs/circonus-gometrics/api","checksumSHA1":"2hV0W0wM75u+OJ4kGv0i7B95cmY=","revision":"f8c68ed96a065c10344dcaf802f608781fc0a981","revisionTime":"2016-08-30T16:47:25Z"}, From 20fa95812699926f8b85ae4ff0bac2cf621deb97 Mon Sep 17 00:00:00 2001 From: Frank Schroeder Date: Sat, 24 Mar 2018 15:23:45 +0100 Subject: [PATCH 05/30] First stab at integrating raw statsd from #335 --- main.go | 9 +++++ metrics4/statsdraw/statsd.go | 73 ++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 metrics4/statsdraw/statsd.go diff --git a/main.go b/main.go index b57b187ef..e2a7d172e 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ import ( "github.com/fabiolb/fabio/metrics4" "github.com/fabiolb/fabio/metrics4/flat" "github.com/fabiolb/fabio/metrics4/label" + "github.com/fabiolb/fabio/metrics4/statsdraw" "github.com/fabiolb/fabio/noroute" "github.com/fabiolb/fabio/proxy" "github.com/fabiolb/fabio/proxy/tcp" @@ -317,6 +318,14 @@ func initMetrics(cfg *config.Config) metrics4.Provider { p = append(p, &flat.Provider{}) case "label": p = append(p, &label.Provider{}) + case "statsd_raw": + // prefix := cfg.Metrics.Prefix // prefix is a template and needs to be expanded + prefix := "" + pp, err := statsdraw.NewProvider(prefix, cfg.Metrics.StatsDAddr, cfg.Metrics.Interval) + if err != nil { + exit.Fatalf("[FATAL] Cannot initialize statsd metrics: %s", err) + } + p = append(p, pp) default: log.Printf("[WARN] Skipping unknown metrics provider %q", x) continue diff --git a/metrics4/statsdraw/statsd.go b/metrics4/statsdraw/statsd.go new file mode 100644 index 000000000..d67906578 --- /dev/null +++ b/metrics4/statsdraw/statsd.go @@ -0,0 +1,73 @@ +package statsdraw + +import ( + "time" + + "github.com/alexcesaro/statsd" + "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/names" +) + +type Provider struct { + c *statsd.Client +} + +func NewProvider(prefix, addr string, interval time.Duration) (*Provider, error) { + opts := []statsd.Option{ + statsd.Address(addr), + statsd.FlushPeriod(interval), + } + if prefix != "" { + opts = append(opts, statsd.Prefix(prefix)) + } + + c, err := statsd.New(opts...) + if err != nil { + return nil, err + } + return &Provider{c}, nil +} + +func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { + return &Counter{c: p.c, name: name, labels: labels} +} + +func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { + return &Gauge{c: p.c, name: name, labels: labels} +} + +func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { + return &Timer{c: p.c, name: name, labels: labels} +} + +func (p *Provider) Unregister(interface{}) {} + +type Counter struct { + c *statsd.Client + name string + labels []string +} + +func (v *Counter) Count(n int) { + v.c.Count(names.Flatten(v.name, v.labels, names.DotSeparator), n) +} + +type Gauge struct { + c *statsd.Client + name string + labels []string +} + +func (v *Gauge) Update(n int) { + v.c.Gauge(names.Flatten(v.name, v.labels, names.DotSeparator), n) +} + +type Timer struct { + c *statsd.Client + name string + labels []string +} + +func (v *Timer) Update(d time.Duration) { + v.c.Timing(names.Flatten(v.name, v.labels, names.DotSeparator), d) +} From f394debf4744d540b8de13abd21473ec3399a492 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Wed, 12 Dec 2018 16:57:22 +0300 Subject: [PATCH 06/30] Added support of Prometheus.Counter with go-kit/metrics --- admin/server.go | 12 +++ config/config.go | 19 +++-- config/load.go | 1 + main.go | 61 ++++++++------ metrics4/flat/metrics.go | 108 +++++++++++++----------- metrics4/label/metrics.go | 104 ++++++++++++----------- metrics4/metrics.go | 131 ++++++++++++++++------------- metrics4/prometheus/metrics.go | 30 +++++++ metrics4/statsdraw/statsd.go | 146 +++++++++++++++++---------------- proxy/http_proxy.go | 32 ++++---- proxy/http_raw_handler.go | 17 ++-- proxy/tcp/copy_buffer.go | 2 +- proxy/tcp/sni_proxy.go | 20 ++--- proxy/tcp/tcp_proxy.go | 6 +- route/target.go | 3 +- 15 files changed, 388 insertions(+), 304 deletions(-) create mode 100644 metrics4/prometheus/metrics.go diff --git a/admin/server.go b/admin/server.go index 03a4bca01..d7d75f276 100644 --- a/admin/server.go +++ b/admin/server.go @@ -9,7 +9,9 @@ import ( "github.com/fabiolb/fabio/admin/api" "github.com/fabiolb/fabio/admin/ui" "github.com/fabiolb/fabio/config" + "github.com/fabiolb/fabio/metrics4" "github.com/fabiolb/fabio/proxy" + "github.com/prometheus/client_golang/prometheus/promhttp" ) // Server provides the HTTP server for the admin UI and API. @@ -20,6 +22,7 @@ type Server struct { Version string Commands string Cfg *config.Config + Metrics *metrics4.Provider } // ListenAndServe starts the admin server. @@ -64,12 +67,21 @@ func (s *Server) handler() http.Handler { mux.Handle("/api/routes", &api.RoutesHandler{}) mux.Handle("/api/version", &api.VersionHandler{Version: s.Version}) mux.Handle("/routes", &ui.RoutesHandler{Color: s.Color, Title: s.Title, Version: s.Version}) + + initMetricsHandlers(mux, s) + mux.HandleFunc("/logo.svg", ui.HandleLogo) mux.HandleFunc("/health", handleHealth) mux.Handle("/", http.RedirectHandler("/routes", http.StatusSeeOther)) return mux } +func initMetricsHandlers(mux *http.ServeMux, s *Server) { + if strings.Contains(s.Cfg.Metrics.Target, "prometheus") && s.Cfg.Metrics.PrometheusEndpoint != "" { + mux.HandleFunc(s.Cfg.Metrics.PrometheusEndpoint, promhttp.Handler().ServeHTTP) + } +} + func handleHealth(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "OK") } diff --git a/config/config.go b/config/config.go index f8ad7e4c2..3eb5839d7 100644 --- a/config/config.go +++ b/config/config.go @@ -95,15 +95,16 @@ type Log struct { } type Metrics struct { - Target string - Prefix string - Names string - Interval time.Duration - Timeout time.Duration - Retry time.Duration - GraphiteAddr string - StatsDAddr string - Circonus Circonus + Target string + Prefix string + Names string + Interval time.Duration + Timeout time.Duration + Retry time.Duration + PrometheusEndpoint string + GraphiteAddr string + StatsDAddr string + Circonus Circonus } type Registry struct { diff --git a/config/load.go b/config/load.go index d75188471..7be6b0c01 100644 --- a/config/load.go +++ b/config/load.go @@ -149,6 +149,7 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c f.DurationVar(&cfg.Metrics.Interval, "metrics.interval", defaultConfig.Metrics.Interval, "metrics reporting interval") f.DurationVar(&cfg.Metrics.Timeout, "metrics.timeout", defaultConfig.Metrics.Timeout, "timeout for metrics to become available") f.DurationVar(&cfg.Metrics.Retry, "metrics.retry", defaultConfig.Metrics.Retry, "retry interval during startup") + f.StringVar(&cfg.Metrics.PrometheusEndpoint, "metrics.prometheus.endpoint", defaultConfig.Metrics.PrometheusEndpoint, "Metrics endpoint for Prometheus") f.StringVar(&cfg.Metrics.GraphiteAddr, "metrics.graphite.addr", defaultConfig.Metrics.GraphiteAddr, "graphite server address") f.StringVar(&cfg.Metrics.StatsDAddr, "metrics.statsd.addr", defaultConfig.Metrics.StatsDAddr, "statsd server address") f.StringVar(&cfg.Metrics.Circonus.APIKey, "metrics.circonus.apikey", defaultConfig.Metrics.Circonus.APIKey, "Circonus API token key") diff --git a/main.go b/main.go index e2a7d172e..607101b8a 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/json" "fmt" + "github.com/fabiolb/fabio/metrics4/prometheus" "io" "log" "net" @@ -23,9 +24,6 @@ import ( "github.com/fabiolb/fabio/exit" "github.com/fabiolb/fabio/logger" "github.com/fabiolb/fabio/metrics4" - "github.com/fabiolb/fabio/metrics4/flat" - "github.com/fabiolb/fabio/metrics4/label" - "github.com/fabiolb/fabio/metrics4/statsdraw" "github.com/fabiolb/fabio/noroute" "github.com/fabiolb/fabio/proxy" "github.com/fabiolb/fabio/proxy/tcp" @@ -36,6 +34,8 @@ import ( "github.com/fabiolb/fabio/route" "github.com/pkg/profile" dmp "github.com/sergi/go-diff/diffmatchpatch" + + "github.com/go-kit/kit/metrics" ) // version contains the version number @@ -45,7 +45,7 @@ import ( // // It is also set by the linker when fabio // is built via the Makefile or the build/docker.sh -// script to ensure the correct version nubmer +// script to ensure the correct version number var version = "1.5.8" var shuttingDown int32 @@ -76,7 +76,7 @@ func main() { // warn once so that it is at the beginning of the log // this will also start the reminder go routine if necessary. - WarnIfRunAsRoot(cfg.Insecure) + //WarnIfRunAsRoot(cfg.Insecure) // setup profiling if enabled var prof interface { @@ -117,9 +117,14 @@ func main() { }) metrics := initMetrics(cfg) + + // TODO(max): Remove + counter := metrics.NewCounter("test") + counter.Add(123) + initRuntime(cfg) initBackend(cfg) - startAdmin(cfg) + startAdmin(cfg, metrics) go watchNoRouteHTML(cfg) @@ -132,7 +137,7 @@ func main() { startServers(cfg, metrics) // warn again so that it is visible in the terminal - WarnIfRunAsRoot(cfg.Insecure) + //WarnIfRunAsRoot(cfg.Insecure) exit.Wait() log.Print("[INFO] Down") @@ -188,25 +193,25 @@ func newHTTPProxy(cfg *config.Config, stats metrics4.Provider) *proxy.HTTPProxy Lookup: func(r *http.Request) *route.Target { t := route.GetTable().Lookup(r, r.Header.Get("trace"), pick, match) if t == nil { - notFound.Count(1) + notFound.Add(1) log.Print("[WARN] No route for ", r.Host, r.URL) } return t }, - Requests: stats.NewTimer("requests"), + //Requests: stats.NewTimer("requests"), Noroute: stats.NewCounter("notfound"), - WSConn: stats.NewGauge("ws.conn"), + //WSConn: stats.NewGauge("ws.conn"), Metrics: stats, Logger: l, } } -func lookupHostFn(cfg *config.Config, notFound metrics4.Counter) func(string) *route.Target { +func lookupHostFn(cfg *config.Config, notFound metrics.Counter) func(string) *route.Target { pick := route.Picker[cfg.Proxy.Strategy] return func(host string) *route.Target { t := route.GetTable().LookupHost(host, pick) if t == nil { - notFound.Count(1) + notFound.Add(1) log.Print("[WARN] No route for ", host) } return t @@ -228,7 +233,7 @@ func makeTLSConfig(l config.Listen) (*tls.Config, error) { return tlscfg, nil } -func startAdmin(cfg *config.Config) { +func startAdmin(cfg *config.Config, stats metrics4.Provider) { log.Printf("[INFO] Admin server access mode %q", cfg.UI.Access) log.Printf("[INFO] Admin server listening on %q", cfg.UI.Listen.Addr) go func() { @@ -270,7 +275,7 @@ func startServers(cfg *config.Config, stats metrics4.Provider) { go func() { h := newHTTPProxy(cfg, stats) // reset the ws.conn gauge - h.WSConn.Update(0) + //h.WSConn.Update(0) if err := proxy.ListenAndServeHTTP(l, h, tlscfg); err != nil { exit.Fatal("[FATAL] ", err) } @@ -314,18 +319,20 @@ func initMetrics(cfg *config.Config) metrics4.Provider { for _, x := range strings.Split(cfg.Metrics.Target, ",") { x = strings.TrimSpace(x) switch x { - case "flat": - p = append(p, &flat.Provider{}) - case "label": - p = append(p, &label.Provider{}) - case "statsd_raw": - // prefix := cfg.Metrics.Prefix // prefix is a template and needs to be expanded - prefix := "" - pp, err := statsdraw.NewProvider(prefix, cfg.Metrics.StatsDAddr, cfg.Metrics.Interval) - if err != nil { - exit.Fatalf("[FATAL] Cannot initialize statsd metrics: %s", err) - } - p = append(p, pp) + case "prometheus": + p = append(p, prometheus.NewProvider()) + //case "flat": + // p = append(p, &flat.Provider{}) + //case "label": + // p = append(p, &label.Provider{}) + //case "statsd_raw": + // // prefix := cfg.Metrics.Prefix // prefix is a template and needs to be expanded + // prefix := "" + // pp, err := statsdraw.NewProvider(prefix, cfg.Metrics.StatsDAddr, cfg.Metrics.Interval) + // if err != nil { + // exit.Fatalf("[FATAL] Cannot initialize statsd metrics: %s", err) + // } + // p = append(p, pp) default: log.Printf("[WARN] Skipping unknown metrics provider %q", x) continue @@ -454,7 +461,7 @@ func unregisterMetrics(p metrics4.Provider, oldTable, newTable route.Table) { for n := range oldNames { if !newNames[n] { log.Printf("[INFO] Unregistering metric %s", n) - p.Unregister(n) + //p.Unregister(n) } } } diff --git a/metrics4/flat/metrics.go b/metrics4/flat/metrics.go index 5abdaf71e..cc8f0e0fa 100644 --- a/metrics4/flat/metrics.go +++ b/metrics4/flat/metrics.go @@ -1,49 +1,63 @@ package flat -import ( - "fmt" - "time" - - "github.com/fabiolb/fabio/metrics4" - "github.com/fabiolb/fabio/metrics4/names" -) - -type Provider struct{} - -func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { - return &Counter{Name: names.Flatten(name, labels, names.DotSeparator)} -} - -func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { - return &Gauge{Name: names.Flatten(name, labels, names.DotSeparator)} -} - -func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { - return &Timer{Name: names.Flatten(name, labels, names.DotSeparator)} -} - -func (p *Provider) Unregister(interface{}) {} - -type Counter struct { - Name string -} - -func (c *Counter) Count(n int) { - fmt.Printf("%s:%d|c\n", c.Name, n) -} - -type Gauge struct { - Name string -} - -func (g *Gauge) Update(n int) { - fmt.Printf("%s:%d|g\n", g.Name, n) -} - -type Timer struct { - Name string -} - -func (t *Timer) Update(d time.Duration) { - fmt.Printf("%s:%d|ms\n", t.Name, d/time.Millisecond) -} +//import ( +// "fmt" +// "time" +// +// "github.com/fabiolb/fabio/metrics4" +// "github.com/fabiolb/fabio/metrics4/names" +//) +// +//type Provider struct{ +// Metrics []*metrics4.Metric +//} +// +//func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { +// return &Counter{Name: names.Flatten(name, labels, names.DotSeparator)} +//} +// +//func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { +// return &Gauge{Name: names.Flatten(name, labels, names.DotSeparator)} +//} +// +//func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { +// timer := Timer{Name: names.Flatten(name, labels, names.DotSeparator)} +// +// p.Metrics = append(p.Metrics, &timer) +// +// return &timer +//} +// +//func (p *Provider) GetMetrics() []*metrics4.Metric { +// return make([]*metrics4.Metric, 0) +//} +// +//func (p *Provider) Unregister(interface{}) {} +// +//type Counter struct { +// Name string +//} +// +//func (c *Counter) Count(n int) { +// fmt.Printf("%s:%d|c\n", c.Name, n) +//} +// +//type Gauge struct { +// Name string +//} +// +//func (g *Gauge) Update(n int) { +// fmt.Printf("%s:%d|g\n", g.Name, n) +//} +// +//type Timer struct { +// Name string +//} +// +//func (t *Timer) Update(d time.Duration) { +// fmt.Printf("%s:%d|ms\n", t.Name, d/time.Millisecond) +//} +// +//func (t *Timer) String() string { +// return fmt.Sprintf("%s:something|ms\n", t.Name) +//} diff --git a/metrics4/label/metrics.go b/metrics4/label/metrics.go index 4b18356e0..fd30bfcc0 100644 --- a/metrics4/label/metrics.go +++ b/metrics4/label/metrics.go @@ -1,52 +1,56 @@ package label -import ( - "fmt" - "time" - - "github.com/fabiolb/fabio/metrics4" - "github.com/fabiolb/fabio/metrics4/names" -) - -type Provider struct{} - -func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { - return &Counter{Name: name, Labels: labels} -} - -func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { - return &Gauge{Name: name, Labels: labels} -} - -func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { - return &Timer{Name: name, Labels: labels} -} - -func (p *Provider) Unregister(interface{}) {} - -type Counter struct { - Name string - Labels []string -} - -func (c *Counter) Count(n int) { - fmt.Printf("%s:%d|c%s\n", c.Name, n, names.Labels(c.Labels, "|#", ":", ",")) -} - -type Gauge struct { - Name string - Labels []string -} - -func (g *Gauge) Update(n int) { - fmt.Printf("%s:%d|g%s\n", g.Name, n, names.Labels(g.Labels, "|#", ":", ",")) -} - -type Timer struct { - Name string - Labels []string -} - -func (t *Timer) Update(d time.Duration) { - fmt.Printf("%s:%d|ns%s\n", t.Name, d.Nanoseconds(), names.Labels(t.Labels, "|#", ":", ",")) -} +//import ( +// "fmt" +// "time" +// +// "github.com/fabiolb/fabio/metrics4" +// "github.com/fabiolb/fabio/metrics4/names" +//) +// +//type Provider struct{} +// +//func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { +// return &Counter{Name: name, Labels: labels} +//} +// +//func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { +// return &Gauge{Name: name, Labels: labels} +//} +// +//func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { +// return &Timer{Name: name, Labels: labels} +//} +// +//func (p *Provider) GetMetrics() []*metrics4.Metric { +// return make([]*metrics4.Metric, 0) +//} +// +//func (p *Provider) Unregister(interface{}) {} +// +//type Counter struct { +// Name string +// Labels []string +//} +// +//func (c *Counter) Count(n int) { +// fmt.Printf("%s:%d|c%s\n", c.Name, n, names.Labels(c.Labels, "|#", ":", ",")) +//} +// +//type Gauge struct { +// Name string +// Labels []string +//} +// +//func (g *Gauge) Update(n int) { +// fmt.Printf("%s:%d|g%s\n", g.Name, n, names.Labels(g.Labels, "|#", ":", ",")) +//} +// +//type Timer struct { +// Name string +// Labels []string +//} +// +//func (t *Timer) Update(d time.Duration) { +// fmt.Printf("%s:%d|ns%s\n", t.Name, d.Nanoseconds(), names.Labels(t.Labels, "|#", ":", ",")) +//} diff --git a/metrics4/metrics.go b/metrics4/metrics.go index bfa79caa9..502fb9d39 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -1,25 +1,25 @@ package metrics4 -import ( - "time" -) +import "github.com/go-kit/kit/metrics" + +const FABIO_NAMESPACE = "fabio" // Provider is an abstraction of a metrics backend. type Provider interface { // NewCounter creates a new counter object. - NewCounter(name string, labels ...string) Counter + NewCounter(name string) Counter // NewGauge creates a new gauge object. - NewGauge(name string, labels ...string) Gauge + //NewGauge(name string, labels ...string) Gauge // NewTimer creates a new timer object. - NewTimer(name string, labels ...string) Timer + //NewTimer(name string, labels ...string) Timer // Unregister removes a previously registered // name or metric. Required for go-metrics and // service pruning. This signature is probably not // correct. - Unregister(v interface{}) + //Unregister(v interface{}) } // MultiProvider wraps zero or more providers. @@ -33,85 +33,100 @@ func NewMultiProvider(p []Provider) *MultiProvider { // NewCounter creates a MultiCounter with counter objects for all registered // providers. -func (mp *MultiProvider) NewCounter(name string, labels ...string) Counter { - var c []Counter +func (mp *MultiProvider) NewCounter(name string) Counter { + var c []metrics.Counter for _, p := range mp.p { - c = append(c, p.NewCounter(name, labels...)) + c = append(c, p.NewCounter(name)) } return &MultiCounter{c} } // NewGauge creates a MultiGauge with gauge objects for all registered // providers. -func (mp *MultiProvider) NewGauge(name string, labels ...string) Gauge { - var v []Gauge - for _, p := range mp.p { - v = append(v, p.NewGauge(name, labels...)) - } - return &MultiGauge{v} -} +//func (mp *MultiProvider) NewGauge(name string, labels ...string) Gauge { +// var v []Gauge +// for _, p := range mp.p { +// v = append(v, p.NewGauge(name, labels...)) +// } +// return &MultiGauge{v} +//} // NewTimer creates a MultiTimer with timer objects for all registered // providers. -func (mp *MultiProvider) NewTimer(name string, labels ...string) Timer { - var t []Timer - for _, p := range mp.p { - t = append(t, p.NewTimer(name, labels...)) - } - return &MultiTimer{t} -} +//func (mp *MultiProvider) NewTimer(name string, labels ...string) Timer { +// var t []Timer +// for _, p := range mp.p { +// t = append(t, p.NewTimer(name, labels...)) +// } +// return &MultiTimer{t} +//} // Unregister removes the metric object from all registered providers. -func (mp *MultiProvider) Unregister(v interface{}) { - for _, p := range mp.p { - p.Unregister(v) - } -} +//func (mp *MultiProvider) Unregister(v interface{}) { +// for _, p := range mp.p { +// p.Unregister(v) +// } +//} // Count measures a number. -type Counter interface { - Count(int) -} +//type Counter interface { +// Count(int) +//} // MultiCounter wraps zero or more counters. type MultiCounter struct { - c []Counter + c []metrics.Counter } -func (mc *MultiCounter) Count(n int) { +func (mc *MultiCounter) Add(delta float64) { for _, c := range mc.c { - c.Count(n) + c.Add(delta) } } -// Gauge measures a value. -type Gauge interface { - Update(int) +func (mc *MultiCounter) With(labelValues... string) metrics.Counter { + for _, c := range mc.c { + c.With(labelValues...) + } + return mc } +// Gauge measures a value. +//type Gauge interface { +// Update(int) +//} + // MultiGauge wraps zero or more gauges. -type MultiGauge struct { - v []Gauge -} +//type MultiGauge struct { +// v []Gauge +//} -func (m *MultiGauge) Update(n int) { - for _, v := range m.v { - v.Update(n) - } -} +//func (m *MultiGauge) Update(n int) { +// for _, v := range m.v { +// v.Update(n) +// } +//} // Timer measures the time of an event. -type Timer interface { - Update(time.Duration) -} +//type Timer interface { +// Update(time.Duration) +//} // MultTimer wraps zero or more timers. -type MultiTimer struct { - t []Timer -} - -func (mt *MultiTimer) Update(d time.Duration) { - for _, t := range mt.t { - t.Update(d) - } -} +//type MultiTimer struct { +// t []Timer +//} + +//func (mt *MultiTimer) Update(d time.Duration) { +// for _, t := range mt.t { +// t.Update(d) +// } +//} + +//type Metric interface { +// String() string +//} +// +//type MetricsContainer []*Metric + +type Counter metrics.Counter diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go new file mode 100644 index 000000000..afcb6cf8e --- /dev/null +++ b/metrics4/prometheus/metrics.go @@ -0,0 +1,30 @@ +package prometheus + +import ( + "github.com/fabiolb/fabio/metrics4" + + "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +type Provider struct { + counters map[string]metrics4.Counter +} + +func NewProvider() *Provider { + return &Provider{make(map[string]metrics4.Counter)} +} + +func (p *Provider) NewCounter(name string) metrics4.Counter { + // TODO(max): Add lock ? + if p.counters[name] == nil { + p.counters[name] = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: metrics4.FABIO_NAMESPACE, + Subsystem: "", + Name: name, + Help: "", + }, []string{}) + } + + return p.counters[name] +} diff --git a/metrics4/statsdraw/statsd.go b/metrics4/statsdraw/statsd.go index d67906578..0ec3a7957 100644 --- a/metrics4/statsdraw/statsd.go +++ b/metrics4/statsdraw/statsd.go @@ -1,73 +1,77 @@ package statsdraw -import ( - "time" - - "github.com/alexcesaro/statsd" - "github.com/fabiolb/fabio/metrics4" - "github.com/fabiolb/fabio/metrics4/names" -) - -type Provider struct { - c *statsd.Client -} - -func NewProvider(prefix, addr string, interval time.Duration) (*Provider, error) { - opts := []statsd.Option{ - statsd.Address(addr), - statsd.FlushPeriod(interval), - } - if prefix != "" { - opts = append(opts, statsd.Prefix(prefix)) - } - - c, err := statsd.New(opts...) - if err != nil { - return nil, err - } - return &Provider{c}, nil -} - -func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { - return &Counter{c: p.c, name: name, labels: labels} -} - -func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { - return &Gauge{c: p.c, name: name, labels: labels} -} - -func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { - return &Timer{c: p.c, name: name, labels: labels} -} - -func (p *Provider) Unregister(interface{}) {} - -type Counter struct { - c *statsd.Client - name string - labels []string -} - -func (v *Counter) Count(n int) { - v.c.Count(names.Flatten(v.name, v.labels, names.DotSeparator), n) -} - -type Gauge struct { - c *statsd.Client - name string - labels []string -} - -func (v *Gauge) Update(n int) { - v.c.Gauge(names.Flatten(v.name, v.labels, names.DotSeparator), n) -} - -type Timer struct { - c *statsd.Client - name string - labels []string -} - -func (v *Timer) Update(d time.Duration) { - v.c.Timing(names.Flatten(v.name, v.labels, names.DotSeparator), d) -} +//import ( +// "time" +// +// "github.com/alexcesaro/statsd" +// "github.com/fabiolb/fabio/metrics4" +// "github.com/fabiolb/fabio/metrics4/names" +//) +// +//type Provider struct { +// c *statsd.Client +//} +// +//func NewProvider(prefix, addr string, interval time.Duration) (*Provider, error) { +// opts := []statsd.Option{ +// statsd.Address(addr), +// statsd.FlushPeriod(interval), +// } +// if prefix != "" { +// opts = append(opts, statsd.Prefix(prefix)) +// } +// +// c, err := statsd.New(opts...) +// if err != nil { +// return nil, err +// } +// return &Provider{c}, nil +//} +// +//func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { +// return &Counter{c: p.c, name: name, labels: labels} +//} +// +//func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { +// return &Gauge{c: p.c, name: name, labels: labels} +//} +// +//func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { +// return &Timer{c: p.c, name: name, labels: labels} +//} +// +//func (p *Provider) GetMetrics() []*metrics4.Metric { +// return make([]*metrics4.Metric, 0) +//} +// +//func (p *Provider) Unregister(interface{}) {} +// +//type Counter struct { +// c *statsd.Client +// name string +// labels []string +//} +// +//func (v *Counter) Count(n int) { +// v.c.Count(names.Flatten(v.name, v.labels, names.DotSeparator), n) +//} +// +//type Gauge struct { +// c *statsd.Client +// name string +// labels []string +//} +// +//func (v *Gauge) Update(n int) { +// v.c.Gauge(names.Flatten(v.name, v.labels, names.DotSeparator), n) +//} +// +//type Timer struct { +// c *statsd.Client +// name string +// labels []string +//} +// +//func (v *Timer) Update(d time.Duration) { +// v.c.Timing(names.Flatten(v.name, v.labels, names.DotSeparator), d) +//} diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index 08756450e..da5a30b48 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -44,14 +44,14 @@ type HTTPProxy struct { Lookup func(*http.Request) *route.Target // Requests is a timer metric which is updated for every request. - Requests metrics4.Timer + //Requests metrics4.Timer // Noroute is a counter metric which is updated for every request // where Lookup() returns nil. Noroute metrics4.Counter // WSConn counts the number of open web socket connections. - WSConn metrics4.Gauge + //WSConn metrics4.Gauge // Metrics is the configured metrics backend provider. Metrics metrics4.Provider @@ -112,10 +112,10 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { if t.RedirectCode != 0 { http.Redirect(w, r, t.RedirectURL.String(), t.RedirectCode) - if t.Timer != nil { - t.Timer.Update(0) - } - metrics.NewCounter("http.status", "code", strconv.Itoa(t.RedirectCode)).Count(1) + //if t.Timer != nil { + // t.Timer.Update(0) + //} + metrics.NewCounter("http.status").With("code", strconv.Itoa(t.RedirectCode)).Add(1) return } @@ -172,7 +172,7 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { return tls.Dial(network, address, tr.(*http.Transport).TLSClientConfig) } } - h = newRawProxy(targetURL.Host, dial, p.WSConn) + h = newRawProxy(targetURL.Host, dial /*, p.WSConn*/) case accept == "text/event-stream": // use the flush interval for SSE (server-sent events) @@ -196,19 +196,19 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { rw := &responseWriter{w: w} h.ServeHTTP(rw, r) end := timeNow() - dur := end.Sub(start) - - if p.Requests != nil { - p.Requests.Update(dur) - } - if t.Timer != nil { - t.Timer.Update(dur) - } + //dur := end.Sub(start) + + //if p.Requests != nil { + // p.Requests.Update(dur) + //} + //if t.Timer != nil { + // t.Timer.Update(dur) + //} if rw.code <= 0 { return } - metrics.NewTimer("http.status", "code", strconv.Itoa(rw.code)).Update(dur) + //metrics.NewTimer("http.status", "code", strconv.Itoa(rw.code)).Update(dur) // write access log if p.Logger != nil { diff --git a/proxy/http_raw_handler.go b/proxy/http_raw_handler.go index 1c4a16045..f195fea65 100644 --- a/proxy/http_raw_handler.go +++ b/proxy/http_raw_handler.go @@ -5,9 +5,6 @@ import ( "log" "net" "net/http" - "sync/atomic" - - "github.com/fabiolb/fabio/metrics4" ) // conns keeps track of the number of open ws connections @@ -18,14 +15,14 @@ type dialFunc func(network, address string) (net.Conn, error) // newRawProxy returns an HTTP handler which forwards data between // an incoming and outgoing TCP connection including the original request. // This handler establishes a new outgoing connection per request. -func newRawProxy(host string, dial dialFunc, conn metrics4.Gauge) http.Handler { +func newRawProxy(host string, dial dialFunc /*, conn metrics4.Gauge*/) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if conn != nil { - conn.Update(int(atomic.AddInt64(&conns, 1))) - defer func() { - conn.Update(int(atomic.AddInt64(&conns, -1))) - }() - } + //if conn != nil { + // conn.Update(int(atomic.AddInt64(&conns, 1))) + // defer func() { + // conn.Update(int(atomic.AddInt64(&conns, -1))) + // }() + //} hj, ok := w.(http.Hijacker) if !ok { diff --git a/proxy/tcp/copy_buffer.go b/proxy/tcp/copy_buffer.go index ce7395b14..cfba3c1ce 100644 --- a/proxy/tcp/copy_buffer.go +++ b/proxy/tcp/copy_buffer.go @@ -16,7 +16,7 @@ func copyBuffer(dst io.Writer, src io.Reader, c metrics4.Counter) (err error) { nw, ew := dst.Write(buf[0:nr]) if nw > 0 { if c != nil { - c.Count(nw) + c.Add(float64(nw)) } } if ew != nil { diff --git a/proxy/tcp/sni_proxy.go b/proxy/tcp/sni_proxy.go index bea6f1fff..20bee7e19 100644 --- a/proxy/tcp/sni_proxy.go +++ b/proxy/tcp/sni_proxy.go @@ -47,7 +47,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { } if p.Conn != nil { - p.Conn.Count(1) + p.Conn.Add(1) } tlsReader := bufio.NewReader(in) @@ -55,7 +55,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Print("[DEBUG] tcp+sni: TLS handshake failed (failed to peek data)") if p.ConnFail != nil { - p.ConnFail.Count(1) + p.ConnFail.Add(1) } return err } @@ -64,7 +64,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Printf("[DEBUG] tcp+sni: TLS handshake failed (%s)", err) if p.ConnFail != nil { - p.ConnFail.Count(1) + p.ConnFail.Add(1) } return err } @@ -74,7 +74,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Printf("[DEBUG] tcp+sni: TLS handshake failed (%s)", err) if p.ConnFail != nil { - p.ConnFail.Count(1) + p.ConnFail.Add(1) } return err } @@ -85,7 +85,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if !ok { log.Print("[DEBUG] tcp+sni: TLS handshake failed (unable to parse client hello)") if p.ConnFail != nil { - p.ConnFail.Count(1) + p.ConnFail.Add(1) } return nil } @@ -93,7 +93,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if host == "" { log.Print("[DEBUG] tcp+sni: server_name missing") if p.ConnFail != nil { - p.ConnFail.Count(1) + p.ConnFail.Add(1) } return nil } @@ -101,7 +101,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { t := p.Lookup(host) if t == nil { if p.Noroute != nil { - p.Noroute.Count(1) + p.Noroute.Add(1) } return nil } @@ -115,7 +115,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Print("[WARN] tcp+sni: cannot connect to upstream ", addr) if p.ConnFail != nil { - p.ConnFail.Count(1) + p.ConnFail.Add(1) } return err } @@ -126,7 +126,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { if err != nil { log.Print("[WARN] tcp+sni: copy client hello failed. ", err) if p.ConnFail != nil { - p.ConnFail.Count(1) + p.ConnFail.Add(1) } return err } @@ -142,7 +142,7 @@ func (p *SNIProxy) ServeTCP(in net.Conn) error { tx := metrics.NewCounter(t.TimerName.String() + ".tx") // we've received the ClientHello already - rx.Count(n) + rx.Add(float64(n)) go cp(in, out, rx) go cp(out, in, tx) diff --git a/proxy/tcp/tcp_proxy.go b/proxy/tcp/tcp_proxy.go index 494679131..785c1bbe8 100644 --- a/proxy/tcp/tcp_proxy.go +++ b/proxy/tcp/tcp_proxy.go @@ -42,7 +42,7 @@ func (p *Proxy) ServeTCP(in net.Conn) error { } if p.Conn != nil { - p.Conn.Count(1) + p.Conn.Add(1) } _, port, _ := net.SplitHostPort(in.LocalAddr().String()) @@ -50,7 +50,7 @@ func (p *Proxy) ServeTCP(in net.Conn) error { t := p.Lookup(port) if t == nil { if p.Noroute != nil { - p.Noroute.Count(1) + p.Noroute.Add(1) } return nil } @@ -64,7 +64,7 @@ func (p *Proxy) ServeTCP(in net.Conn) error { if err != nil { log.Print("[WARN] tcp: cannot connect to upstream ", addr) if p.ConnFail != nil { - p.ConnFail.Count(1) + p.ConnFail.Add(1) } return err } diff --git a/route/target.go b/route/target.go index f23cd2172..16fe75784 100644 --- a/route/target.go +++ b/route/target.go @@ -4,7 +4,6 @@ import ( "net/url" "strings" - "github.com/fabiolb/fabio/metrics4" "github.com/fabiolb/fabio/metrics4/names" ) @@ -51,7 +50,7 @@ type Target struct { Weight float64 // Timer measures throughput and latency of this target - Timer metrics4.Timer + //Timer metrics4.Timer // TimerName is the name of the timer in the metrics registry TimerName names.Service From e21c6ca851148f8824d1fb3172ccb73b9197807b Mon Sep 17 00:00:00 2001 From: maximka777 Date: Wed, 12 Dec 2018 17:35:55 +0300 Subject: [PATCH 07/30] Added Prometheus.Gauge --- main.go | 8 ++-- metrics4/metrics.go | 74 ++++++++++++++++++---------------- metrics4/prometheus/metrics.go | 22 +++++++++- proxy/http_proxy.go | 4 +- proxy/http_raw_handler.go | 16 ++++---- 5 files changed, 74 insertions(+), 50 deletions(-) diff --git a/main.go b/main.go index 607101b8a..a4155808d 100644 --- a/main.go +++ b/main.go @@ -34,8 +34,6 @@ import ( "github.com/fabiolb/fabio/route" "github.com/pkg/profile" dmp "github.com/sergi/go-diff/diffmatchpatch" - - "github.com/go-kit/kit/metrics" ) // version contains the version number @@ -200,13 +198,13 @@ func newHTTPProxy(cfg *config.Config, stats metrics4.Provider) *proxy.HTTPProxy }, //Requests: stats.NewTimer("requests"), Noroute: stats.NewCounter("notfound"), - //WSConn: stats.NewGauge("ws.conn"), + WSConn: stats.NewGauge("wsconn"), Metrics: stats, Logger: l, } } -func lookupHostFn(cfg *config.Config, notFound metrics.Counter) func(string) *route.Target { +func lookupHostFn(cfg *config.Config, notFound metrics4.Counter) func(string) *route.Target { pick := route.Picker[cfg.Proxy.Strategy] return func(host string) *route.Target { t := route.GetTable().LookupHost(host, pick) @@ -275,7 +273,7 @@ func startServers(cfg *config.Config, stats metrics4.Provider) { go func() { h := newHTTPProxy(cfg, stats) // reset the ws.conn gauge - //h.WSConn.Update(0) + h.WSConn.Set(0) if err := proxy.ListenAndServeHTTP(l, h, tlscfg); err != nil { exit.Fatal("[FATAL] ", err) } diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 502fb9d39..20d9ef1d2 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -2,7 +2,13 @@ package metrics4 import "github.com/go-kit/kit/metrics" -const FABIO_NAMESPACE = "fabio" +const FabioNamespace = "fabio" + +type Counter metrics.Counter + +type Gauge metrics.Gauge + +type Timer metrics.Timer // Provider is an abstraction of a metrics backend. type Provider interface { @@ -10,7 +16,7 @@ type Provider interface { NewCounter(name string) Counter // NewGauge creates a new gauge object. - //NewGauge(name string, labels ...string) Gauge + NewGauge(name string) Gauge // NewTimer creates a new timer object. //NewTimer(name string, labels ...string) Timer @@ -34,7 +40,7 @@ func NewMultiProvider(p []Provider) *MultiProvider { // NewCounter creates a MultiCounter with counter objects for all registered // providers. func (mp *MultiProvider) NewCounter(name string) Counter { - var c []metrics.Counter + var c []Counter for _, p := range mp.p { c = append(c, p.NewCounter(name)) } @@ -43,13 +49,13 @@ func (mp *MultiProvider) NewCounter(name string) Counter { // NewGauge creates a MultiGauge with gauge objects for all registered // providers. -//func (mp *MultiProvider) NewGauge(name string, labels ...string) Gauge { -// var v []Gauge -// for _, p := range mp.p { -// v = append(v, p.NewGauge(name, labels...)) -// } -// return &MultiGauge{v} -//} +func (mp *MultiProvider) NewGauge(name string) Gauge { + var g []Gauge + for _, p := range mp.p { + g = append(g, p.NewGauge(name)) + } + return &MultiGauge{g} +} // NewTimer creates a MultiTimer with timer objects for all registered // providers. @@ -75,37 +81,45 @@ func (mp *MultiProvider) NewCounter(name string) Counter { // MultiCounter wraps zero or more counters. type MultiCounter struct { - c []metrics.Counter + counters []Counter } func (mc *MultiCounter) Add(delta float64) { - for _, c := range mc.c { + for _, c := range mc.counters { c.Add(delta) } } func (mc *MultiCounter) With(labelValues... string) metrics.Counter { - for _, c := range mc.c { + for _, c := range mc.counters { c.With(labelValues...) } return mc } -// Gauge measures a value. -//type Gauge interface { -// Update(int) -//} - // MultiGauge wraps zero or more gauges. -//type MultiGauge struct { -// v []Gauge -//} +type MultiGauge struct { + gauges []Gauge +} -//func (m *MultiGauge) Update(n int) { -// for _, v := range m.v { -// v.Update(n) -// } -//} +func (mg *MultiGauge) Add(delta float64) { + for _, g := range mg.gauges { + g.Add(delta) + } +} + +func (mg *MultiGauge) Set(delta float64) { + for _, g := range mg.gauges { + g.Set(delta) + } +} + +func (mg *MultiGauge) With(labelValues... string) metrics.Gauge { + for _, g := range mg.gauges { + g.With(labelValues...) + } + return mg +} // Timer measures the time of an event. //type Timer interface { @@ -122,11 +136,3 @@ func (mc *MultiCounter) With(labelValues... string) metrics.Counter { // t.Update(d) // } //} - -//type Metric interface { -// String() string -//} -// -//type MetricsContainer []*Metric - -type Counter metrics.Counter diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index afcb6cf8e..a8533570d 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -9,17 +9,21 @@ import ( type Provider struct { counters map[string]metrics4.Counter + gauges map[string]metrics4.Gauge } func NewProvider() *Provider { - return &Provider{make(map[string]metrics4.Counter)} + return &Provider{ + make(map[string]metrics4.Counter), + make(map[string]metrics4.Gauge), + } } func (p *Provider) NewCounter(name string) metrics4.Counter { // TODO(max): Add lock ? if p.counters[name] == nil { p.counters[name] = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ - Namespace: metrics4.FABIO_NAMESPACE, + Namespace: metrics4.FabioNamespace, Subsystem: "", Name: name, Help: "", @@ -28,3 +32,17 @@ func (p *Provider) NewCounter(name string) metrics4.Counter { return p.counters[name] } + +func (p *Provider) NewGauge(name string) metrics4.Gauge { + // TODO(max): Add lock ? + if p.gauges[name] == nil { + p.gauges[name] = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: metrics4.FabioNamespace, + Subsystem: "", + Name: name, + Help: "", + }, []string{}) + } + + return p.gauges[name] +} diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index da5a30b48..56a7ff220 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -51,7 +51,7 @@ type HTTPProxy struct { Noroute metrics4.Counter // WSConn counts the number of open web socket connections. - //WSConn metrics4.Gauge + WSConn metrics4.Gauge // Metrics is the configured metrics backend provider. Metrics metrics4.Provider @@ -172,7 +172,7 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { return tls.Dial(network, address, tr.(*http.Transport).TLSClientConfig) } } - h = newRawProxy(targetURL.Host, dial /*, p.WSConn*/) + h = newRawProxy(targetURL.Host, dial, p.WSConn) case accept == "text/event-stream": // use the flush interval for SSE (server-sent events) diff --git a/proxy/http_raw_handler.go b/proxy/http_raw_handler.go index f195fea65..558c411a2 100644 --- a/proxy/http_raw_handler.go +++ b/proxy/http_raw_handler.go @@ -1,10 +1,12 @@ package proxy import ( + "github.com/fabiolb/fabio/metrics4" "io" "log" "net" "net/http" + "sync/atomic" ) // conns keeps track of the number of open ws connections @@ -15,14 +17,14 @@ type dialFunc func(network, address string) (net.Conn, error) // newRawProxy returns an HTTP handler which forwards data between // an incoming and outgoing TCP connection including the original request. // This handler establishes a new outgoing connection per request. -func newRawProxy(host string, dial dialFunc /*, conn metrics4.Gauge*/) http.Handler { +func newRawProxy(host string, dial dialFunc, conn metrics4.Gauge) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - //if conn != nil { - // conn.Update(int(atomic.AddInt64(&conns, 1))) - // defer func() { - // conn.Update(int(atomic.AddInt64(&conns, -1))) - // }() - //} + if conn != nil { + conn.Set(float64(atomic.AddInt64(&conns, 1))) + defer func() { + conn.Set(float64(atomic.AddInt64(&conns, -1))) + }() + } hj, ok := w.(http.Hijacker) if !ok { From ba2dd8443f298e694ec4486fe52a7f70518d5daf Mon Sep 17 00:00:00 2001 From: maximka777 Date: Wed, 12 Dec 2018 18:48:31 +0300 Subject: [PATCH 08/30] Prometheus Histograms and Timers (needs testing) --- main.go | 6 +- metrics4/flat/metrics.go | 4 - metrics4/metrics.go | 130 +++++++++++++++++++++++---------- metrics4/prometheus/metrics.go | 27 ++++++- proxy/http_proxy.go | 20 ++--- route/target.go | 3 +- 6 files changed, 131 insertions(+), 59 deletions(-) diff --git a/main.go b/main.go index a4155808d..765da3646 100644 --- a/main.go +++ b/main.go @@ -116,10 +116,6 @@ func main() { metrics := initMetrics(cfg) - // TODO(max): Remove - counter := metrics.NewCounter("test") - counter.Add(123) - initRuntime(cfg) initBackend(cfg) startAdmin(cfg, metrics) @@ -196,7 +192,7 @@ func newHTTPProxy(cfg *config.Config, stats metrics4.Provider) *proxy.HTTPProxy } return t }, - //Requests: stats.NewTimer("requests"), + Requests: stats.NewTimer("requests"), Noroute: stats.NewCounter("notfound"), WSConn: stats.NewGauge("wsconn"), Metrics: stats, diff --git a/metrics4/flat/metrics.go b/metrics4/flat/metrics.go index cc8f0e0fa..12a003b8f 100644 --- a/metrics4/flat/metrics.go +++ b/metrics4/flat/metrics.go @@ -57,7 +57,3 @@ package flat //func (t *Timer) Update(d time.Duration) { // fmt.Printf("%s:%d|ms\n", t.Name, d/time.Millisecond) //} -// -//func (t *Timer) String() string { -// return fmt.Sprintf("%s:something|ms\n", t.Name) -//} diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 20d9ef1d2..9c3e933c8 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -1,6 +1,9 @@ package metrics4 -import "github.com/go-kit/kit/metrics" +import ( + "github.com/go-kit/kit/metrics" + "time" +) const FabioNamespace = "fabio" @@ -8,7 +11,50 @@ type Counter metrics.Counter type Gauge metrics.Gauge -type Timer metrics.Timer +type Histogram metrics.Histogram + +// TODO(max): Refactor Timer thingies +type Timer struct { + histograms []Histogram + start time.Time + unit time.Duration +} + +type ITimer interface { + Unit(time.Duration) + Reset() + Stop() + Duration(float64) + With(labelValues... string) ITimer +} + +func (t *Timer) Unit(u time.Duration) { + t.unit = u +} + +func (t *Timer) Stop() { + duration := float64(time.Since(t.start).Nanoseconds()) / float64(t.unit) + for _, h := range t.histograms { + h.Observe(duration) + } +} + +func (t *Timer) Reset() { + t.start = time.Now() +} + +func (t *Timer) Duration(duration float64) { + for _, h := range t.histograms { + h.Observe(duration) + } +} + +func (t *Timer) With(labelValues... string) ITimer { + for _, h := range t.histograms { + h.With(labelValues...) + } + return t +} // Provider is an abstraction of a metrics backend. type Provider interface { @@ -19,7 +65,10 @@ type Provider interface { NewGauge(name string) Gauge // NewTimer creates a new timer object. - //NewTimer(name string, labels ...string) Timer + NewTimer(name string) ITimer + + // NewHistogram creates a new histogram object. + NewHistogram(name string) Histogram // Unregister removes a previously registered // name or metric. Required for go-metrics and @@ -59,25 +108,29 @@ func (mp *MultiProvider) NewGauge(name string) Gauge { // NewTimer creates a MultiTimer with timer objects for all registered // providers. -//func (mp *MultiProvider) NewTimer(name string, labels ...string) Timer { -// var t []Timer -// for _, p := range mp.p { -// t = append(t, p.NewTimer(name, labels...)) -// } -// return &MultiTimer{t} -//} - -// Unregister removes the metric object from all registered providers. -//func (mp *MultiProvider) Unregister(v interface{}) { -// for _, p := range mp.p { -// p.Unregister(v) -// } -//} - -// Count measures a number. -//type Counter interface { -// Count(int) -//} +func (mp *MultiProvider) NewTimer(name string) ITimer { + var h []Histogram + + for _, p := range mp.p { + h = append(h, p.NewHistogram(name)) + } + + return &Timer{ + histograms: h, + start: time.Now(), + unit: time.Millisecond, + } +} + +// NewHistogram creates a MultiTimer with timer objects for all registered +// providers. +func (mp *MultiProvider) NewHistogram(name string) Histogram { + var h []Histogram + for _, p := range mp.p { + h = append(h, p.NewHistogram(name)) + } + return &MultiHistogram{h} +} // MultiCounter wraps zero or more counters. type MultiCounter struct { @@ -90,7 +143,7 @@ func (mc *MultiCounter) Add(delta float64) { } } -func (mc *MultiCounter) With(labelValues... string) metrics.Counter { +func (mc *MultiCounter) With(labelValues ... string) metrics.Counter { for _, c := range mc.counters { c.With(labelValues...) } @@ -114,25 +167,28 @@ func (mg *MultiGauge) Set(delta float64) { } } -func (mg *MultiGauge) With(labelValues... string) metrics.Gauge { +func (mg *MultiGauge) With(labelValues ... string) metrics.Gauge { for _, g := range mg.gauges { g.With(labelValues...) } return mg } -// Timer measures the time of an event. -//type Timer interface { -// Update(time.Duration) -//} +// MultiGauge wraps zero or more gauges. +type MultiHistogram struct { + histograms []Histogram +} -// MultTimer wraps zero or more timers. -//type MultiTimer struct { -// t []Timer -//} +func (mh *MultiHistogram) Observe(delta float64) { + for _, h := range mh.histograms { + h.Observe(delta) + } +} + +func (mh *MultiHistogram) With(labelValues ... string) metrics.Histogram { + for _, h := range mh.histograms { + h.With(labelValues...) + } + return mh +} -//func (mt *MultiTimer) Update(d time.Duration) { -// for _, t := range mt.t { -// t.Update(d) -// } -//} diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index a8533570d..2a48a520e 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -8,14 +8,18 @@ import ( ) type Provider struct { - counters map[string]metrics4.Counter - gauges map[string]metrics4.Gauge + counters map[string]metrics4.Counter + gauges map[string]metrics4.Gauge + timers map[string]metrics4.Timer + histograms map[string]metrics4.Histogram } func NewProvider() *Provider { return &Provider{ make(map[string]metrics4.Counter), make(map[string]metrics4.Gauge), + make(map[string]metrics4.Timer), + make(map[string]metrics4.Histogram), } } @@ -46,3 +50,22 @@ func (p *Provider) NewGauge(name string) metrics4.Gauge { return p.gauges[name] } + +func (p *Provider) NewHistogram(name string) metrics4.Histogram { + // TODO(max): Add lock ? + if p.histograms[name] == nil { + p.histograms[name] = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ + Namespace: metrics4.FabioNamespace, + Subsystem: "", + Name: name, + Help: "", + // TODO: Look on 'Buckets' + }, []string{}) + } + + return p.histograms[name] +} + +func (p *Provider) NewTimer(name string) metrics4.ITimer { + return nil +} diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index 56a7ff220..591289b33 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -44,7 +44,7 @@ type HTTPProxy struct { Lookup func(*http.Request) *route.Target // Requests is a timer metric which is updated for every request. - //Requests metrics4.Timer + Requests metrics4.ITimer // Noroute is a counter metric which is updated for every request // where Lookup() returns nil. @@ -196,19 +196,19 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { rw := &responseWriter{w: w} h.ServeHTTP(rw, r) end := timeNow() - //dur := end.Sub(start) - - //if p.Requests != nil { - // p.Requests.Update(dur) - //} - //if t.Timer != nil { - // t.Timer.Update(dur) - //} + dur := end.Sub(start) + + if p.Requests != nil { + p.Requests.Duration(float64(dur)) + } + if t.Timer != nil { + t.Timer.Duration(float64(dur)) + } if rw.code <= 0 { return } - //metrics.NewTimer("http.status", "code", strconv.Itoa(rw.code)).Update(dur) + metrics.NewTimer("http_status").With("code", strconv.Itoa(rw.code)).Duration(float64(dur)) // write access log if p.Logger != nil { diff --git a/route/target.go b/route/target.go index 16fe75784..1cd93bed3 100644 --- a/route/target.go +++ b/route/target.go @@ -1,6 +1,7 @@ package route import ( + "github.com/fabiolb/fabio/metrics4" "net/url" "strings" @@ -50,7 +51,7 @@ type Target struct { Weight float64 // Timer measures throughput and latency of this target - //Timer metrics4.Timer + Timer metrics4.ITimer // TimerName is the name of the timer in the metrics registry TimerName names.Service From 49aadaad62a72403639b1619bcb51fa470fd3eec Mon Sep 17 00:00:00 2001 From: maximka777 Date: Wed, 12 Dec 2018 19:04:13 +0300 Subject: [PATCH 09/30] Fixed timer.Duration arguments --- metrics4/metrics.go | 1 + proxy/http_proxy.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 9c3e933c8..539c1af57 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -44,6 +44,7 @@ func (t *Timer) Reset() { } func (t *Timer) Duration(duration float64) { + duration = duration / float64(t.unit) for _, h := range t.histograms { h.Observe(duration) } diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index 591289b33..cb8d3419c 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -199,16 +199,16 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { dur := end.Sub(start) if p.Requests != nil { - p.Requests.Duration(float64(dur)) + p.Requests.Duration(float64(dur.Nanoseconds())) } if t.Timer != nil { - t.Timer.Duration(float64(dur)) + t.Timer.Duration(float64(dur.Nanoseconds())) } if rw.code <= 0 { return } - metrics.NewTimer("http_status").With("code", strconv.Itoa(rw.code)).Duration(float64(dur)) + metrics.NewTimer("http_status").With("code", strconv.Itoa(rw.code)).Duration(float64(dur.Nanoseconds())) // write access log if p.Logger != nil { From 42b202c7f02ed80d2f9d261fb402145c0bde9868 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Thu, 13 Dec 2018 11:32:50 +0300 Subject: [PATCH 10/30] Refactored Timers --- metrics4/metrics.go | 97 ++++++++++++++++++++-------------- metrics4/prometheus/metrics.go | 14 ++++- proxy/http_proxy.go | 12 ++--- route/target.go | 5 +- 4 files changed, 75 insertions(+), 53 deletions(-) diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 539c1af57..930e2e65a 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -1,8 +1,9 @@ package metrics4 import ( - "github.com/go-kit/kit/metrics" "time" + + "github.com/go-kit/kit/metrics" ) const FabioNamespace = "fabio" @@ -13,48 +14,42 @@ type Gauge metrics.Gauge type Histogram metrics.Histogram -// TODO(max): Refactor Timer thingies -type Timer struct { - histograms []Histogram - start time.Time - unit time.Duration +type TimerStruct struct { + histogram Histogram + start time.Time } -type ITimer interface { - Unit(time.Duration) - Reset() - Stop() - Duration(float64) - With(labelValues... string) ITimer +func NewTimerStruct(h Histogram, start time.Time) Timer { + return &TimerStruct{ + h, + start, + } } -func (t *Timer) Unit(u time.Duration) { - t.unit = u +type Timer interface { + Start() + Stop() + Observe(duration time.Duration) + With(labelValues ... string) Timer } -func (t *Timer) Stop() { - duration := float64(time.Since(t.start).Nanoseconds()) / float64(t.unit) - for _, h := range t.histograms { - h.Observe(duration) - } +func (t *TimerStruct) Stop() { + t.histogram.Observe(float64(time.Since(t.start).Nanoseconds()) / float64(time.Millisecond)) } -func (t *Timer) Reset() { +func (t *TimerStruct) Start() { t.start = time.Now() } -func (t *Timer) Duration(duration float64) { - duration = duration / float64(t.unit) - for _, h := range t.histograms { - h.Observe(duration) - } +func (t *TimerStruct) Observe(duration time.Duration) { + t.histogram.Observe(float64(duration.Nanoseconds()) / float64(time.Millisecond)) } -func (t *Timer) With(labelValues... string) ITimer { - for _, h := range t.histograms { - h.With(labelValues...) +func (t *TimerStruct) With(labelValues ... string) Timer { + return &TimerStruct{ + t.histogram.With(labelValues...), + t.start, } - return t } // Provider is an abstraction of a metrics backend. @@ -66,7 +61,7 @@ type Provider interface { NewGauge(name string) Gauge // NewTimer creates a new timer object. - NewTimer(name string) ITimer + NewTimer(name string) Timer // NewHistogram creates a new histogram object. NewHistogram(name string) Histogram @@ -109,18 +104,12 @@ func (mp *MultiProvider) NewGauge(name string) Gauge { // NewTimer creates a MultiTimer with timer objects for all registered // providers. -func (mp *MultiProvider) NewTimer(name string) ITimer { - var h []Histogram - +func (mp *MultiProvider) NewTimer(name string) Timer { + var t []Timer for _, p := range mp.p { - h = append(h, p.NewHistogram(name)) - } - - return &Timer{ - histograms: h, - start: time.Now(), - unit: time.Millisecond, + t = append(t, p.NewTimer(name)) } + return &MultiTimer{t} } // NewHistogram creates a MultiTimer with timer objects for all registered @@ -193,3 +182,31 @@ func (mh *MultiHistogram) With(labelValues ... string) metrics.Histogram { return mh } +type MultiTimer struct { + timers []Timer +} + +func (mt *MultiTimer) Observe(duration time.Duration) { + for _, t := range mt.timers { + t.Observe(duration) + } +} + +func (mt *MultiTimer) Start() { + for _, t := range mt.timers { + t.Start() + } +} + +func (mt *MultiTimer) Stop() { + for _, t := range mt.timers { + t.Stop() + } +} + +func (mt *MultiTimer) With(labelValues ... string) Timer { + for _, t := range mt.timers { + t.With(labelValues...) + } + return mt +} diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index 2a48a520e..84e24ac9d 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -2,6 +2,7 @@ package prometheus import ( "github.com/fabiolb/fabio/metrics4" + "time" "github.com/go-kit/kit/metrics/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus" @@ -66,6 +67,15 @@ func (p *Provider) NewHistogram(name string) metrics4.Histogram { return p.histograms[name] } -func (p *Provider) NewTimer(name string) metrics4.ITimer { - return nil +func (p *Provider) NewTimer(name string) metrics4.Timer { + if p.timers[name] == nil { + h := prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ + Namespace: metrics4.FabioNamespace, + Name: name, + }, []string{}) + + p.timers[name] = metrics4.NewTimerStruct(h, time.Now()) + } + + return p.timers[name] } diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index cb8d3419c..647fae03f 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -44,7 +44,7 @@ type HTTPProxy struct { Lookup func(*http.Request) *route.Target // Requests is a timer metric which is updated for every request. - Requests metrics4.ITimer + Requests metrics4.Timer // Noroute is a counter metric which is updated for every request // where Lookup() returns nil. @@ -112,9 +112,6 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { if t.RedirectCode != 0 { http.Redirect(w, r, t.RedirectURL.String(), t.RedirectCode) - //if t.Timer != nil { - // t.Timer.Update(0) - //} metrics.NewCounter("http.status").With("code", strconv.Itoa(t.RedirectCode)).Add(1) return } @@ -199,16 +196,13 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { dur := end.Sub(start) if p.Requests != nil { - p.Requests.Duration(float64(dur.Nanoseconds())) - } - if t.Timer != nil { - t.Timer.Duration(float64(dur.Nanoseconds())) + p.Requests.Observe(dur) } if rw.code <= 0 { return } - metrics.NewTimer("http_status").With("code", strconv.Itoa(rw.code)).Duration(float64(dur.Nanoseconds())) + metrics.NewTimer("http_status").With("code", strconv.Itoa(rw.code)).Observe(dur) // write access log if p.Logger != nil { diff --git a/route/target.go b/route/target.go index 1cd93bed3..9e602e441 100644 --- a/route/target.go +++ b/route/target.go @@ -50,8 +50,9 @@ type Target struct { // Weight is the actual weight for this service in percent. Weight float64 - // Timer measures throughput and latency of this target - Timer metrics4.ITimer + // TODO(max): Isn't it the same as Requests timer in HTTPProxy + // TimerStruct measures throughput and latency of this target + Timer metrics4.Timer // TimerName is the name of the timer in the metrics registry TimerName names.Service From 1974cee6b6891be5da4f60c70520852f6dad6eb8 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Thu, 13 Dec 2018 12:07:50 +0300 Subject: [PATCH 11/30] Fixedusing of labels --- metrics4/metrics.go | 52 ++++++++++++++++++---------------- metrics4/prometheus/metrics.go | 16 +++++------ proxy/http_proxy.go | 4 ++- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 930e2e65a..78ca15e36 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -55,16 +55,16 @@ func (t *TimerStruct) With(labelValues ... string) Timer { // Provider is an abstraction of a metrics backend. type Provider interface { // NewCounter creates a new counter object. - NewCounter(name string) Counter + NewCounter(name string, labels... string) Counter // NewGauge creates a new gauge object. - NewGauge(name string) Gauge + NewGauge(name string, labels... string) Gauge // NewTimer creates a new timer object. - NewTimer(name string) Timer + NewTimer(name string, labels... string) Timer // NewHistogram creates a new histogram object. - NewHistogram(name string) Histogram + NewHistogram(name string, labels... string) Histogram // Unregister removes a previously registered // name or metric. Required for go-metrics and @@ -84,40 +84,40 @@ func NewMultiProvider(p []Provider) *MultiProvider { // NewCounter creates a MultiCounter with counter objects for all registered // providers. -func (mp *MultiProvider) NewCounter(name string) Counter { +func (mp *MultiProvider) NewCounter(name string, labels... string) Counter { var c []Counter for _, p := range mp.p { - c = append(c, p.NewCounter(name)) + c = append(c, p.NewCounter(name, labels...)) } return &MultiCounter{c} } // NewGauge creates a MultiGauge with gauge objects for all registered // providers. -func (mp *MultiProvider) NewGauge(name string) Gauge { +func (mp *MultiProvider) NewGauge(name string, labels... string) Gauge { var g []Gauge for _, p := range mp.p { - g = append(g, p.NewGauge(name)) + g = append(g, p.NewGauge(name, labels...)) } return &MultiGauge{g} } // NewTimer creates a MultiTimer with timer objects for all registered // providers. -func (mp *MultiProvider) NewTimer(name string) Timer { +func (mp *MultiProvider) NewTimer(name string, labels... string) Timer { var t []Timer for _, p := range mp.p { - t = append(t, p.NewTimer(name)) + t = append(t, p.NewTimer(name, labels...)) } return &MultiTimer{t} } // NewHistogram creates a MultiTimer with timer objects for all registered // providers. -func (mp *MultiProvider) NewHistogram(name string) Histogram { +func (mp *MultiProvider) NewHistogram(name string, labels... string) Histogram { var h []Histogram for _, p := range mp.p { - h = append(h, p.NewHistogram(name)) + h = append(h, p.NewHistogram(name, labels...)) } return &MultiHistogram{h} } @@ -134,10 +134,11 @@ func (mc *MultiCounter) Add(delta float64) { } func (mc *MultiCounter) With(labelValues ... string) metrics.Counter { - for _, c := range mc.counters { - c.With(labelValues...) + labeledCounters := make([]Counter, len(mc.counters)) + for i, c := range mc.counters { + labeledCounters[i] = c.With(labelValues...) } - return mc + return &MultiCounter{labeledCounters} } // MultiGauge wraps zero or more gauges. @@ -158,10 +159,11 @@ func (mg *MultiGauge) Set(delta float64) { } func (mg *MultiGauge) With(labelValues ... string) metrics.Gauge { - for _, g := range mg.gauges { - g.With(labelValues...) + labeledGauges := make([]Gauge, len(mg.gauges)) + for i, g := range mg.gauges { + labeledGauges[i] = g.With(labelValues...) } - return mg + return &MultiGauge{labeledGauges} } // MultiGauge wraps zero or more gauges. @@ -176,10 +178,11 @@ func (mh *MultiHistogram) Observe(delta float64) { } func (mh *MultiHistogram) With(labelValues ... string) metrics.Histogram { - for _, h := range mh.histograms { - h.With(labelValues...) + labeledHistograms := make([]Histogram, len(mh.histograms)) + for i, h := range mh.histograms { + labeledHistograms[i] = h.With(labelValues...) } - return mh + return &MultiHistogram{labeledHistograms} } type MultiTimer struct { @@ -205,8 +208,9 @@ func (mt *MultiTimer) Stop() { } func (mt *MultiTimer) With(labelValues ... string) Timer { - for _, t := range mt.timers { - t.With(labelValues...) + labeledTimers := make([]Timer, len(mt.timers)) + for i, t := range mt.timers { + labeledTimers[i] = t.With(labelValues...) } - return mt + return &MultiTimer{labeledTimers} } diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index 84e24ac9d..b02cf909e 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -24,7 +24,7 @@ func NewProvider() *Provider { } } -func (p *Provider) NewCounter(name string) metrics4.Counter { +func (p *Provider) NewCounter(name string, labels... string) metrics4.Counter { // TODO(max): Add lock ? if p.counters[name] == nil { p.counters[name] = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ @@ -32,13 +32,13 @@ func (p *Provider) NewCounter(name string) metrics4.Counter { Subsystem: "", Name: name, Help: "", - }, []string{}) + }, labels) } return p.counters[name] } -func (p *Provider) NewGauge(name string) metrics4.Gauge { +func (p *Provider) NewGauge(name string, labels... string) metrics4.Gauge { // TODO(max): Add lock ? if p.gauges[name] == nil { p.gauges[name] = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ @@ -46,13 +46,13 @@ func (p *Provider) NewGauge(name string) metrics4.Gauge { Subsystem: "", Name: name, Help: "", - }, []string{}) + }, labels) } return p.gauges[name] } -func (p *Provider) NewHistogram(name string) metrics4.Histogram { +func (p *Provider) NewHistogram(name string, labels... string) metrics4.Histogram { // TODO(max): Add lock ? if p.histograms[name] == nil { p.histograms[name] = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ @@ -61,18 +61,18 @@ func (p *Provider) NewHistogram(name string) metrics4.Histogram { Name: name, Help: "", // TODO: Look on 'Buckets' - }, []string{}) + }, labels) } return p.histograms[name] } -func (p *Provider) NewTimer(name string) metrics4.Timer { +func (p *Provider) NewTimer(name string, labels... string) metrics4.Timer { if p.timers[name] == nil { h := prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ Namespace: metrics4.FabioNamespace, Name: name, - }, []string{}) + }, labels) p.timers[name] = metrics4.NewTimerStruct(h, time.Now()) } diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index 647fae03f..cb04de1c7 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -202,7 +202,9 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - metrics.NewTimer("http_status").With("code", strconv.Itoa(rw.code)).Observe(dur) + http_status_timer := metrics.NewTimer("http_status", "code") + http_status_timer = http_status_timer.With("code", strconv.Itoa(rw.code)) + http_status_timer.Observe(dur) // write access log if p.Logger != nil { From 301c7e957022115a0e4bf960df65d327c2b1575b Mon Sep 17 00:00:00 2001 From: maximka777 Date: Thu, 13 Dec 2018 12:48:46 +0300 Subject: [PATCH 12/30] Added sync to Prometheus metrics provider --- metrics4/metrics.go | 3 ++- metrics4/prometheus/metrics.go | 29 ++++++++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 78ca15e36..910e9bade 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -112,7 +112,7 @@ func (mp *MultiProvider) NewTimer(name string, labels... string) Timer { return &MultiTimer{t} } -// NewHistogram creates a MultiTimer with timer objects for all registered +// NewHistogram creates a MultiHistogram with histogram objects for all registered // providers. func (mp *MultiProvider) NewHistogram(name string, labels... string) Histogram { var h []Histogram @@ -185,6 +185,7 @@ func (mh *MultiHistogram) With(labelValues ... string) metrics.Histogram { return &MultiHistogram{labeledHistograms} } +// MultiTimer wraps zero or more timers. type MultiTimer struct { timers []Timer } diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index b02cf909e..a505a03ed 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -2,6 +2,7 @@ package prometheus import ( "github.com/fabiolb/fabio/metrics4" + "sync" "time" "github.com/go-kit/kit/metrics/prometheus" @@ -13,19 +14,21 @@ type Provider struct { gauges map[string]metrics4.Gauge timers map[string]metrics4.Timer histograms map[string]metrics4.Histogram + mutex sync.Mutex } func NewProvider() *Provider { return &Provider{ - make(map[string]metrics4.Counter), - make(map[string]metrics4.Gauge), - make(map[string]metrics4.Timer), - make(map[string]metrics4.Histogram), + counters: make(map[string]metrics4.Counter), + gauges: make(map[string]metrics4.Gauge), + timers: make(map[string]metrics4.Timer), + histograms: make(map[string]metrics4.Histogram), } } -func (p *Provider) NewCounter(name string, labels... string) metrics4.Counter { - // TODO(max): Add lock ? +func (p *Provider) NewCounter(name string, labels ... string) metrics4.Counter { + p.mutex.Lock() + defer p.mutex.Unlock() if p.counters[name] == nil { p.counters[name] = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: metrics4.FabioNamespace, @@ -38,8 +41,9 @@ func (p *Provider) NewCounter(name string, labels... string) metrics4.Counter { return p.counters[name] } -func (p *Provider) NewGauge(name string, labels... string) metrics4.Gauge { - // TODO(max): Add lock ? +func (p *Provider) NewGauge(name string, labels ... string) metrics4.Gauge { + p.mutex.Lock() + defer p.mutex.Unlock() if p.gauges[name] == nil { p.gauges[name] = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: metrics4.FabioNamespace, @@ -52,8 +56,9 @@ func (p *Provider) NewGauge(name string, labels... string) metrics4.Gauge { return p.gauges[name] } -func (p *Provider) NewHistogram(name string, labels... string) metrics4.Histogram { - // TODO(max): Add lock ? +func (p *Provider) NewHistogram(name string, labels ... string) metrics4.Histogram { + p.mutex.Lock() + defer p.mutex.Unlock() if p.histograms[name] == nil { p.histograms[name] = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ Namespace: metrics4.FabioNamespace, @@ -67,7 +72,9 @@ func (p *Provider) NewHistogram(name string, labels... string) metrics4.Histogra return p.histograms[name] } -func (p *Provider) NewTimer(name string, labels... string) metrics4.Timer { +func (p *Provider) NewTimer(name string, labels ... string) metrics4.Timer { + p.mutex.Lock() + defer p.mutex.Unlock() if p.timers[name] == nil { h := prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ Namespace: metrics4.FabioNamespace, From 1288a0c64dff9bde973321726ecdec5dbb45650f Mon Sep 17 00:00:00 2001 From: maximka777 Date: Thu, 13 Dec 2018 14:42:10 +0300 Subject: [PATCH 13/30] Added NoopMetrics; Commented histograms; Added StatsD Gauge. --- admin/server.go | 4 +- config/config.go | 20 ++++----- config/default.go | 14 +++---- config/load.go | 20 ++++----- main.go | 19 +++++---- metrics4/metrics.go | 70 ++++++++++++++++--------------- metrics4/noop.go | 53 +++++++++++++++++++++++ metrics4/prometheus/metrics.go | 32 +++++++------- metrics4/statsd/statsd.go | 74 ++++++++++++++++++++++++++++++++ metrics4/statsdraw/statsd.go | 77 ---------------------------------- 10 files changed, 217 insertions(+), 166 deletions(-) create mode 100644 metrics4/noop.go create mode 100644 metrics4/statsd/statsd.go delete mode 100644 metrics4/statsdraw/statsd.go diff --git a/admin/server.go b/admin/server.go index d7d75f276..76da6224e 100644 --- a/admin/server.go +++ b/admin/server.go @@ -77,8 +77,8 @@ func (s *Server) handler() http.Handler { } func initMetricsHandlers(mux *http.ServeMux, s *Server) { - if strings.Contains(s.Cfg.Metrics.Target, "prometheus") && s.Cfg.Metrics.PrometheusEndpoint != "" { - mux.HandleFunc(s.Cfg.Metrics.PrometheusEndpoint, promhttp.Handler().ServeHTTP) + if strings.Contains(s.Cfg.Metrics.Target, "prometheus") && s.Cfg.Metrics.PrometheusMetricsEndpoint != "" { + mux.HandleFunc(s.Cfg.Metrics.PrometheusMetricsEndpoint, promhttp.Handler().ServeHTTP) } } diff --git a/config/config.go b/config/config.go index 3eb5839d7..56696e579 100644 --- a/config/config.go +++ b/config/config.go @@ -95,16 +95,16 @@ type Log struct { } type Metrics struct { - Target string - Prefix string - Names string - Interval time.Duration - Timeout time.Duration - Retry time.Duration - PrometheusEndpoint string - GraphiteAddr string - StatsDAddr string - Circonus Circonus + Target string + //Prefix string + //Names string + Interval time.Duration + //Timeout time.Duration + //Retry time.Duration + PrometheusMetricsEndpoint string + StatsDAddr string + GraphiteAddr string + //Circonus Circonus } type Registry struct { diff --git a/config/default.go b/config/default.go index 45f68424d..20464c84d 100644 --- a/config/default.go +++ b/config/default.go @@ -26,14 +26,14 @@ var defaultConfig = &Config{ Level: "INFO", }, Metrics: Metrics{ - Prefix: "{{clean .Hostname}}.{{clean .Exec}}", - Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", + //Prefix: "{{clean .Hostname}}.{{clean .Exec}}", + //Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", Interval: 30 * time.Second, - Timeout: 10 * time.Second, - Retry: 500 * time.Millisecond, - Circonus: Circonus{ - APIApp: "fabio", - }, + //Timeout: 10 * time.Second, + //Retry: 500 * time.Millisecond, + //Circonus: Circonus{ + // APIApp: "fabio", + //}, }, Proxy: Proxy{ MaxConn: 10000, diff --git a/config/load.go b/config/load.go index 7be6b0c01..18ec01cc3 100644 --- a/config/load.go +++ b/config/load.go @@ -144,19 +144,19 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c f.StringVar(&cfg.Log.RoutesFormat, "log.routes.format", defaultConfig.Log.RoutesFormat, "log format of routing table updates") f.StringVar(&cfg.Log.Level, "log.level", defaultConfig.Log.Level, "log level: TRACE, DEBUG, INFO, WARN, ERROR, FATAL") f.StringVar(&cfg.Metrics.Target, "metrics.target", defaultConfig.Metrics.Target, "metrics backend") - f.StringVar(&cfg.Metrics.Prefix, "metrics.prefix", defaultConfig.Metrics.Prefix, "prefix for reported metrics") - f.StringVar(&cfg.Metrics.Names, "metrics.names", defaultConfig.Metrics.Names, "route metric name template") + //f.StringVar(&cfg.Metrics.Prefix, "metrics.prefix", defaultConfig.Metrics.Prefix, "prefix for reported metrics") + //f.StringVar(&cfg.Metrics.Names, "metrics.names", defaultConfig.Metrics.Names, "route metric name template") f.DurationVar(&cfg.Metrics.Interval, "metrics.interval", defaultConfig.Metrics.Interval, "metrics reporting interval") - f.DurationVar(&cfg.Metrics.Timeout, "metrics.timeout", defaultConfig.Metrics.Timeout, "timeout for metrics to become available") - f.DurationVar(&cfg.Metrics.Retry, "metrics.retry", defaultConfig.Metrics.Retry, "retry interval during startup") - f.StringVar(&cfg.Metrics.PrometheusEndpoint, "metrics.prometheus.endpoint", defaultConfig.Metrics.PrometheusEndpoint, "Metrics endpoint for Prometheus") + //f.DurationVar(&cfg.Metrics.Timeout, "metrics.timeout", defaultConfig.Metrics.Timeout, "timeout for metrics to become available") + //f.DurationVar(&cfg.Metrics.Retry, "metrics.retry", defaultConfig.Metrics.Retry, "retry interval during startup") + f.StringVar(&cfg.Metrics.PrometheusMetricsEndpoint, "metrics.prometheus.endpoint", defaultConfig.Metrics.PrometheusMetricsEndpoint, "Metrics endpoint for Prometheus") f.StringVar(&cfg.Metrics.GraphiteAddr, "metrics.graphite.addr", defaultConfig.Metrics.GraphiteAddr, "graphite server address") f.StringVar(&cfg.Metrics.StatsDAddr, "metrics.statsd.addr", defaultConfig.Metrics.StatsDAddr, "statsd server address") - f.StringVar(&cfg.Metrics.Circonus.APIKey, "metrics.circonus.apikey", defaultConfig.Metrics.Circonus.APIKey, "Circonus API token key") - f.StringVar(&cfg.Metrics.Circonus.APIApp, "metrics.circonus.apiapp", defaultConfig.Metrics.Circonus.APIApp, "Circonus API token app") - f.StringVar(&cfg.Metrics.Circonus.APIURL, "metrics.circonus.apiurl", defaultConfig.Metrics.Circonus.APIURL, "Circonus API URL") - f.StringVar(&cfg.Metrics.Circonus.BrokerID, "metrics.circonus.brokerid", defaultConfig.Metrics.Circonus.BrokerID, "Circonus Broker ID") - f.StringVar(&cfg.Metrics.Circonus.CheckID, "metrics.circonus.checkid", defaultConfig.Metrics.Circonus.CheckID, "Circonus Check ID") + //f.StringVar(&cfg.Metrics.Circonus.APIKey, "metrics.circonus.apikey", defaultConfig.Metrics.Circonus.APIKey, "Circonus API token key") + //f.StringVar(&cfg.Metrics.Circonus.APIApp, "metrics.circonus.apiapp", defaultConfig.Metrics.Circonus.APIApp, "Circonus API token app") + //f.StringVar(&cfg.Metrics.Circonus.APIURL, "metrics.circonus.apiurl", defaultConfig.Metrics.Circonus.APIURL, "Circonus API URL") + //f.StringVar(&cfg.Metrics.Circonus.BrokerID, "metrics.circonus.brokerid", defaultConfig.Metrics.Circonus.BrokerID, "Circonus Broker ID") + //f.StringVar(&cfg.Metrics.Circonus.CheckID, "metrics.circonus.checkid", defaultConfig.Metrics.Circonus.CheckID, "Circonus Check ID") f.StringVar(&cfg.Registry.Backend, "registry.backend", defaultConfig.Registry.Backend, "registry backend") f.DurationVar(&cfg.Registry.Timeout, "registry.timeout", defaultConfig.Registry.Timeout, "timeout for registry to become available") f.DurationVar(&cfg.Registry.Retry, "registry.retry", defaultConfig.Registry.Retry, "retry interval during startup") diff --git a/main.go b/main.go index 765da3646..88db0f9ff 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "crypto/tls" "encoding/json" "fmt" - "github.com/fabiolb/fabio/metrics4/prometheus" "io" "log" "net" @@ -24,6 +23,8 @@ import ( "github.com/fabiolb/fabio/exit" "github.com/fabiolb/fabio/logger" "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/prometheus" + "github.com/fabiolb/fabio/metrics4/statsd" "github.com/fabiolb/fabio/noroute" "github.com/fabiolb/fabio/proxy" "github.com/fabiolb/fabio/proxy/tcp" @@ -319,14 +320,14 @@ func initMetrics(cfg *config.Config) metrics4.Provider { // p = append(p, &flat.Provider{}) //case "label": // p = append(p, &label.Provider{}) - //case "statsd_raw": - // // prefix := cfg.Metrics.Prefix // prefix is a template and needs to be expanded - // prefix := "" - // pp, err := statsdraw.NewProvider(prefix, cfg.Metrics.StatsDAddr, cfg.Metrics.Interval) - // if err != nil { - // exit.Fatalf("[FATAL] Cannot initialize statsd metrics: %s", err) - // } - // p = append(p, pp) + case "statsd": + provider, err := statsd.NewProvider(cfg.Metrics.StatsDAddr, cfg.Metrics.Interval) + + if err != nil { + exit.Fatalf("[FATAL] Cannot initialize statsd metrics: %s", err) + } + + p = append(p, provider) default: log.Printf("[WARN] Skipping unknown metrics provider %q", x) continue diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 910e9bade..1fa4108b9 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -12,14 +12,14 @@ type Counter metrics.Counter type Gauge metrics.Gauge -type Histogram metrics.Histogram +//type Histogram metrics.Histogram type TimerStruct struct { - histogram Histogram + histogram metrics.Histogram start time.Time } -func NewTimerStruct(h Histogram, start time.Time) Timer { +func NewTimerStruct(h metrics.Histogram, start time.Time) Timer { return &TimerStruct{ h, start, @@ -55,16 +55,16 @@ func (t *TimerStruct) With(labelValues ... string) Timer { // Provider is an abstraction of a metrics backend. type Provider interface { // NewCounter creates a new counter object. - NewCounter(name string, labels... string) Counter + NewCounter(name string, labels ... string) Counter // NewGauge creates a new gauge object. - NewGauge(name string, labels... string) Gauge + NewGauge(name string, labels ... string) Gauge // NewTimer creates a new timer object. - NewTimer(name string, labels... string) Timer + NewTimer(name string, labels ... string) Timer // NewHistogram creates a new histogram object. - NewHistogram(name string, labels... string) Histogram + //NewHistogram(name string, labels ... string) Histogram // Unregister removes a previously registered // name or metric. Required for go-metrics and @@ -84,7 +84,7 @@ func NewMultiProvider(p []Provider) *MultiProvider { // NewCounter creates a MultiCounter with counter objects for all registered // providers. -func (mp *MultiProvider) NewCounter(name string, labels... string) Counter { +func (mp *MultiProvider) NewCounter(name string, labels ... string) Counter { var c []Counter for _, p := range mp.p { c = append(c, p.NewCounter(name, labels...)) @@ -94,7 +94,7 @@ func (mp *MultiProvider) NewCounter(name string, labels... string) Counter { // NewGauge creates a MultiGauge with gauge objects for all registered // providers. -func (mp *MultiProvider) NewGauge(name string, labels... string) Gauge { +func (mp *MultiProvider) NewGauge(name string, labels ... string) Gauge { var g []Gauge for _, p := range mp.p { g = append(g, p.NewGauge(name, labels...)) @@ -104,7 +104,7 @@ func (mp *MultiProvider) NewGauge(name string, labels... string) Gauge { // NewTimer creates a MultiTimer with timer objects for all registered // providers. -func (mp *MultiProvider) NewTimer(name string, labels... string) Timer { +func (mp *MultiProvider) NewTimer(name string, labels ... string) Timer { var t []Timer for _, p := range mp.p { t = append(t, p.NewTimer(name, labels...)) @@ -114,13 +114,13 @@ func (mp *MultiProvider) NewTimer(name string, labels... string) Timer { // NewHistogram creates a MultiHistogram with histogram objects for all registered // providers. -func (mp *MultiProvider) NewHistogram(name string, labels... string) Histogram { - var h []Histogram - for _, p := range mp.p { - h = append(h, p.NewHistogram(name, labels...)) - } - return &MultiHistogram{h} -} +//func (mp *MultiProvider) NewHistogram(name string, labels ... string) Histogram { +// var h []Histogram +// for _, p := range mp.p { +// h = append(h, p.NewHistogram(name, labels...)) +// } +// return &MultiHistogram{h} +//} // MultiCounter wraps zero or more counters. type MultiCounter struct { @@ -167,23 +167,25 @@ func (mg *MultiGauge) With(labelValues ... string) metrics.Gauge { } // MultiGauge wraps zero or more gauges. -type MultiHistogram struct { - histograms []Histogram -} - -func (mh *MultiHistogram) Observe(delta float64) { - for _, h := range mh.histograms { - h.Observe(delta) - } -} - -func (mh *MultiHistogram) With(labelValues ... string) metrics.Histogram { - labeledHistograms := make([]Histogram, len(mh.histograms)) - for i, h := range mh.histograms { - labeledHistograms[i] = h.With(labelValues...) - } - return &MultiHistogram{labeledHistograms} -} +//type MultiHistogram struct { +// histograms []Histogram +//} +// +//func (mh *MultiHistogram) Observe(delta float64) { +// for _, h := range mh.histograms { +// if h != nil { +// h.Observe(delta) +// } +// } +//} +// +//func (mh *MultiHistogram) With(labelValues ... string) metrics.Histogram { +// labeledHistograms := make([]Histogram, len(mh.histograms)) +// for i, h := range mh.histograms { +// labeledHistograms[i] = h.With(labelValues...) +// } +// return &MultiHistogram{labeledHistograms} +//} // MultiTimer wraps zero or more timers. type MultiTimer struct { diff --git a/metrics4/noop.go b/metrics4/noop.go new file mode 100644 index 000000000..354a71b8d --- /dev/null +++ b/metrics4/noop.go @@ -0,0 +1,53 @@ +package metrics4 + +import ( + "time" + + "github.com/go-kit/kit/metrics" +) + +var noopCounter = NoopCounter{} + +type NoopCounter struct{} + +func (c *NoopCounter) Add(float64) {} + +func (c *NoopCounter) With(labels ... string) metrics.Counter { + return c +} + +var noopTimer = NoopTimer{} + +type NoopTimer struct{} + +func (t *NoopTimer) Observe(time.Duration) {} + +func (t *NoopTimer) Start() {} + +func (t *NoopTimer) Stop() {} + +func (t *NoopTimer) With(labels ... string) Timer { + return t +} + +var noopGauge = NoopGauge{} + +type NoopGauge struct{} + +func (g *NoopGauge) Add(float64) {} + +func (g *NoopGauge) Set(float64) {} + +func (g *NoopGauge) With(... string) metrics.Gauge { + return g +} + +//var noopHistogram = NoopHistogram{} +// +//type NoopHistogram struct{} +// +//func (h *NoopHistogram) With(labelValues ...string) metrics.Histogram { +// return h +//} +// +//func (h *NoopHistogram) Observe(value float64) {} diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index a505a03ed..2941cf143 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -13,7 +13,6 @@ type Provider struct { counters map[string]metrics4.Counter gauges map[string]metrics4.Gauge timers map[string]metrics4.Timer - histograms map[string]metrics4.Histogram mutex sync.Mutex } @@ -22,7 +21,6 @@ func NewProvider() *Provider { counters: make(map[string]metrics4.Counter), gauges: make(map[string]metrics4.Gauge), timers: make(map[string]metrics4.Timer), - histograms: make(map[string]metrics4.Histogram), } } @@ -56,21 +54,21 @@ func (p *Provider) NewGauge(name string, labels ... string) metrics4.Gauge { return p.gauges[name] } -func (p *Provider) NewHistogram(name string, labels ... string) metrics4.Histogram { - p.mutex.Lock() - defer p.mutex.Unlock() - if p.histograms[name] == nil { - p.histograms[name] = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ - Namespace: metrics4.FabioNamespace, - Subsystem: "", - Name: name, - Help: "", - // TODO: Look on 'Buckets' - }, labels) - } - - return p.histograms[name] -} +//func (p *Provider) NewHistogram(name string, labels ... string) metrics4.Histogram { +// p.mutex.Lock() +// defer p.mutex.Unlock() +// if p.histograms[name] == nil { +// p.histograms[name] = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ +// Namespace: metrics4.FabioNamespace, +// Subsystem: "", +// Name: name, +// Help: "", +// // TODO: Look on 'Buckets' +// }, labels) +// } +// +// return p.histograms[name] +//} func (p *Provider) NewTimer(name string, labels ... string) metrics4.Timer { p.mutex.Lock() diff --git a/metrics4/statsd/statsd.go b/metrics4/statsd/statsd.go new file mode 100644 index 000000000..8d880a6a3 --- /dev/null +++ b/metrics4/statsd/statsd.go @@ -0,0 +1,74 @@ +package statsd + +import ( + "github.com/go-kit/kit/log" + "time" + + "github.com/fabiolb/fabio/metrics4" + "github.com/go-kit/kit/metrics/statsd" +) + +type Provider struct { + client *statsd.Statsd + ticker *time.Ticker +} + +func NewProvider(addr string, interval time.Duration) (metrics4.Provider, error) { + client := statsd.New(metrics4.FabioNamespace, log.NewNopLogger()) + + ticker := time.NewTicker(interval) + + go client.SendLoop(ticker.C, "udp", addr) + + return &Provider{client, ticker}, nil +} + +func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { + return &metrics4.NoopCounter{} +} + +func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { + return p.client.NewGauge(name) +} + +func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { + return &metrics4.NoopTimer{} +} + +//func (p *Provider) NewHistogram(name string, labels ...string) metrics4.Histogram { +// return &metrics4.NoopHistogram{} +//} + +// TODO:(max): Add kinda destructor + +//func (p *Provider) Unregister(interface{}) {} +// +//type Counter struct { +// c *statsd.Client +// name string +// labels []string +//} +// +//func (v *Counter) Count(n int) { +// v.c.Count(names.Flatten(v.name, v.labels, names.DotSeparator), n) +//} +// +//type Gauge struct { +// c *statsd.Client +// name string +// labels []string +//} +// +//func (v *Gauge) Update(n int) { +// v.c.Gauge(names.Flatten(v.name, v.labels, names.DotSeparator), n) +//} +// +//type Timer struct { +// c *statsd.Client +// name string +// labels []string +//} +// +//func (v *Timer) Update(d time.Duration) { +// v.c.Timing(names.Flatten(v.name, v.labels, names.DotSeparator), d) +//} diff --git a/metrics4/statsdraw/statsd.go b/metrics4/statsdraw/statsd.go deleted file mode 100644 index 0ec3a7957..000000000 --- a/metrics4/statsdraw/statsd.go +++ /dev/null @@ -1,77 +0,0 @@ -package statsdraw - -//import ( -// "time" -// -// "github.com/alexcesaro/statsd" -// "github.com/fabiolb/fabio/metrics4" -// "github.com/fabiolb/fabio/metrics4/names" -//) -// -//type Provider struct { -// c *statsd.Client -//} -// -//func NewProvider(prefix, addr string, interval time.Duration) (*Provider, error) { -// opts := []statsd.Option{ -// statsd.Address(addr), -// statsd.FlushPeriod(interval), -// } -// if prefix != "" { -// opts = append(opts, statsd.Prefix(prefix)) -// } -// -// c, err := statsd.New(opts...) -// if err != nil { -// return nil, err -// } -// return &Provider{c}, nil -//} -// -//func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { -// return &Counter{c: p.c, name: name, labels: labels} -//} -// -//func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { -// return &Gauge{c: p.c, name: name, labels: labels} -//} -// -//func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { -// return &Timer{c: p.c, name: name, labels: labels} -//} -// -//func (p *Provider) GetMetrics() []*metrics4.Metric { -// return make([]*metrics4.Metric, 0) -//} -// -//func (p *Provider) Unregister(interface{}) {} -// -//type Counter struct { -// c *statsd.Client -// name string -// labels []string -//} -// -//func (v *Counter) Count(n int) { -// v.c.Count(names.Flatten(v.name, v.labels, names.DotSeparator), n) -//} -// -//type Gauge struct { -// c *statsd.Client -// name string -// labels []string -//} -// -//func (v *Gauge) Update(n int) { -// v.c.Gauge(names.Flatten(v.name, v.labels, names.DotSeparator), n) -//} -// -//type Timer struct { -// c *statsd.Client -// name string -// labels []string -//} -// -//func (v *Timer) Update(d time.Duration) { -// v.c.Timing(names.Flatten(v.name, v.labels, names.DotSeparator), d) -//} From 785446c88a120357ab88824b97a646f15a0b811f Mon Sep 17 00:00:00 2001 From: maximka777 Date: Thu, 13 Dec 2018 14:47:24 +0300 Subject: [PATCH 14/30] Removed comments --- metrics4/metrics.go | 39 +++------------------------------- metrics4/noop.go | 10 --------- metrics4/prometheus/metrics.go | 20 ----------------- metrics4/statsd/statsd.go | 38 --------------------------------- 4 files changed, 3 insertions(+), 104 deletions(-) diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 1fa4108b9..24f67a96b 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -63,14 +63,12 @@ type Provider interface { // NewTimer creates a new timer object. NewTimer(name string, labels ... string) Timer - // NewHistogram creates a new histogram object. - //NewHistogram(name string, labels ... string) Histogram + // Dispose() // Unregister removes a previously registered // name or metric. Required for go-metrics and - // service pruning. This signature is probably not - // correct. - //Unregister(v interface{}) + // service pruning. + // Unregister(name string) } // MultiProvider wraps zero or more providers. @@ -112,16 +110,6 @@ func (mp *MultiProvider) NewTimer(name string, labels ... string) Timer { return &MultiTimer{t} } -// NewHistogram creates a MultiHistogram with histogram objects for all registered -// providers. -//func (mp *MultiProvider) NewHistogram(name string, labels ... string) Histogram { -// var h []Histogram -// for _, p := range mp.p { -// h = append(h, p.NewHistogram(name, labels...)) -// } -// return &MultiHistogram{h} -//} - // MultiCounter wraps zero or more counters. type MultiCounter struct { counters []Counter @@ -166,27 +154,6 @@ func (mg *MultiGauge) With(labelValues ... string) metrics.Gauge { return &MultiGauge{labeledGauges} } -// MultiGauge wraps zero or more gauges. -//type MultiHistogram struct { -// histograms []Histogram -//} -// -//func (mh *MultiHistogram) Observe(delta float64) { -// for _, h := range mh.histograms { -// if h != nil { -// h.Observe(delta) -// } -// } -//} -// -//func (mh *MultiHistogram) With(labelValues ... string) metrics.Histogram { -// labeledHistograms := make([]Histogram, len(mh.histograms)) -// for i, h := range mh.histograms { -// labeledHistograms[i] = h.With(labelValues...) -// } -// return &MultiHistogram{labeledHistograms} -//} - // MultiTimer wraps zero or more timers. type MultiTimer struct { timers []Timer diff --git a/metrics4/noop.go b/metrics4/noop.go index 354a71b8d..1e89d984d 100644 --- a/metrics4/noop.go +++ b/metrics4/noop.go @@ -41,13 +41,3 @@ func (g *NoopGauge) Set(float64) {} func (g *NoopGauge) With(... string) metrics.Gauge { return g } - -//var noopHistogram = NoopHistogram{} -// -//type NoopHistogram struct{} -// -//func (h *NoopHistogram) With(labelValues ...string) metrics.Histogram { -// return h -//} -// -//func (h *NoopHistogram) Observe(value float64) {} diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index 2941cf143..f5984000b 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -30,9 +30,7 @@ func (p *Provider) NewCounter(name string, labels ... string) metrics4.Counter { if p.counters[name] == nil { p.counters[name] = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: metrics4.FabioNamespace, - Subsystem: "", Name: name, - Help: "", }, labels) } @@ -45,31 +43,13 @@ func (p *Provider) NewGauge(name string, labels ... string) metrics4.Gauge { if p.gauges[name] == nil { p.gauges[name] = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: metrics4.FabioNamespace, - Subsystem: "", Name: name, - Help: "", }, labels) } return p.gauges[name] } -//func (p *Provider) NewHistogram(name string, labels ... string) metrics4.Histogram { -// p.mutex.Lock() -// defer p.mutex.Unlock() -// if p.histograms[name] == nil { -// p.histograms[name] = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ -// Namespace: metrics4.FabioNamespace, -// Subsystem: "", -// Name: name, -// Help: "", -// // TODO: Look on 'Buckets' -// }, labels) -// } -// -// return p.histograms[name] -//} - func (p *Provider) NewTimer(name string, labels ... string) metrics4.Timer { p.mutex.Lock() defer p.mutex.Unlock() diff --git a/metrics4/statsd/statsd.go b/metrics4/statsd/statsd.go index 8d880a6a3..48734fbc6 100644 --- a/metrics4/statsd/statsd.go +++ b/metrics4/statsd/statsd.go @@ -34,41 +34,3 @@ func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { return &metrics4.NoopTimer{} } - -//func (p *Provider) NewHistogram(name string, labels ...string) metrics4.Histogram { -// return &metrics4.NoopHistogram{} -//} - -// TODO:(max): Add kinda destructor - -//func (p *Provider) Unregister(interface{}) {} -// -//type Counter struct { -// c *statsd.Client -// name string -// labels []string -//} -// -//func (v *Counter) Count(n int) { -// v.c.Count(names.Flatten(v.name, v.labels, names.DotSeparator), n) -//} -// -//type Gauge struct { -// c *statsd.Client -// name string -// labels []string -//} -// -//func (v *Gauge) Update(n int) { -// v.c.Gauge(names.Flatten(v.name, v.labels, names.DotSeparator), n) -//} -// -//type Timer struct { -// c *statsd.Client -// name string -// labels []string -//} -// -//func (v *Timer) Update(d time.Duration) { -// v.c.Timing(names.Flatten(v.name, v.labels, names.DotSeparator), d) -//} From cb7b1fb520a14010683e4b5ef35c706f6bd369fd Mon Sep 17 00:00:00 2001 From: maximka777 Date: Thu, 13 Dec 2018 15:42:20 +0300 Subject: [PATCH 15/30] Added StatsD Counter and Timer --- metrics4/metrics.go | 55 ++--------------------- metrics4/noop.go | 8 +--- metrics4/prometheus/metrics.go | 24 +++++++++- metrics4/statsd/{statsd.go => metrics.go} | 32 ++++++++++++- 4 files changed, 56 insertions(+), 63 deletions(-) rename metrics4/statsd/{statsd.go => metrics.go} (55%) diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 24f67a96b..994ccc208 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -1,8 +1,6 @@ package metrics4 import ( - "time" - "github.com/go-kit/kit/metrics" ) @@ -12,44 +10,9 @@ type Counter metrics.Counter type Gauge metrics.Gauge -//type Histogram metrics.Histogram - -type TimerStruct struct { - histogram metrics.Histogram - start time.Time -} - -func NewTimerStruct(h metrics.Histogram, start time.Time) Timer { - return &TimerStruct{ - h, - start, - } -} - type Timer interface { - Start() - Stop() - Observe(duration time.Duration) - With(labelValues ... string) Timer -} - -func (t *TimerStruct) Stop() { - t.histogram.Observe(float64(time.Since(t.start).Nanoseconds()) / float64(time.Millisecond)) -} - -func (t *TimerStruct) Start() { - t.start = time.Now() -} - -func (t *TimerStruct) Observe(duration time.Duration) { - t.histogram.Observe(float64(duration.Nanoseconds()) / float64(time.Millisecond)) -} - -func (t *TimerStruct) With(labelValues ... string) Timer { - return &TimerStruct{ - t.histogram.With(labelValues...), - t.start, - } + Observe(float64) + With(... string) Timer } // Provider is an abstraction of a metrics backend. @@ -159,24 +122,12 @@ type MultiTimer struct { timers []Timer } -func (mt *MultiTimer) Observe(duration time.Duration) { +func (mt *MultiTimer) Observe(duration float64) { for _, t := range mt.timers { t.Observe(duration) } } -func (mt *MultiTimer) Start() { - for _, t := range mt.timers { - t.Start() - } -} - -func (mt *MultiTimer) Stop() { - for _, t := range mt.timers { - t.Stop() - } -} - func (mt *MultiTimer) With(labelValues ... string) Timer { labeledTimers := make([]Timer, len(mt.timers)) for i, t := range mt.timers { diff --git a/metrics4/noop.go b/metrics4/noop.go index 1e89d984d..212778ef5 100644 --- a/metrics4/noop.go +++ b/metrics4/noop.go @@ -1,8 +1,6 @@ package metrics4 import ( - "time" - "github.com/go-kit/kit/metrics" ) @@ -20,11 +18,7 @@ var noopTimer = NoopTimer{} type NoopTimer struct{} -func (t *NoopTimer) Observe(time.Duration) {} - -func (t *NoopTimer) Start() {} - -func (t *NoopTimer) Stop() {} +func (t *NoopTimer) Observe(float64) {} func (t *NoopTimer) With(labels ... string) Timer { return t diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index f5984000b..3da0f4a2a 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -1,10 +1,11 @@ package prometheus import ( - "github.com/fabiolb/fabio/metrics4" "sync" "time" + "github.com/fabiolb/fabio/metrics4" + "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus" ) @@ -59,8 +60,27 @@ func (p *Provider) NewTimer(name string, labels ... string) metrics4.Timer { Name: name, }, labels) - p.timers[name] = metrics4.NewTimerStruct(h, time.Now()) + p.timers[name] = &Timer{ + h, + time.Now(), + } } return p.timers[name] } + +type Timer struct { + histogram metrics.Histogram + start time.Time +} + +func (t *Timer) Observe(dur float64) { + t.histogram.Observe(dur) +} + +func (t *Timer) With(labelValues ... string) metrics4.Timer { + return &Timer{ + t.histogram.With(labelValues...), + t.start, + } +} diff --git a/metrics4/statsd/statsd.go b/metrics4/statsd/metrics.go similarity index 55% rename from metrics4/statsd/statsd.go rename to metrics4/statsd/metrics.go index 48734fbc6..0df0c6864 100644 --- a/metrics4/statsd/statsd.go +++ b/metrics4/statsd/metrics.go @@ -2,6 +2,7 @@ package statsd import ( "github.com/go-kit/kit/log" + "github.com/go-kit/kit/metrics" "time" "github.com/fabiolb/fabio/metrics4" @@ -24,7 +25,7 @@ func NewProvider(addr string, interval time.Duration) (metrics4.Provider, error) } func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { - return &metrics4.NoopCounter{} + return p.client.NewCounter(name, 1) } func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { @@ -32,5 +33,32 @@ func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { } func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { - return &metrics4.NoopTimer{} + return &Timer{ + timing: p.client.NewTiming(name, 1), + } +} + +type Timer struct { + timing *statsd.Timing + histogram metrics.Histogram +} + +func (t *Timer) Observe(value float64) { + if t.timing != nil { + t.timing.Observe(value) + } else if t.histogram != nil { + t.histogram.Observe(value) + } +} + +func (t *Timer) With(labelValues ... string) metrics4.Timer { + if t.timing != nil { + return &Timer{ + histogram: t.timing.With(labelValues...), + } + } else { + return &Timer{ + histogram: t.histogram.With(labelValues...), + } + } } From 68e11810bf2b29dfc9d995a9b1656c8015f90088 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Thu, 13 Dec 2018 20:13:17 +0300 Subject: [PATCH 16/30] Small refactoring --- config/config.go | 15 ++++++---- config/default.go | 14 +++++----- metrics4/flat/metrics.go | 59 --------------------------------------- metrics4/label/metrics.go | 56 ------------------------------------- proxy/http_proxy.go | 6 ++-- 5 files changed, 19 insertions(+), 131 deletions(-) delete mode 100644 metrics4/flat/metrics.go delete mode 100644 metrics4/label/metrics.go diff --git a/config/config.go b/config/config.go index 56696e579..83cd34af8 100644 --- a/config/config.go +++ b/config/config.go @@ -96,15 +96,20 @@ type Log struct { type Metrics struct { Target string - //Prefix string - //Names string + // TODO (max): Remove ? + Prefix string + // TODO (max): Remove ? + Names string Interval time.Duration - //Timeout time.Duration - //Retry time.Duration + // TODO (max): Remove ? + Timeout time.Duration + // TODO (max): Remove ? + Retry time.Duration PrometheusMetricsEndpoint string StatsDAddr string GraphiteAddr string - //Circonus Circonus + // TODO (max): Remove ? + Circonus Circonus } type Registry struct { diff --git a/config/default.go b/config/default.go index 20464c84d..45f68424d 100644 --- a/config/default.go +++ b/config/default.go @@ -26,14 +26,14 @@ var defaultConfig = &Config{ Level: "INFO", }, Metrics: Metrics{ - //Prefix: "{{clean .Hostname}}.{{clean .Exec}}", - //Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", + Prefix: "{{clean .Hostname}}.{{clean .Exec}}", + Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", Interval: 30 * time.Second, - //Timeout: 10 * time.Second, - //Retry: 500 * time.Millisecond, - //Circonus: Circonus{ - // APIApp: "fabio", - //}, + Timeout: 10 * time.Second, + Retry: 500 * time.Millisecond, + Circonus: Circonus{ + APIApp: "fabio", + }, }, Proxy: Proxy{ MaxConn: 10000, diff --git a/metrics4/flat/metrics.go b/metrics4/flat/metrics.go deleted file mode 100644 index 12a003b8f..000000000 --- a/metrics4/flat/metrics.go +++ /dev/null @@ -1,59 +0,0 @@ -package flat - -//import ( -// "fmt" -// "time" -// -// "github.com/fabiolb/fabio/metrics4" -// "github.com/fabiolb/fabio/metrics4/names" -//) -// -//type Provider struct{ -// Metrics []*metrics4.Metric -//} -// -//func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { -// return &Counter{Name: names.Flatten(name, labels, names.DotSeparator)} -//} -// -//func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { -// return &Gauge{Name: names.Flatten(name, labels, names.DotSeparator)} -//} -// -//func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { -// timer := Timer{Name: names.Flatten(name, labels, names.DotSeparator)} -// -// p.Metrics = append(p.Metrics, &timer) -// -// return &timer -//} -// -//func (p *Provider) GetMetrics() []*metrics4.Metric { -// return make([]*metrics4.Metric, 0) -//} -// -//func (p *Provider) Unregister(interface{}) {} -// -//type Counter struct { -// Name string -//} -// -//func (c *Counter) Count(n int) { -// fmt.Printf("%s:%d|c\n", c.Name, n) -//} -// -//type Gauge struct { -// Name string -//} -// -//func (g *Gauge) Update(n int) { -// fmt.Printf("%s:%d|g\n", g.Name, n) -//} -// -//type Timer struct { -// Name string -//} -// -//func (t *Timer) Update(d time.Duration) { -// fmt.Printf("%s:%d|ms\n", t.Name, d/time.Millisecond) -//} diff --git a/metrics4/label/metrics.go b/metrics4/label/metrics.go deleted file mode 100644 index fd30bfcc0..000000000 --- a/metrics4/label/metrics.go +++ /dev/null @@ -1,56 +0,0 @@ -package label - -//import ( -// "fmt" -// "time" -// -// "github.com/fabiolb/fabio/metrics4" -// "github.com/fabiolb/fabio/metrics4/names" -//) -// -//type Provider struct{} -// -//func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { -// return &Counter{Name: name, Labels: labels} -//} -// -//func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { -// return &Gauge{Name: name, Labels: labels} -//} -// -//func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { -// return &Timer{Name: name, Labels: labels} -//} -// -//func (p *Provider) GetMetrics() []*metrics4.Metric { -// return make([]*metrics4.Metric, 0) -//} -// -//func (p *Provider) Unregister(interface{}) {} -// -//type Counter struct { -// Name string -// Labels []string -//} -// -//func (c *Counter) Count(n int) { -// fmt.Printf("%s:%d|c%s\n", c.Name, n, names.Labels(c.Labels, "|#", ":", ",")) -//} -// -//type Gauge struct { -// Name string -// Labels []string -//} -// -//func (g *Gauge) Update(n int) { -// fmt.Printf("%s:%d|g%s\n", g.Name, n, names.Labels(g.Labels, "|#", ":", ",")) -//} -// -//type Timer struct { -// Name string -// Labels []string -//} -// -//func (t *Timer) Update(d time.Duration) { -// fmt.Printf("%s:%d|ns%s\n", t.Name, d.Nanoseconds(), names.Labels(t.Labels, "|#", ":", ",")) -//} diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index cb04de1c7..61c9aefc6 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -196,15 +196,13 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { dur := end.Sub(start) if p.Requests != nil { - p.Requests.Observe(dur) + p.Requests.Observe(float64(dur.Nanoseconds()) / float64(time.Millisecond)) } if rw.code <= 0 { return } - http_status_timer := metrics.NewTimer("http_status", "code") - http_status_timer = http_status_timer.With("code", strconv.Itoa(rw.code)) - http_status_timer.Observe(dur) + metrics.NewTimer("http_status", "code").With("code", strconv.Itoa(rw.code)).Observe(float64(dur.Nanoseconds()) / float64(time.Millisecond)) // write access log if p.Logger != nil { From 3e1cfe499183263bda16f7b5b26e048f9c697c08 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Fri, 14 Dec 2018 12:33:02 +0300 Subject: [PATCH 17/30] Simplified Timer interface --- main.go | 4 ++-- metrics4/metrics.go | 10 ++++++---- metrics4/statsd/metrics.go | 30 +----------------------------- 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/main.go b/main.go index 88db0f9ff..da9646681 100644 --- a/main.go +++ b/main.go @@ -119,7 +119,7 @@ func main() { initRuntime(cfg) initBackend(cfg) - startAdmin(cfg, metrics) + startAdmin(cfg) go watchNoRouteHTML(cfg) @@ -228,7 +228,7 @@ func makeTLSConfig(l config.Listen) (*tls.Config, error) { return tlscfg, nil } -func startAdmin(cfg *config.Config, stats metrics4.Provider) { +func startAdmin(cfg *config.Config) { log.Printf("[INFO] Admin server access mode %q", cfg.UI.Access) log.Printf("[INFO] Admin server listening on %q", cfg.UI.Listen.Addr) go func() { diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 994ccc208..de557b696 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -10,10 +10,12 @@ type Counter metrics.Counter type Gauge metrics.Gauge -type Timer interface { - Observe(float64) - With(... string) Timer -} +type Timer = metrics.Histogram + +//type Timer interface { +// Observe(float64) +// With(... string) Timer +//} // Provider is an abstraction of a metrics backend. type Provider interface { diff --git a/metrics4/statsd/metrics.go b/metrics4/statsd/metrics.go index 0df0c6864..424db70dc 100644 --- a/metrics4/statsd/metrics.go +++ b/metrics4/statsd/metrics.go @@ -2,7 +2,6 @@ package statsd import ( "github.com/go-kit/kit/log" - "github.com/go-kit/kit/metrics" "time" "github.com/fabiolb/fabio/metrics4" @@ -33,32 +32,5 @@ func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { } func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { - return &Timer{ - timing: p.client.NewTiming(name, 1), - } -} - -type Timer struct { - timing *statsd.Timing - histogram metrics.Histogram -} - -func (t *Timer) Observe(value float64) { - if t.timing != nil { - t.timing.Observe(value) - } else if t.histogram != nil { - t.histogram.Observe(value) - } -} - -func (t *Timer) With(labelValues ... string) metrics4.Timer { - if t.timing != nil { - return &Timer{ - histogram: t.timing.With(labelValues...), - } - } else { - return &Timer{ - histogram: t.histogram.With(labelValues...), - } - } + return p.client.NewTiming(name, 1) } From eee0495a7759e98489a85d20daab5412d0c98e40 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Fri, 14 Dec 2018 12:43:12 +0300 Subject: [PATCH 18/30] Simplified Prometheus timer --- metrics4/metrics.go | 5 ----- metrics4/prometheus/metrics.go | 28 ++-------------------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/metrics4/metrics.go b/metrics4/metrics.go index de557b696..395d455cb 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -12,11 +12,6 @@ type Gauge metrics.Gauge type Timer = metrics.Histogram -//type Timer interface { -// Observe(float64) -// With(... string) Timer -//} - // Provider is an abstraction of a metrics backend. type Provider interface { // NewCounter creates a new counter object. diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index 3da0f4a2a..2438f0ceb 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -1,13 +1,10 @@ package prometheus import ( - "sync" - "time" - "github.com/fabiolb/fabio/metrics4" - "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus" + "sync" ) type Provider struct { @@ -55,32 +52,11 @@ func (p *Provider) NewTimer(name string, labels ... string) metrics4.Timer { p.mutex.Lock() defer p.mutex.Unlock() if p.timers[name] == nil { - h := prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ + p.timers[name] = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ Namespace: metrics4.FabioNamespace, Name: name, }, labels) - - p.timers[name] = &Timer{ - h, - time.Now(), - } } return p.timers[name] } - -type Timer struct { - histogram metrics.Histogram - start time.Time -} - -func (t *Timer) Observe(dur float64) { - t.histogram.Observe(dur) -} - -func (t *Timer) With(labelValues ... string) metrics4.Timer { - return &Timer{ - t.histogram.With(labelValues...), - t.start, - } -} From c6186e279bb721e29bda840cccf6a0026ee3f530 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Fri, 14 Dec 2018 13:35:23 +0300 Subject: [PATCH 19/30] Made Provider closable --- metrics4/metrics.go | 9 +++------ metrics4/prometheus/metrics.go | 6 +++++- metrics4/statsd/metrics.go | 7 +++++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 395d455cb..185223472 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -2,6 +2,7 @@ package metrics4 import ( "github.com/go-kit/kit/metrics" + "io" ) const FabioNamespace = "fabio" @@ -23,12 +24,8 @@ type Provider interface { // NewTimer creates a new timer object. NewTimer(name string, labels ... string) Timer - // Dispose() - - // Unregister removes a previously registered - // name or metric. Required for go-metrics and - // service pruning. - // Unregister(name string) + // It extends Provider with Close method which closes a disposable objects that are connected with a provider. + io.Closer } // MultiProvider wraps zero or more providers. diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index 2438f0ceb..01e4d2834 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -14,7 +14,7 @@ type Provider struct { mutex sync.Mutex } -func NewProvider() *Provider { +func NewProvider() metrics4.Provider { return &Provider{ counters: make(map[string]metrics4.Counter), gauges: make(map[string]metrics4.Gauge), @@ -60,3 +60,7 @@ func (p *Provider) NewTimer(name string, labels ... string) metrics4.Timer { return p.timers[name] } + +func (p *Provider) Close() error { + return nil +} diff --git a/metrics4/statsd/metrics.go b/metrics4/statsd/metrics.go index 424db70dc..1437c35b3 100644 --- a/metrics4/statsd/metrics.go +++ b/metrics4/statsd/metrics.go @@ -24,6 +24,7 @@ func NewProvider(addr string, interval time.Duration) (metrics4.Provider, error) } func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { + // TODO: Move 'sampleRate' of StatsD to Config return p.client.NewCounter(name, 1) } @@ -32,5 +33,11 @@ func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { } func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { + // TODO: Move 'sampleRate' of StatsD to Config return p.client.NewTiming(name, 1) } + +func (p *Provider) Close() error { + p.ticker.Stop() + return nil +} From fae8636745d4c2005b923e0890ab21a7c938e970 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Fri, 14 Dec 2018 18:03:38 +0300 Subject: [PATCH 20/30] Added tests for labling of StatsD metrics --- metrics4/metrics.go | 26 +++++-- metrics4/statsd/metrics.go | 132 +++++++++++++++++++++++++++++--- metrics4/statsd/metrics_test.go | 129 +++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+), 14 deletions(-) create mode 100644 metrics4/statsd/metrics_test.go diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 185223472..db60a66ac 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -7,22 +7,23 @@ import ( const FabioNamespace = "fabio" -type Counter metrics.Counter +type Counter = metrics.Counter -type Gauge metrics.Gauge +type Gauge = metrics.Gauge type Timer = metrics.Histogram // Provider is an abstraction of a metrics backend. type Provider interface { // NewCounter creates a new counter object. - NewCounter(name string, labels ... string) Counter + // labels - array of labels names + NewCounter(name string, labelsNames ... string) Counter // NewGauge creates a new gauge object. - NewGauge(name string, labels ... string) Gauge + NewGauge(name string, labelsNames ... string) Gauge // NewTimer creates a new timer object. - NewTimer(name string, labels ... string) Timer + NewTimer(name string, labelsNames ... string) Timer // It extends Provider with Close method which closes a disposable objects that are connected with a provider. io.Closer @@ -67,6 +68,21 @@ func (mp *MultiProvider) NewTimer(name string, labels ... string) Timer { return &MultiTimer{t} } +func (mp *MultiProvider) Close() error { + var errors []error + for _, p := range mp.p { + e := p.Close() + if e != nil { + errors = append(errors, e) + } + } + if len(errors) > 0 { + // TODO(max): Define MultiError + return errors[0] + } + return nil +} + // MultiCounter wraps zero or more counters. type MultiCounter struct { counters []Counter diff --git a/metrics4/statsd/metrics.go b/metrics4/statsd/metrics.go index 1437c35b3..33057f94b 100644 --- a/metrics4/statsd/metrics.go +++ b/metrics4/statsd/metrics.go @@ -2,6 +2,7 @@ package statsd import ( "github.com/go-kit/kit/log" + "strings" "time" "github.com/fabiolb/fabio/metrics4" @@ -14,7 +15,7 @@ type Provider struct { } func NewProvider(addr string, interval time.Duration) (metrics4.Provider, error) { - client := statsd.New(metrics4.FabioNamespace, log.NewNopLogger()) + client := statsd.New(metrics4.FabioNamespace + "_", log.NewNopLogger()) ticker := time.NewTicker(interval) @@ -23,21 +24,134 @@ func NewProvider(addr string, interval time.Duration) (metrics4.Provider, error) return &Provider{client, ticker}, nil } -func (p *Provider) NewCounter(name string, labels ...string) metrics4.Counter { - // TODO: Move 'sampleRate' of StatsD to Config - return p.client.NewCounter(name, 1) +func (p *Provider) NewCounter(name string, labelsNames ...string) metrics4.Counter { + var counter metrics4.Counter + if len(labelsNames) == 0 { + // TODO: Move 'sampleRate' of StatsD to Config + counter = p.client.NewCounter(name, 1) + } + return &Counter{ + c: counter, + m: &metric{ + p: p, + name: name, + labelsNames: labelsNames, + }, + } } -func (p *Provider) NewGauge(name string, labels ...string) metrics4.Gauge { - return p.client.NewGauge(name) +func (p *Provider) NewGauge(name string, labelsNames ...string) metrics4.Gauge { + var gauge metrics4.Gauge + if len(labelsNames) == 0 { + // TODO: Move 'sampleRate' of StatsD to Config + gauge = p.client.NewGauge(name) + } + return &Gauge{ + g: gauge, + m: &metric{ + p: p, + name: name, + labelsNames: labelsNames, + }, + } } -func (p *Provider) NewTimer(name string, labels ...string) metrics4.Timer { - // TODO: Move 'sampleRate' of StatsD to Config - return p.client.NewTiming(name, 1) +func (p *Provider) NewTimer(name string, labelsNames ...string) metrics4.Timer { + var timer metrics4.Timer + if len(labelsNames) == 0 { + // TODO: Move 'sampleRate' of StatsD to Config + timer = p.client.NewTiming(name, 1) + } + return &Timer{ + t: timer, + m: &metric{ + p: p, + name: name, + labelsNames: labelsNames, + }, + } } func (p *Provider) Close() error { p.ticker.Stop() return nil } + +//func parseLabelsValues(labelsNames []string, labels []string) ([]string, error) { +// labelsCount := len(labelsNames) +// labelsValues := make([]string, labelsCount) +// +// for i := 1; i <= labelsCount; i++ { +// if labelsNames[i - 1] != labelsNames[(i * 2) - 1] { +// return nil, errors.New("incorrect label name") +// } +// +// labelsValues[i] = labels[(i * 2) - 1] +// } +// +// return labelsValues, nil +//} + +func makeNameFromLabels(labels []string) string { + return strings.Join(labels, "_") +} + +type metric struct { + name string + labelsNames []string + p *Provider +} + +type Counter struct { + c metrics4.Counter + m *metric +} + +func (c *Counter) Add(delta float64) { + if c.c != nil { + c.c.Add(delta) + } +} + +func (c *Counter) With(labels ... string) metrics4.Counter { + // TODO(max): Check labels + return c.m.p.NewCounter(c.m.name + "_" + makeNameFromLabels(labels)) +} + +type Timer struct { + t metrics4.Timer + m *metric +} + +func (t *Timer) Observe(value float64) { + if t.t != nil { + t.t.Observe(value) + } +} + +func (t *Timer) With(labels ... string) metrics4.Timer { + // TODO(max): Check labels + return t.m.p.NewTimer(t.m.name + "_" + makeNameFromLabels(labels)) +} + +type Gauge struct { + g metrics4.Gauge + m *metric +} + +func (g *Gauge) Add(value float64) { + if g.g != nil { + g.g.Add(value) + } +} + +func (g *Gauge) Set(value float64) { + if g.g != nil { + g.g.Set(value) + } +} + +func (g *Gauge) With(labels ... string) metrics4.Gauge { + // TODO(max): Check labels + return g.m.p.NewGauge(g.m.name + "_" + makeNameFromLabels(labels)) +} diff --git a/metrics4/statsd/metrics_test.go b/metrics4/statsd/metrics_test.go new file mode 100644 index 000000000..a956c6796 --- /dev/null +++ b/metrics4/statsd/metrics_test.go @@ -0,0 +1,129 @@ +package statsd + +import ( + "net" + "testing" + "time" +) + +const addr = ":9876" + +// It shouldn't panic after creating several metrics with the same name +func TestIdenticalNamesForCounters(t *testing.T) { + metricName := "metric" + provider, err := NewProvider("addr", 5 * time.Second) + + if err != nil { + t.Error(err) + } + + counter := provider.NewCounter(metricName) + counter.Add(1) + counter = provider.NewCounter(metricName) + counter.Add(1) +} + +func TestLabeledCounters(t *testing.T) { + counterMessage := "fabio_counter_code_200:1.000000|c\n" + + provider, err := NewProvider(addr, time.Second) + if err != nil { + t.Fatal(err) + } + + l, err := net.ListenPacket("udp", addr) + if err != nil { + t.Fatal(err) + } + + go func() { + timer := time.NewTimer(5 * time.Second) + <-timer.C + t.Fatal("timeout") + }() + + defer l.Close() + + provider.NewCounter("counter", "code").With("code", "200").Add(1) + + buffer := make([]byte, len(counterMessage)) + + read, _, err := l.ReadFrom(buffer) + if err != nil { + t.Fatal(err) + } + + if msg := string(buffer[:read]); msg != counterMessage { + t.Fatalf("Unexpected message:\nGot:\t%s\nExpected:\t%s\n", msg, counterMessage) + } +} + +func TestLabeledTimers(t *testing.T) { + timerMessage := "fabio_timer_code_200:0.500000|ms" + + provider, err := NewProvider(addr, time.Second) + if err != nil { + t.Fatal(err) + } + + l, err := net.ListenPacket("udp", addr) + if err != nil { + t.Fatal(err) + } + + go func() { + timer := time.NewTimer(5 * time.Second) + <-timer.C + t.Fatal("timeout") + }() + + defer l.Close() + + provider.NewTimer("timer", "code").With("code", "200").Observe(0.5) + + buffer := make([]byte, len(timerMessage)) + + read, _, err := l.ReadFrom(buffer) + if err != nil { + t.Fatal(err) + } + + if msg := string(buffer[:read]); msg != timerMessage { + t.Fatalf("Unexpected message:\nGot:\t%s\nExpected:\t%s\n", msg, timerMessage) + } +} + +func TestLabeledGauges(t *testing.T) { + gaugeMessage := "fabio_gauge_code_200:5.000000|g" + + provider, err := NewProvider(addr, time.Second) + if err != nil { + t.Fatal(err) + } + + l, err := net.ListenPacket("udp", addr) + if err != nil { + t.Fatal(err) + } + + go func() { + timer := time.NewTimer(5 * time.Second) + <-timer.C + t.Fatal("timeout") + }() + + defer l.Close() + + provider.NewGauge("gauge", "code").With("code", "200").Add(5) + + buffer := make([]byte, len(gaugeMessage)) + + read, _, err := l.ReadFrom(buffer) + if err != nil { + t.Fatal(err) + } + + if msg := string(buffer[:read]); msg != gaugeMessage { + t.Fatalf("Unexpected message:\nGot:\t%s\nExpected:\t%s\n", msg, gaugeMessage) + } +} From e885ad5b7bc84f84464faabfe76efd11abe6cf68 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Fri, 14 Dec 2018 18:21:41 +0300 Subject: [PATCH 21/30] StatsD tests are refactored --- metrics4/statsd/metrics_test.go | 109 ++++++++++++++------------------ 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/metrics4/statsd/metrics_test.go b/metrics4/statsd/metrics_test.go index a956c6796..5bcf90aa2 100644 --- a/metrics4/statsd/metrics_test.go +++ b/metrics4/statsd/metrics_test.go @@ -1,6 +1,7 @@ package statsd import ( + "github.com/fabiolb/fabio/metrics4" "net" "testing" "time" @@ -23,107 +24,95 @@ func TestIdenticalNamesForCounters(t *testing.T) { counter.Add(1) } -func TestLabeledCounters(t *testing.T) { - counterMessage := "fabio_counter_code_200:1.000000|c\n" - +func createProvider(t *testing.T, addr string, interval time.Duration) metrics4.Provider { provider, err := NewProvider(addr, time.Second) if err != nil { t.Fatal(err) } + return provider +} - l, err := net.ListenPacket("udp", addr) +func createUdpConnection(t *testing.T, addr string) net.PacketConn { + conn, err := net.ListenPacket("udp", addr) if err != nil { t.Fatal(err) } + return conn +} +func readStringFromUdpConnection(t *testing.T, conn net.PacketConn, length int) string { + buffer := make([]byte, length) + read, _, err := conn.ReadFrom(buffer) + if err != nil { + t.Fatal(err) + } + return string(buffer[:read]) +} + +func startTimeout(t *testing.T, duration time.Duration) { go func() { - timer := time.NewTimer(5 * time.Second) + timer := time.NewTimer(duration) <-timer.C t.Fatal("timeout") }() +} + +func TestLabeledCounters(t *testing.T) { + counterMessage := "fabio_counter_code_200:1.000000|c\n" - defer l.Close() + provider := createProvider(t, addr, time.Second) + defer provider.Close() - provider.NewCounter("counter", "code").With("code", "200").Add(1) + conn := createUdpConnection(t, addr) + defer conn.Close() - buffer := make([]byte, len(counterMessage)) + startTimeout(t, 5 * time.Second) - read, _, err := l.ReadFrom(buffer) - if err != nil { - t.Fatal(err) - } + provider.NewCounter("counter", "code").With("code", "200").Add(1) + + message := readStringFromUdpConnection(t, conn, len(counterMessage)) - if msg := string(buffer[:read]); msg != counterMessage { - t.Fatalf("Unexpected message:\nGot:\t%s\nExpected:\t%s\n", msg, counterMessage) + if message != counterMessage { + t.Fatalf("Unexpected message:\nGot:\t%s\nExpected:\t%s\n", message, counterMessage) } } func TestLabeledTimers(t *testing.T) { timerMessage := "fabio_timer_code_200:0.500000|ms" - provider, err := NewProvider(addr, time.Second) - if err != nil { - t.Fatal(err) - } - - l, err := net.ListenPacket("udp", addr) - if err != nil { - t.Fatal(err) - } + provider := createProvider(t, addr, time.Second) + defer provider.Close() - go func() { - timer := time.NewTimer(5 * time.Second) - <-timer.C - t.Fatal("timeout") - }() + conn := createUdpConnection(t, addr) + defer conn.Close() - defer l.Close() + startTimeout(t, 5 * time.Second) provider.NewTimer("timer", "code").With("code", "200").Observe(0.5) - buffer := make([]byte, len(timerMessage)) + message := readStringFromUdpConnection(t, conn, len(timerMessage)) - read, _, err := l.ReadFrom(buffer) - if err != nil { - t.Fatal(err) - } - - if msg := string(buffer[:read]); msg != timerMessage { - t.Fatalf("Unexpected message:\nGot:\t%s\nExpected:\t%s\n", msg, timerMessage) + if message != timerMessage { + t.Fatalf("Unexpected message:\nGot:\t%s\nExpected:\t%s\n", message, timerMessage) } } func TestLabeledGauges(t *testing.T) { gaugeMessage := "fabio_gauge_code_200:5.000000|g" - provider, err := NewProvider(addr, time.Second) - if err != nil { - t.Fatal(err) - } - - l, err := net.ListenPacket("udp", addr) - if err != nil { - t.Fatal(err) - } + provider := createProvider(t, addr, time.Second) + defer provider.Close() - go func() { - timer := time.NewTimer(5 * time.Second) - <-timer.C - t.Fatal("timeout") - }() + conn := createUdpConnection(t, addr) + defer conn.Close() - defer l.Close() + startTimeout(t, 5 * time.Second) provider.NewGauge("gauge", "code").With("code", "200").Add(5) - buffer := make([]byte, len(gaugeMessage)) - - read, _, err := l.ReadFrom(buffer) - if err != nil { - t.Fatal(err) - } + message := readStringFromUdpConnection(t, conn, len(gaugeMessage)) - if msg := string(buffer[:read]); msg != gaugeMessage { - t.Fatalf("Unexpected message:\nGot:\t%s\nExpected:\t%s\n", msg, gaugeMessage) + if message != gaugeMessage { + t.Fatalf("Unexpected message:\nGot:\t%s\nExpected:\t%s\n", message, gaugeMessage) } } From ee1edf5eadef40039f9697de739986c4f99ef845 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Fri, 14 Dec 2018 19:13:43 +0300 Subject: [PATCH 22/30] Untagged metrics abstraction --- metrics4/statsd/metrics.go | 117 ++----------------------------------- metrics4/untagged.go | 96 ++++++++++++++++++++++++++++++ metrics4/untagged_test.go | 23 ++++++++ 3 files changed, 125 insertions(+), 111 deletions(-) create mode 100644 metrics4/untagged.go create mode 100644 metrics4/untagged_test.go diff --git a/metrics4/statsd/metrics.go b/metrics4/statsd/metrics.go index 33057f94b..7b3f2cdca 100644 --- a/metrics4/statsd/metrics.go +++ b/metrics4/statsd/metrics.go @@ -2,7 +2,6 @@ package statsd import ( "github.com/go-kit/kit/log" - "strings" "time" "github.com/fabiolb/fabio/metrics4" @@ -25,133 +24,29 @@ func NewProvider(addr string, interval time.Duration) (metrics4.Provider, error) } func (p *Provider) NewCounter(name string, labelsNames ...string) metrics4.Counter { - var counter metrics4.Counter if len(labelsNames) == 0 { // TODO: Move 'sampleRate' of StatsD to Config - counter = p.client.NewCounter(name, 1) - } - return &Counter{ - c: counter, - m: &metric{ - p: p, - name: name, - labelsNames: labelsNames, - }, + return p.client.NewCounter(name, 1) } + return metrics4.NewUntaggedCounter(p, name, labelsNames) } func (p *Provider) NewGauge(name string, labelsNames ...string) metrics4.Gauge { - var gauge metrics4.Gauge if len(labelsNames) == 0 { - // TODO: Move 'sampleRate' of StatsD to Config - gauge = p.client.NewGauge(name) - } - return &Gauge{ - g: gauge, - m: &metric{ - p: p, - name: name, - labelsNames: labelsNames, - }, + return p.client.NewGauge(name) } + return metrics4.NewUntaggedGauge(p, name, labelsNames) } func (p *Provider) NewTimer(name string, labelsNames ...string) metrics4.Timer { - var timer metrics4.Timer if len(labelsNames) == 0 { // TODO: Move 'sampleRate' of StatsD to Config - timer = p.client.NewTiming(name, 1) - } - return &Timer{ - t: timer, - m: &metric{ - p: p, - name: name, - labelsNames: labelsNames, - }, + return p.client.NewTiming(name, 1) } + return metrics4.NewUntaggedTimer(p, name, labelsNames) } func (p *Provider) Close() error { p.ticker.Stop() return nil } - -//func parseLabelsValues(labelsNames []string, labels []string) ([]string, error) { -// labelsCount := len(labelsNames) -// labelsValues := make([]string, labelsCount) -// -// for i := 1; i <= labelsCount; i++ { -// if labelsNames[i - 1] != labelsNames[(i * 2) - 1] { -// return nil, errors.New("incorrect label name") -// } -// -// labelsValues[i] = labels[(i * 2) - 1] -// } -// -// return labelsValues, nil -//} - -func makeNameFromLabels(labels []string) string { - return strings.Join(labels, "_") -} - -type metric struct { - name string - labelsNames []string - p *Provider -} - -type Counter struct { - c metrics4.Counter - m *metric -} - -func (c *Counter) Add(delta float64) { - if c.c != nil { - c.c.Add(delta) - } -} - -func (c *Counter) With(labels ... string) metrics4.Counter { - // TODO(max): Check labels - return c.m.p.NewCounter(c.m.name + "_" + makeNameFromLabels(labels)) -} - -type Timer struct { - t metrics4.Timer - m *metric -} - -func (t *Timer) Observe(value float64) { - if t.t != nil { - t.t.Observe(value) - } -} - -func (t *Timer) With(labels ... string) metrics4.Timer { - // TODO(max): Check labels - return t.m.p.NewTimer(t.m.name + "_" + makeNameFromLabels(labels)) -} - -type Gauge struct { - g metrics4.Gauge - m *metric -} - -func (g *Gauge) Add(value float64) { - if g.g != nil { - g.g.Add(value) - } -} - -func (g *Gauge) Set(value float64) { - if g.g != nil { - g.g.Set(value) - } -} - -func (g *Gauge) With(labels ... string) metrics4.Gauge { - // TODO(max): Check labels - return g.m.p.NewGauge(g.m.name + "_" + makeNameFromLabels(labels)) -} diff --git a/metrics4/untagged.go b/metrics4/untagged.go new file mode 100644 index 000000000..fcecf9058 --- /dev/null +++ b/metrics4/untagged.go @@ -0,0 +1,96 @@ +package metrics4 + +import ( + "errors" + "strings" +) + +// This module provides Counter, Gauge, Timer for metric tools which don't support tags (labels) + +func parseLabelsValues(labelsNames []string, labels []string) ([]string, error) { + labelsCount := len(labelsNames) + labelsValues := make([]string, labelsCount) + + for i := 0; i < labelsCount; i++ { + if labelsNames[i] != labels[(i * 2)] { + return nil, errors.New("incorrect label name") + } + + labelsValues[i] = labels[(i * 2) + 1] + } + + return labelsValues, nil +} + +func makeNameFromLabels(labelsNames []string, labels []string) string { + _, err := parseLabelsValues(labelsNames, labels) + if err != nil { + panic(err) + } + return strings.Join(labels, "_") +} + +type untaggedMetric struct { + p Provider + name string + labelsNames []string +} + +func newUntaggedMetric(p Provider, name string, labelsNames []string) *untaggedMetric { + return &untaggedMetric{ + p, + name, + labelsNames, + } +} + +type untaggedCounter struct { + m *untaggedMetric +} + +func (c *untaggedCounter) Add(delta float64) {} + +func (c *untaggedCounter) With(labels ... string) Counter { + return c.m.p.NewCounter(c.m.name + "_" + makeNameFromLabels(c.m.labelsNames, labels)) +} + +func NewUntaggedCounter(p Provider, name string, labelsNames []string) Counter { + return &untaggedCounter{ + newUntaggedMetric(p, name, labelsNames), + } +} + +type untaggedTimer struct { + m *untaggedMetric +} + +func (t *untaggedTimer) Observe(value float64) {} + +func (t *untaggedTimer) With(labels ... string) Timer { + return t.m.p.NewTimer(t.m.name + "_" + makeNameFromLabels(t.m.labelsNames, labels)) +} + +func NewUntaggedTimer(p Provider, name string, labelsNames []string) Timer { + return &untaggedTimer{ + newUntaggedMetric(p, name, labelsNames), + } +} + +type untaggedGauge struct { + m *untaggedMetric +} + +func (g *untaggedGauge) Add(value float64) {} + +func (g *untaggedGauge) Set(value float64) {} + +func (g *untaggedGauge) With(labels ... string) Gauge { + return g.m.p.NewGauge(g.m.name + "_" + makeNameFromLabels(g.m.labelsNames, labels)) +} + +func NewUntaggedGauge(p Provider, name string, labelsNames []string) Gauge { + return &untaggedGauge{ + newUntaggedMetric(p, name, labelsNames), + } +} + diff --git a/metrics4/untagged_test.go b/metrics4/untagged_test.go new file mode 100644 index 000000000..1a1c9a90e --- /dev/null +++ b/metrics4/untagged_test.go @@ -0,0 +1,23 @@ +package metrics4 + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestParseLabelsValues(t *testing.T) { + labelsNames := []string{ "a", "b" } + labels := []string { "a", "1", "b", "2" } + labelsValues, err := parseLabelsValues(labelsNames, labels) + if err != nil { + t.Fatal(err) + } + assert.EqualValues(t, []string{ "1", "2" }, labelsValues) +} + +func TestMakeNameFromLabels(t *testing.T) { + labelsNames := []string{ "a", "b" } + labels := []string { "a", "1", "b", "2" } + name := makeNameFromLabels(labelsNames, labels) + assert.EqualValues(t, "a_1_b_2", name) +} From f643d270c671a5bfe055a2ed9feab010d3ade7db Mon Sep 17 00:00:00 2001 From: maximka777 Date: Mon, 17 Dec 2018 16:23:20 +0300 Subject: [PATCH 23/30] Added Graphite and StdOut metric drivers --- admin/server.go | 4 +- config/config.go | 42 ++++++---- config/default.go | 28 +++++-- config/load.go | 21 ++++- main.go | 15 ++-- metrics4/gm/gm.go | 89 ++++++++++++++++++++++ metrics4/graphite/metrics.go | 29 +++++++ metrics4/graphite/metrics_test.go | 24 ++++++ metrics4/metrics.go | 2 + metrics4/statsd/metrics.go | 29 +++---- metrics4/stdout/metrics.go | 21 +++++ metrics4/untagged.go | 96 ----------------------- metrics4/untagged/untagged.go | 97 ++++++++++++++++++++++++ metrics4/{ => untagged}/untagged_test.go | 2 +- 14 files changed, 357 insertions(+), 142 deletions(-) create mode 100644 metrics4/gm/gm.go create mode 100644 metrics4/graphite/metrics.go create mode 100644 metrics4/graphite/metrics_test.go create mode 100644 metrics4/stdout/metrics.go delete mode 100644 metrics4/untagged.go create mode 100644 metrics4/untagged/untagged.go rename metrics4/{ => untagged}/untagged_test.go (97%) diff --git a/admin/server.go b/admin/server.go index 76da6224e..ff542b37a 100644 --- a/admin/server.go +++ b/admin/server.go @@ -77,8 +77,8 @@ func (s *Server) handler() http.Handler { } func initMetricsHandlers(mux *http.ServeMux, s *Server) { - if strings.Contains(s.Cfg.Metrics.Target, "prometheus") && s.Cfg.Metrics.PrometheusMetricsEndpoint != "" { - mux.HandleFunc(s.Cfg.Metrics.PrometheusMetricsEndpoint, promhttp.Handler().ServeHTTP) + if strings.Contains(s.Cfg.Metrics.Target, "prometheus") && s.Cfg.Metrics.Prometheus.MetricsEndpoint != "" { + mux.HandleFunc(s.Cfg.Metrics.Prometheus.MetricsEndpoint, promhttp.Handler().ServeHTTP) } } diff --git a/config/config.go b/config/config.go index 83cd34af8..34ba41758 100644 --- a/config/config.go +++ b/config/config.go @@ -96,20 +96,36 @@ type Log struct { type Metrics struct { Target string - // TODO (max): Remove ? - Prefix string - // TODO (max): Remove ? - Names string + //Prefix string + //Names string + //Interval time.Duration + //Timeout time.Duration + //Retry time.Duration + Prometheus Prometheus + StatsD StatsD + StdOut StdOut + Graphite Graphite + //Circonus Circonus +} + +type Graphite struct { + Addr string + Interval time.Duration +} + +type StatsD struct { + Addr string + Protocol string + Interval time.Duration + SampleRate float64 +} + +type Prometheus struct { + MetricsEndpoint string +} + +type StdOut struct { Interval time.Duration - // TODO (max): Remove ? - Timeout time.Duration - // TODO (max): Remove ? - Retry time.Duration - PrometheusMetricsEndpoint string - StatsDAddr string - GraphiteAddr string - // TODO (max): Remove ? - Circonus Circonus } type Registry struct { diff --git a/config/default.go b/config/default.go index 45f68424d..aa13ab12d 100644 --- a/config/default.go +++ b/config/default.go @@ -26,14 +26,28 @@ var defaultConfig = &Config{ Level: "INFO", }, Metrics: Metrics{ - Prefix: "{{clean .Hostname}}.{{clean .Exec}}", - Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", - Interval: 30 * time.Second, - Timeout: 10 * time.Second, - Retry: 500 * time.Millisecond, - Circonus: Circonus{ - APIApp: "fabio", + Prometheus: Prometheus{ + MetricsEndpoint: "/metrics/prometheus", }, + StatsD: StatsD{ + Protocol: "udp", + Interval: 30 * time.Second, + SampleRate: 1, + }, + StdOut: StdOut{ + Interval: 30 * time.Second, + }, + Graphite: Graphite{ + Interval: 30 * time.Second, + }, + //Prefix: "{{clean .Hostname}}.{{clean .Exec}}", + //Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", + //Interval: 30 * time.Second, + //Timeout: 10 * time.Second, + //Retry: 500 * time.Millisecond, + //Circonus: Circonus{ + // APIApp: "fabio", + //}, }, Proxy: Proxy{ MaxConn: 10000, diff --git a/config/load.go b/config/load.go index 18ec01cc3..a3972bf2a 100644 --- a/config/load.go +++ b/config/load.go @@ -143,20 +143,33 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c f.StringVar(&cfg.Log.AccessTarget, "log.access.target", defaultConfig.Log.AccessTarget, "access log target") f.StringVar(&cfg.Log.RoutesFormat, "log.routes.format", defaultConfig.Log.RoutesFormat, "log format of routing table updates") f.StringVar(&cfg.Log.Level, "log.level", defaultConfig.Log.Level, "log level: TRACE, DEBUG, INFO, WARN, ERROR, FATAL") + f.StringVar(&cfg.Metrics.Target, "metrics.target", defaultConfig.Metrics.Target, "metrics backend") + //f.StringVar(&cfg.Metrics.Prefix, "metrics.prefix", defaultConfig.Metrics.Prefix, "prefix for reported metrics") //f.StringVar(&cfg.Metrics.Names, "metrics.names", defaultConfig.Metrics.Names, "route metric name template") - f.DurationVar(&cfg.Metrics.Interval, "metrics.interval", defaultConfig.Metrics.Interval, "metrics reporting interval") + //f.DurationVar(&cfg.Metrics.Interval, "metrics.interval", defaultConfig.Metrics.Interval, "metrics reporting interval") //f.DurationVar(&cfg.Metrics.Timeout, "metrics.timeout", defaultConfig.Metrics.Timeout, "timeout for metrics to become available") //f.DurationVar(&cfg.Metrics.Retry, "metrics.retry", defaultConfig.Metrics.Retry, "retry interval during startup") - f.StringVar(&cfg.Metrics.PrometheusMetricsEndpoint, "metrics.prometheus.endpoint", defaultConfig.Metrics.PrometheusMetricsEndpoint, "Metrics endpoint for Prometheus") - f.StringVar(&cfg.Metrics.GraphiteAddr, "metrics.graphite.addr", defaultConfig.Metrics.GraphiteAddr, "graphite server address") - f.StringVar(&cfg.Metrics.StatsDAddr, "metrics.statsd.addr", defaultConfig.Metrics.StatsDAddr, "statsd server address") + + f.StringVar(&cfg.Metrics.Prometheus.MetricsEndpoint, "metrics.prometheus.endpoint", defaultConfig.Metrics.Prometheus.MetricsEndpoint, "metrics endpoint for Prometheus") + + f.StringVar(&cfg.Metrics.Graphite.Addr, "metrics.graphite.addr", defaultConfig.Metrics.Graphite.Addr, "graphite server address") + f.DurationVar(&cfg.Metrics.Graphite.Interval, "metrics.graphite.interval", defaultConfig.Metrics.Graphite.Interval, "graphite sending interval") + + f.DurationVar(&cfg.Metrics.StdOut.Interval, "metrics.stdout.interval", defaultConfig.Metrics.StdOut.Interval, "stdout logging interval") + + f.StringVar(&cfg.Metrics.StatsD.Addr, "metrics.statsd.addr", defaultConfig.Metrics.StatsD.Addr, "statsd server address") + f.StringVar(&cfg.Metrics.StatsD.Protocol, "metrics.statsd.protocol", defaultConfig.Metrics.StatsD.Protocol, "statsd server protocol") + f.DurationVar(&cfg.Metrics.StatsD.Interval, "metrics.statsd.interval", defaultConfig.Metrics.StatsD.Interval, "statsd sending interval") + f.Float64Var(&cfg.Metrics.StatsD.SampleRate, "metrics.statsd.sampleRate", defaultConfig.Metrics.StatsD.SampleRate, "statsd sample rate") + //f.StringVar(&cfg.Metrics.Circonus.APIKey, "metrics.circonus.apikey", defaultConfig.Metrics.Circonus.APIKey, "Circonus API token key") //f.StringVar(&cfg.Metrics.Circonus.APIApp, "metrics.circonus.apiapp", defaultConfig.Metrics.Circonus.APIApp, "Circonus API token app") //f.StringVar(&cfg.Metrics.Circonus.APIURL, "metrics.circonus.apiurl", defaultConfig.Metrics.Circonus.APIURL, "Circonus API URL") //f.StringVar(&cfg.Metrics.Circonus.BrokerID, "metrics.circonus.brokerid", defaultConfig.Metrics.Circonus.BrokerID, "Circonus Broker ID") //f.StringVar(&cfg.Metrics.Circonus.CheckID, "metrics.circonus.checkid", defaultConfig.Metrics.Circonus.CheckID, "Circonus Check ID") + f.StringVar(&cfg.Registry.Backend, "registry.backend", defaultConfig.Registry.Backend, "registry backend") f.DurationVar(&cfg.Registry.Timeout, "registry.timeout", defaultConfig.Registry.Timeout, "timeout for registry to become available") f.DurationVar(&cfg.Registry.Retry, "registry.retry", defaultConfig.Registry.Retry, "retry interval during startup") diff --git a/main.go b/main.go index da9646681..46588d15f 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/json" "fmt" + "github.com/fabiolb/fabio/metrics4/graphite" "io" "log" "net" @@ -316,12 +317,16 @@ func initMetrics(cfg *config.Config) metrics4.Provider { switch x { case "prometheus": p = append(p, prometheus.NewProvider()) - //case "flat": - // p = append(p, &flat.Provider{}) - //case "label": - // p = append(p, &label.Provider{}) + case "graphite": + provider, err := graphite.NewProvider(cfg.Metrics.Graphite) + + if err != nil { + exit.Fatalf("[FATAL] Cannot initialize statsd metrics: %s", err) + } + + p = append(p, provider) case "statsd": - provider, err := statsd.NewProvider(cfg.Metrics.StatsDAddr, cfg.Metrics.Interval) + provider, err := statsd.NewProvider(cfg.Metrics.StatsD) if err != nil { exit.Fatalf("[FATAL] Cannot initialize statsd metrics: %s", err) diff --git a/metrics4/gm/gm.go b/metrics4/gm/gm.go new file mode 100644 index 000000000..349f5d16c --- /dev/null +++ b/metrics4/gm/gm.go @@ -0,0 +1,89 @@ +package gm + +import ( + "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/untagged" + gm "github.com/rcrowley/go-metrics" + "time" +) + +type provider struct { + r gm.Registry +} + +func (p *provider) NewCounter(name string, labelsNames ...string) metrics4.Counter { + if len(labelsNames) == 0 { + return &counter{gm.GetOrRegisterCounter(name, p.r)} + } + return untagged.NewCounter(p, name, labelsNames) +} + +func (p *provider) NewGauge(name string, labelsNames ...string) metrics4.Gauge { + if len(labelsNames) == 0 { + return &gauge{gm.GetOrRegisterGaugeFloat64(name, p.r)} + } + return untagged.NewGauge(p, name, labelsNames) +} + +func (p *provider) NewTimer(name string, labelsNames ...string) metrics4.Timer { + if len(labelsNames) == 0 { + return &timer{gm.GetOrRegisterTimer(name, p.r)} + } + return untagged.NewTimer(p, name, labelsNames) +} + +func (p *provider) Close() error { + return nil +} + +func NewProvider(r gm.Registry) metrics4.Provider { + return &provider{r} +} + +type counter struct { + c gm.Counter +} + +func (c *counter) Add(value float64) { + c.c.Inc(int64(value)) +} + +func (c *counter) With(labels ... string) metrics4.Counter { + return c +} + +type gauge struct { + g gm.GaugeFloat64 +} + +func (g *gauge) Add(value float64) { + g.g.Update(g.g.Value() + value) +} + +func (g *gauge) Set(value float64) { + g.g.Update(value) +} + +func (g *gauge) With(labels ... string) metrics4.Gauge { + return g +} + +func NewGauge(g gm.GaugeFloat64) metrics4.Gauge { + return &gauge{g} +} + +type timer struct { + t gm.Timer +} + +func (t *timer) Observe(value float64) { + t.t.Update(time.Duration(value)) +} + +func (t *timer) With(labels ... string) metrics4.Timer { + return t +} + +func NewTimer(t gm.Timer) metrics4.Timer { + return &timer{t} +} diff --git a/metrics4/graphite/metrics.go b/metrics4/graphite/metrics.go new file mode 100644 index 000000000..c74a19f46 --- /dev/null +++ b/metrics4/graphite/metrics.go @@ -0,0 +1,29 @@ +package graphite + +import ( + "errors" + "fmt" + "github.com/cyberdelia/go-metrics-graphite" + "github.com/fabiolb/fabio/config" + "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/gm" + rcgm "github.com/rcrowley/go-metrics" + "net" +) + +func NewProvider(cfg config.Graphite) (metrics4.Provider, error) { + if cfg.Addr == "" { + return nil, errors.New(" graphite addr missing") + } + + a, err := net.ResolveTCPAddr("tcp", cfg.Addr) + if err != nil { + return nil, fmt.Errorf(" cannot connect to Graphite: %s", err) + } + + registry := rcgm.NewRegistry() + + go graphite.Graphite(registry, cfg.Interval, metrics4.FabioNamespace + "_", a) + + return gm.NewProvider(registry), nil +} \ No newline at end of file diff --git a/metrics4/graphite/metrics_test.go b/metrics4/graphite/metrics_test.go new file mode 100644 index 000000000..ce94e8659 --- /dev/null +++ b/metrics4/graphite/metrics_test.go @@ -0,0 +1,24 @@ +package graphite + +import ( + "github.com/fabiolb/fabio/config" + "testing" + "time" +) + +const addr = ":9876" + +// It shouldn't panic after creating several metrics with the same name +func TestIdenticalNamesForCounters(t *testing.T) { + metricName := "metric" + provider, err := NewProvider(config.Graphite{Interval: 1 * time.Second}) + + if err != nil { + t.Error(err) + } + + counter := provider.NewCounter(metricName) + counter.Add(1) + counter = provider.NewCounter(metricName) + counter.Add(1) +} diff --git a/metrics4/metrics.go b/metrics4/metrics.go index db60a66ac..9b87372ba 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -6,6 +6,8 @@ import ( ) const FabioNamespace = "fabio" +const DotDelimiter = "." +const UnderscoreDelimiter = "_" type Counter = metrics.Counter diff --git a/metrics4/statsd/metrics.go b/metrics4/statsd/metrics.go index 7b3f2cdca..2ff45c87c 100644 --- a/metrics4/statsd/metrics.go +++ b/metrics4/statsd/metrics.go @@ -1,6 +1,8 @@ package statsd import ( + "github.com/fabiolb/fabio/config" + "github.com/fabiolb/fabio/metrics4/untagged" "github.com/go-kit/kit/log" "time" @@ -9,41 +11,40 @@ import ( ) type Provider struct { - client *statsd.Statsd - ticker *time.Ticker + client *statsd.Statsd + ticker *time.Ticker + sampleRate float64 } -func NewProvider(addr string, interval time.Duration) (metrics4.Provider, error) { - client := statsd.New(metrics4.FabioNamespace + "_", log.NewNopLogger()) +func NewProvider(cfg config.StatsD) (metrics4.Provider, error) { + client := statsd.New(metrics4.FabioNamespace+"_", log.NewNopLogger()) - ticker := time.NewTicker(interval) + ticker := time.NewTicker(cfg.Interval) - go client.SendLoop(ticker.C, "udp", addr) + go client.SendLoop(ticker.C, cfg.Protocol, cfg.Addr) - return &Provider{client, ticker}, nil + return &Provider{client, ticker, cfg.SampleRate}, nil } func (p *Provider) NewCounter(name string, labelsNames ...string) metrics4.Counter { if len(labelsNames) == 0 { - // TODO: Move 'sampleRate' of StatsD to Config - return p.client.NewCounter(name, 1) + return p.client.NewCounter(name, p.sampleRate) } - return metrics4.NewUntaggedCounter(p, name, labelsNames) + return untagged.NewCounter(p, name, labelsNames) } func (p *Provider) NewGauge(name string, labelsNames ...string) metrics4.Gauge { if len(labelsNames) == 0 { return p.client.NewGauge(name) } - return metrics4.NewUntaggedGauge(p, name, labelsNames) + return untagged.NewGauge(p, name, labelsNames) } func (p *Provider) NewTimer(name string, labelsNames ...string) metrics4.Timer { if len(labelsNames) == 0 { - // TODO: Move 'sampleRate' of StatsD to Config - return p.client.NewTiming(name, 1) + return p.client.NewTiming(name, p.sampleRate) } - return metrics4.NewUntaggedTimer(p, name, labelsNames) + return untagged.NewTimer(p, name, labelsNames) } func (p *Provider) Close() error { diff --git a/metrics4/stdout/metrics.go b/metrics4/stdout/metrics.go new file mode 100644 index 000000000..6a2df9690 --- /dev/null +++ b/metrics4/stdout/metrics.go @@ -0,0 +1,21 @@ +package stdout + +import ( + "github.com/fabiolb/fabio/config" + "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/gm" + rcgm "github.com/rcrowley/go-metrics" + "log" + "os" +) + +func NewProvider(cfg config.StdOut) (metrics4.Provider, error) { + logger := log.New(os.Stdout, "localhost: ", log.Lmicroseconds) + + r := rcgm.NewRegistry() + + go rcgm.Log(r, cfg.Interval, logger) + + return gm.NewProvider(r), nil +} + diff --git a/metrics4/untagged.go b/metrics4/untagged.go deleted file mode 100644 index fcecf9058..000000000 --- a/metrics4/untagged.go +++ /dev/null @@ -1,96 +0,0 @@ -package metrics4 - -import ( - "errors" - "strings" -) - -// This module provides Counter, Gauge, Timer for metric tools which don't support tags (labels) - -func parseLabelsValues(labelsNames []string, labels []string) ([]string, error) { - labelsCount := len(labelsNames) - labelsValues := make([]string, labelsCount) - - for i := 0; i < labelsCount; i++ { - if labelsNames[i] != labels[(i * 2)] { - return nil, errors.New("incorrect label name") - } - - labelsValues[i] = labels[(i * 2) + 1] - } - - return labelsValues, nil -} - -func makeNameFromLabels(labelsNames []string, labels []string) string { - _, err := parseLabelsValues(labelsNames, labels) - if err != nil { - panic(err) - } - return strings.Join(labels, "_") -} - -type untaggedMetric struct { - p Provider - name string - labelsNames []string -} - -func newUntaggedMetric(p Provider, name string, labelsNames []string) *untaggedMetric { - return &untaggedMetric{ - p, - name, - labelsNames, - } -} - -type untaggedCounter struct { - m *untaggedMetric -} - -func (c *untaggedCounter) Add(delta float64) {} - -func (c *untaggedCounter) With(labels ... string) Counter { - return c.m.p.NewCounter(c.m.name + "_" + makeNameFromLabels(c.m.labelsNames, labels)) -} - -func NewUntaggedCounter(p Provider, name string, labelsNames []string) Counter { - return &untaggedCounter{ - newUntaggedMetric(p, name, labelsNames), - } -} - -type untaggedTimer struct { - m *untaggedMetric -} - -func (t *untaggedTimer) Observe(value float64) {} - -func (t *untaggedTimer) With(labels ... string) Timer { - return t.m.p.NewTimer(t.m.name + "_" + makeNameFromLabels(t.m.labelsNames, labels)) -} - -func NewUntaggedTimer(p Provider, name string, labelsNames []string) Timer { - return &untaggedTimer{ - newUntaggedMetric(p, name, labelsNames), - } -} - -type untaggedGauge struct { - m *untaggedMetric -} - -func (g *untaggedGauge) Add(value float64) {} - -func (g *untaggedGauge) Set(value float64) {} - -func (g *untaggedGauge) With(labels ... string) Gauge { - return g.m.p.NewGauge(g.m.name + "_" + makeNameFromLabels(g.m.labelsNames, labels)) -} - -func NewUntaggedGauge(p Provider, name string, labelsNames []string) Gauge { - return &untaggedGauge{ - newUntaggedMetric(p, name, labelsNames), - } -} - diff --git a/metrics4/untagged/untagged.go b/metrics4/untagged/untagged.go new file mode 100644 index 000000000..eff77a9d2 --- /dev/null +++ b/metrics4/untagged/untagged.go @@ -0,0 +1,97 @@ +package untagged + +import ( + "errors" + "github.com/fabiolb/fabio/metrics4" + "strings" +) + +// This module provides Counter, Gauge, Timer for metric tools which don't support tags (labels) + +func parseLabelsValues(labelsNames []string, labels []string) ([]string, error) { + labelsCount := len(labelsNames) + labelsValues := make([]string, labelsCount) + + for i := 0; i < labelsCount; i++ { + if labelsNames[i] != labels[(i * 2)] { + return nil, errors.New("incorrect label name") + } + + labelsValues[i] = labels[(i * 2) + 1] + } + + return labelsValues, nil +} + +func makeNameFromLabels(labelsNames []string, labels []string) string { + _, err := parseLabelsValues(labelsNames, labels) + if err != nil { + panic(err) + } + return strings.Join(labels, metrics4.DotDelimiter) +} + +type metric struct { + p metrics4.Provider + name string + labelsNames []string +} + +func newMetric(p metrics4.Provider, name string, labelsNames []string) *metric { + return &metric{ + p, + name, + labelsNames, + } +} + +type counter struct { + m *metric +} + +func (c *counter) Add(delta float64) {} + +func (c *counter) With(labels ... string) metrics4.Counter { + return c.m.p.NewCounter(c.m.name + "_" + makeNameFromLabels(c.m.labelsNames, labels)) +} + +func NewCounter(p metrics4.Provider, name string, labelsNames []string) metrics4.Counter { + return &counter{ + newMetric(p, name, labelsNames), + } +} + +type timer struct { + m *metric +} + +func (t *timer) Observe(value float64) {} + +func (t *timer) With(labels ... string) metrics4.Timer { + return t.m.p.NewTimer(t.m.name + "_" + makeNameFromLabels(t.m.labelsNames, labels)) +} + +func NewTimer(p metrics4.Provider, name string, labelsNames []string) metrics4.Timer { + return &timer{ + newMetric(p, name, labelsNames), + } +} + +type gauge struct { + m *metric +} + +func (g *gauge) Add(value float64) {} + +func (g *gauge) Set(value float64) {} + +func (g *gauge) With(labels ... string) metrics4.Gauge { + return g.m.p.NewGauge(g.m.name + "_" + makeNameFromLabels(g.m.labelsNames, labels)) +} + +func NewGauge(p metrics4.Provider, name string, labelsNames []string) metrics4.Gauge { + return &gauge{ + newMetric(p, name, labelsNames), + } +} + diff --git a/metrics4/untagged_test.go b/metrics4/untagged/untagged_test.go similarity index 97% rename from metrics4/untagged_test.go rename to metrics4/untagged/untagged_test.go index 1a1c9a90e..baf474875 100644 --- a/metrics4/untagged_test.go +++ b/metrics4/untagged/untagged_test.go @@ -1,4 +1,4 @@ -package metrics4 +package untagged import ( "github.com/stretchr/testify/assert" From b2c7c635397ad65537d1373c79c3d01b693fc4bd Mon Sep 17 00:00:00 2001 From: maximka777 Date: Mon, 17 Dec 2018 16:51:49 +0300 Subject: [PATCH 24/30] Fixed naming through dot --- config/config.go | 1 - config/default.go | 1 - config/load.go | 3 +-- main.go | 2 +- metrics4/graphite/metrics.go | 2 +- metrics4/prometheus/metrics.go | 8 ++++++++ metrics4/statsd/metrics.go | 4 ++-- metrics4/untagged/untagged.go | 6 +++--- proxy/http_proxy.go | 2 +- 9 files changed, 17 insertions(+), 12 deletions(-) diff --git a/config/config.go b/config/config.go index 34ba41758..0febc2b32 100644 --- a/config/config.go +++ b/config/config.go @@ -115,7 +115,6 @@ type Graphite struct { type StatsD struct { Addr string - Protocol string Interval time.Duration SampleRate float64 } diff --git a/config/default.go b/config/default.go index aa13ab12d..c4e0d762b 100644 --- a/config/default.go +++ b/config/default.go @@ -30,7 +30,6 @@ var defaultConfig = &Config{ MetricsEndpoint: "/metrics/prometheus", }, StatsD: StatsD{ - Protocol: "udp", Interval: 30 * time.Second, SampleRate: 1, }, diff --git a/config/load.go b/config/load.go index a3972bf2a..06c50d989 100644 --- a/config/load.go +++ b/config/load.go @@ -154,13 +154,12 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c f.StringVar(&cfg.Metrics.Prometheus.MetricsEndpoint, "metrics.prometheus.endpoint", defaultConfig.Metrics.Prometheus.MetricsEndpoint, "metrics endpoint for Prometheus") - f.StringVar(&cfg.Metrics.Graphite.Addr, "metrics.graphite.addr", defaultConfig.Metrics.Graphite.Addr, "graphite server address") + f.StringVar(&cfg.Metrics.Graphite.Addr, "metrics.graphite.addr", defaultConfig.Metrics.Graphite.Addr, "graphite carbon receiver (plaintext) address") f.DurationVar(&cfg.Metrics.Graphite.Interval, "metrics.graphite.interval", defaultConfig.Metrics.Graphite.Interval, "graphite sending interval") f.DurationVar(&cfg.Metrics.StdOut.Interval, "metrics.stdout.interval", defaultConfig.Metrics.StdOut.Interval, "stdout logging interval") f.StringVar(&cfg.Metrics.StatsD.Addr, "metrics.statsd.addr", defaultConfig.Metrics.StatsD.Addr, "statsd server address") - f.StringVar(&cfg.Metrics.StatsD.Protocol, "metrics.statsd.protocol", defaultConfig.Metrics.StatsD.Protocol, "statsd server protocol") f.DurationVar(&cfg.Metrics.StatsD.Interval, "metrics.statsd.interval", defaultConfig.Metrics.StatsD.Interval, "statsd sending interval") f.Float64Var(&cfg.Metrics.StatsD.SampleRate, "metrics.statsd.sampleRate", defaultConfig.Metrics.StatsD.SampleRate, "statsd sample rate") diff --git a/main.go b/main.go index 46588d15f..371d1d2a4 100644 --- a/main.go +++ b/main.go @@ -196,7 +196,7 @@ func newHTTPProxy(cfg *config.Config, stats metrics4.Provider) *proxy.HTTPProxy }, Requests: stats.NewTimer("requests"), Noroute: stats.NewCounter("notfound"), - WSConn: stats.NewGauge("wsconn"), + WSConn: stats.NewGauge("ws.conn"), Metrics: stats, Logger: l, } diff --git a/metrics4/graphite/metrics.go b/metrics4/graphite/metrics.go index c74a19f46..fdba7ae4e 100644 --- a/metrics4/graphite/metrics.go +++ b/metrics4/graphite/metrics.go @@ -23,7 +23,7 @@ func NewProvider(cfg config.Graphite) (metrics4.Provider, error) { registry := rcgm.NewRegistry() - go graphite.Graphite(registry, cfg.Interval, metrics4.FabioNamespace + "_", a) + go graphite.Graphite(registry, cfg.Interval, metrics4.FabioNamespace, a) return gm.NewProvider(registry), nil } \ No newline at end of file diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index 01e4d2834..e1b4e7602 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -4,6 +4,7 @@ import ( "github.com/fabiolb/fabio/metrics4" "github.com/go-kit/kit/metrics/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus" + "strings" "sync" ) @@ -14,6 +15,10 @@ type Provider struct { mutex sync.Mutex } +func normalizeName(name string) string { + return strings.Replace(name, ".", "_", -1) +} + func NewProvider() metrics4.Provider { return &Provider{ counters: make(map[string]metrics4.Counter), @@ -23,6 +28,7 @@ func NewProvider() metrics4.Provider { } func (p *Provider) NewCounter(name string, labels ... string) metrics4.Counter { + name = normalizeName(name) p.mutex.Lock() defer p.mutex.Unlock() if p.counters[name] == nil { @@ -36,6 +42,7 @@ func (p *Provider) NewCounter(name string, labels ... string) metrics4.Counter { } func (p *Provider) NewGauge(name string, labels ... string) metrics4.Gauge { + name = normalizeName(name) p.mutex.Lock() defer p.mutex.Unlock() if p.gauges[name] == nil { @@ -49,6 +56,7 @@ func (p *Provider) NewGauge(name string, labels ... string) metrics4.Gauge { } func (p *Provider) NewTimer(name string, labels ... string) metrics4.Timer { + name = normalizeName(name) p.mutex.Lock() defer p.mutex.Unlock() if p.timers[name] == nil { diff --git a/metrics4/statsd/metrics.go b/metrics4/statsd/metrics.go index 2ff45c87c..4e5be3c1f 100644 --- a/metrics4/statsd/metrics.go +++ b/metrics4/statsd/metrics.go @@ -17,11 +17,11 @@ type Provider struct { } func NewProvider(cfg config.StatsD) (metrics4.Provider, error) { - client := statsd.New(metrics4.FabioNamespace+"_", log.NewNopLogger()) + client := statsd.New(metrics4.FabioNamespace + ".", log.NewNopLogger()) ticker := time.NewTicker(cfg.Interval) - go client.SendLoop(ticker.C, cfg.Protocol, cfg.Addr) + go client.SendLoop(ticker.C, "udp", cfg.Addr) return &Provider{client, ticker, cfg.SampleRate}, nil } diff --git a/metrics4/untagged/untagged.go b/metrics4/untagged/untagged.go index eff77a9d2..35f1f850b 100644 --- a/metrics4/untagged/untagged.go +++ b/metrics4/untagged/untagged.go @@ -52,7 +52,7 @@ type counter struct { func (c *counter) Add(delta float64) {} func (c *counter) With(labels ... string) metrics4.Counter { - return c.m.p.NewCounter(c.m.name + "_" + makeNameFromLabels(c.m.labelsNames, labels)) + return c.m.p.NewCounter(c.m.name + metrics4.DotDelimiter + makeNameFromLabels(c.m.labelsNames, labels)) } func NewCounter(p metrics4.Provider, name string, labelsNames []string) metrics4.Counter { @@ -68,7 +68,7 @@ type timer struct { func (t *timer) Observe(value float64) {} func (t *timer) With(labels ... string) metrics4.Timer { - return t.m.p.NewTimer(t.m.name + "_" + makeNameFromLabels(t.m.labelsNames, labels)) + return t.m.p.NewTimer(t.m.name + metrics4.DotDelimiter + makeNameFromLabels(t.m.labelsNames, labels)) } func NewTimer(p metrics4.Provider, name string, labelsNames []string) metrics4.Timer { @@ -86,7 +86,7 @@ func (g *gauge) Add(value float64) {} func (g *gauge) Set(value float64) {} func (g *gauge) With(labels ... string) metrics4.Gauge { - return g.m.p.NewGauge(g.m.name + "_" + makeNameFromLabels(g.m.labelsNames, labels)) + return g.m.p.NewGauge(g.m.name + metrics4.DotDelimiter + makeNameFromLabels(g.m.labelsNames, labels)) } func NewGauge(p metrics4.Provider, name string, labelsNames []string) metrics4.Gauge { diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go index 61c9aefc6..6c00f9598 100644 --- a/proxy/http_proxy.go +++ b/proxy/http_proxy.go @@ -202,7 +202,7 @@ func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - metrics.NewTimer("http_status", "code").With("code", strconv.Itoa(rw.code)).Observe(float64(dur.Nanoseconds()) / float64(time.Millisecond)) + metrics.NewTimer("http.status", "code").With("code", strconv.Itoa(rw.code)).Observe(float64(dur.Nanoseconds()) / float64(time.Millisecond)) // write access log if p.Logger != nil { From 0c915ea08b762d2f136cb4b1df0f3ceefb5f2bb1 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Mon, 17 Dec 2018 17:03:58 +0300 Subject: [PATCH 25/30] Added stdout to initMetrics --- main.go | 13 +++++-------- metrics4/statsd/metrics.go | 4 ++-- metrics4/stdout/metrics.go | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index 371d1d2a4..390201cc7 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "github.com/fabiolb/fabio/metrics4/graphite" + "github.com/fabiolb/fabio/metrics4/stdout" "io" "log" "net" @@ -321,18 +322,14 @@ func initMetrics(cfg *config.Config) metrics4.Provider { provider, err := graphite.NewProvider(cfg.Metrics.Graphite) if err != nil { - exit.Fatalf("[FATAL] Cannot initialize statsd metrics: %s", err) + exit.Fatalf("[FATAL] Cannot initialize graphite metrics: %s", err) } p = append(p, provider) case "statsd": - provider, err := statsd.NewProvider(cfg.Metrics.StatsD) - - if err != nil { - exit.Fatalf("[FATAL] Cannot initialize statsd metrics: %s", err) - } - - p = append(p, provider) + p = append(p, statsd.NewProvider(cfg.Metrics.StatsD)) + case "stdout": + p = append(p, stdout.NewProvider(cfg.Metrics.StdOut)) default: log.Printf("[WARN] Skipping unknown metrics provider %q", x) continue diff --git a/metrics4/statsd/metrics.go b/metrics4/statsd/metrics.go index 4e5be3c1f..cbd143bf0 100644 --- a/metrics4/statsd/metrics.go +++ b/metrics4/statsd/metrics.go @@ -16,14 +16,14 @@ type Provider struct { sampleRate float64 } -func NewProvider(cfg config.StatsD) (metrics4.Provider, error) { +func NewProvider(cfg config.StatsD) metrics4.Provider { client := statsd.New(metrics4.FabioNamespace + ".", log.NewNopLogger()) ticker := time.NewTicker(cfg.Interval) go client.SendLoop(ticker.C, "udp", cfg.Addr) - return &Provider{client, ticker, cfg.SampleRate}, nil + return &Provider{client, ticker, cfg.SampleRate} } func (p *Provider) NewCounter(name string, labelsNames ...string) metrics4.Counter { diff --git a/metrics4/stdout/metrics.go b/metrics4/stdout/metrics.go index 6a2df9690..3d72202fd 100644 --- a/metrics4/stdout/metrics.go +++ b/metrics4/stdout/metrics.go @@ -9,13 +9,13 @@ import ( "os" ) -func NewProvider(cfg config.StdOut) (metrics4.Provider, error) { +func NewProvider(cfg config.StdOut) metrics4.Provider { logger := log.New(os.Stdout, "localhost: ", log.Lmicroseconds) r := rcgm.NewRegistry() go rcgm.Log(r, cfg.Interval, logger) - return gm.NewProvider(r), nil + return gm.NewProvider(r) } From 9e5db0518428fead4b8e6f2170c21746b1cbf7d8 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Mon, 17 Dec 2018 18:07:09 +0300 Subject: [PATCH 26/30] Added Circonus --- config/config.go | 13 +-- config/default.go | 22 ++-- config/load.go | 16 ++- main.go | 15 ++- metrics4/circonus/metrics.go | 192 +++++++++++++++++++++++++++++++++++ metrics4/graphite/metrics.go | 5 +- metrics4/statsd/metrics.go | 4 +- metrics4/stdout/metrics.go | 7 +- 8 files changed, 229 insertions(+), 45 deletions(-) create mode 100644 metrics4/circonus/metrics.go diff --git a/config/config.go b/config/config.go index 0febc2b32..e05ba72b6 100644 --- a/config/config.go +++ b/config/config.go @@ -98,24 +98,21 @@ type Metrics struct { Target string //Prefix string //Names string - //Interval time.Duration + Interval time.Duration //Timeout time.Duration //Retry time.Duration Prometheus Prometheus StatsD StatsD - StdOut StdOut Graphite Graphite - //Circonus Circonus + Circonus Circonus } type Graphite struct { - Addr string - Interval time.Duration + Addr string } type StatsD struct { Addr string - Interval time.Duration SampleRate float64 } @@ -123,10 +120,6 @@ type Prometheus struct { MetricsEndpoint string } -type StdOut struct { - Interval time.Duration -} - type Registry struct { Backend string Static Static diff --git a/config/default.go b/config/default.go index c4e0d762b..805ece8ed 100644 --- a/config/default.go +++ b/config/default.go @@ -26,27 +26,21 @@ var defaultConfig = &Config{ Level: "INFO", }, Metrics: Metrics{ + //Prefix: "{{clean .Hostname}}.{{clean .Exec}}", + //Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", + Interval: 30 * time.Second, + //Timeout: 10 * time.Second, + //Retry: 500 * time.Millisecond, Prometheus: Prometheus{ MetricsEndpoint: "/metrics/prometheus", }, StatsD: StatsD{ - Interval: 30 * time.Second, SampleRate: 1, }, - StdOut: StdOut{ - Interval: 30 * time.Second, - }, - Graphite: Graphite{ - Interval: 30 * time.Second, + Graphite: Graphite{}, + Circonus: Circonus{ + APIApp: "fabio", }, - //Prefix: "{{clean .Hostname}}.{{clean .Exec}}", - //Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", - //Interval: 30 * time.Second, - //Timeout: 10 * time.Second, - //Retry: 500 * time.Millisecond, - //Circonus: Circonus{ - // APIApp: "fabio", - //}, }, Proxy: Proxy{ MaxConn: 10000, diff --git a/config/load.go b/config/load.go index 06c50d989..ee675a8ec 100644 --- a/config/load.go +++ b/config/load.go @@ -148,26 +148,22 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c //f.StringVar(&cfg.Metrics.Prefix, "metrics.prefix", defaultConfig.Metrics.Prefix, "prefix for reported metrics") //f.StringVar(&cfg.Metrics.Names, "metrics.names", defaultConfig.Metrics.Names, "route metric name template") - //f.DurationVar(&cfg.Metrics.Interval, "metrics.interval", defaultConfig.Metrics.Interval, "metrics reporting interval") + f.DurationVar(&cfg.Metrics.Interval, "metrics.interval", defaultConfig.Metrics.Interval, "metrics reporting interval") //f.DurationVar(&cfg.Metrics.Timeout, "metrics.timeout", defaultConfig.Metrics.Timeout, "timeout for metrics to become available") //f.DurationVar(&cfg.Metrics.Retry, "metrics.retry", defaultConfig.Metrics.Retry, "retry interval during startup") f.StringVar(&cfg.Metrics.Prometheus.MetricsEndpoint, "metrics.prometheus.endpoint", defaultConfig.Metrics.Prometheus.MetricsEndpoint, "metrics endpoint for Prometheus") f.StringVar(&cfg.Metrics.Graphite.Addr, "metrics.graphite.addr", defaultConfig.Metrics.Graphite.Addr, "graphite carbon receiver (plaintext) address") - f.DurationVar(&cfg.Metrics.Graphite.Interval, "metrics.graphite.interval", defaultConfig.Metrics.Graphite.Interval, "graphite sending interval") - - f.DurationVar(&cfg.Metrics.StdOut.Interval, "metrics.stdout.interval", defaultConfig.Metrics.StdOut.Interval, "stdout logging interval") f.StringVar(&cfg.Metrics.StatsD.Addr, "metrics.statsd.addr", defaultConfig.Metrics.StatsD.Addr, "statsd server address") - f.DurationVar(&cfg.Metrics.StatsD.Interval, "metrics.statsd.interval", defaultConfig.Metrics.StatsD.Interval, "statsd sending interval") f.Float64Var(&cfg.Metrics.StatsD.SampleRate, "metrics.statsd.sampleRate", defaultConfig.Metrics.StatsD.SampleRate, "statsd sample rate") - //f.StringVar(&cfg.Metrics.Circonus.APIKey, "metrics.circonus.apikey", defaultConfig.Metrics.Circonus.APIKey, "Circonus API token key") - //f.StringVar(&cfg.Metrics.Circonus.APIApp, "metrics.circonus.apiapp", defaultConfig.Metrics.Circonus.APIApp, "Circonus API token app") - //f.StringVar(&cfg.Metrics.Circonus.APIURL, "metrics.circonus.apiurl", defaultConfig.Metrics.Circonus.APIURL, "Circonus API URL") - //f.StringVar(&cfg.Metrics.Circonus.BrokerID, "metrics.circonus.brokerid", defaultConfig.Metrics.Circonus.BrokerID, "Circonus Broker ID") - //f.StringVar(&cfg.Metrics.Circonus.CheckID, "metrics.circonus.checkid", defaultConfig.Metrics.Circonus.CheckID, "Circonus Check ID") + f.StringVar(&cfg.Metrics.Circonus.APIKey, "metrics.circonus.apikey", defaultConfig.Metrics.Circonus.APIKey, "Circonus API token key") + f.StringVar(&cfg.Metrics.Circonus.APIApp, "metrics.circonus.apiapp", defaultConfig.Metrics.Circonus.APIApp, "Circonus API token app") + f.StringVar(&cfg.Metrics.Circonus.APIURL, "metrics.circonus.apiurl", defaultConfig.Metrics.Circonus.APIURL, "Circonus API URL") + f.StringVar(&cfg.Metrics.Circonus.BrokerID, "metrics.circonus.brokerid", defaultConfig.Metrics.Circonus.BrokerID, "Circonus Broker ID") + f.StringVar(&cfg.Metrics.Circonus.CheckID, "metrics.circonus.checkid", defaultConfig.Metrics.Circonus.CheckID, "Circonus Check ID") f.StringVar(&cfg.Registry.Backend, "registry.backend", defaultConfig.Registry.Backend, "registry backend") f.DurationVar(&cfg.Registry.Timeout, "registry.timeout", defaultConfig.Registry.Timeout, "timeout for registry to become available") diff --git a/main.go b/main.go index 390201cc7..b1e5a57d6 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/json" "fmt" + "github.com/fabiolb/fabio/metrics4/circonus" "github.com/fabiolb/fabio/metrics4/graphite" "github.com/fabiolb/fabio/metrics4/stdout" "io" @@ -319,7 +320,7 @@ func initMetrics(cfg *config.Config) metrics4.Provider { case "prometheus": p = append(p, prometheus.NewProvider()) case "graphite": - provider, err := graphite.NewProvider(cfg.Metrics.Graphite) + provider, err := graphite.NewProvider(cfg.Metrics.Graphite, cfg.Metrics.Interval) if err != nil { exit.Fatalf("[FATAL] Cannot initialize graphite metrics: %s", err) @@ -327,9 +328,17 @@ func initMetrics(cfg *config.Config) metrics4.Provider { p = append(p, provider) case "statsd": - p = append(p, statsd.NewProvider(cfg.Metrics.StatsD)) + p = append(p, statsd.NewProvider(cfg.Metrics.StatsD, cfg.Metrics.Interval)) case "stdout": - p = append(p, stdout.NewProvider(cfg.Metrics.StdOut)) + p = append(p, stdout.NewProvider(cfg.Metrics.Interval)) + case "circonus": + provider, err := circonus.NewProvider(cfg.Metrics.Circonus, cfg.Metrics.Interval) + + if err != nil { + exit.Fatalf("[FATAL] Cannot initialize circonus metrics: %s", err) + } + + p = append(p, provider) default: log.Printf("[WARN] Skipping unknown metrics provider %q", x) continue diff --git a/metrics4/circonus/metrics.go b/metrics4/circonus/metrics.go new file mode 100644 index 000000000..a204a7607 --- /dev/null +++ b/metrics4/circonus/metrics.go @@ -0,0 +1,192 @@ +package circonus + +import ( + "errors" + "fmt" + cgm "github.com/circonus-labs/circonus-gometrics" + "github.com/fabiolb/fabio/config" + "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/gm" + rcgm "github.com/rcrowley/go-metrics" + "log" + "os" + "sync" + "time" +) + +var ( + circonus *cgmRegistry + once sync.Once +) + +func NewProvider(cfg config.Circonus, interval time.Duration) (metrics4.Provider, error) { + r, err := circonusRegistry("", cfg, interval) + if err != nil { + return nil, err + } + return gm.NewProvider(r), nil +} + +func circonusRegistry(prefix string, circonusCfg config.Circonus, interval time.Duration) (rcgm.Registry, error) { + var initError error + + once.Do(func() { + if circonusCfg.APIKey == "" { + initError = errors.New("metrics: Circonus API token key") + return + } + + if circonusCfg.APIApp == "" { + circonusCfg.APIApp = metrics4.FabioNamespace + } + + host, err := os.Hostname() + if err != nil { + initError = fmt.Errorf("metrics: unable to initialize Circonus %s", err) + return + } + + cfg := &cgm.Config{} + + cfg.CheckManager.API.TokenKey = circonusCfg.APIKey + cfg.CheckManager.API.TokenApp = circonusCfg.APIApp + cfg.CheckManager.API.URL = circonusCfg.APIURL + cfg.CheckManager.Check.ID = circonusCfg.CheckID + cfg.CheckManager.Broker.ID = circonusCfg.BrokerID + cfg.Interval = fmt.Sprintf("%.0fs", interval.Seconds()) + cfg.CheckManager.Check.InstanceID = host + cfg.CheckManager.Check.DisplayName = fmt.Sprintf("%s /%s", host, metrics4.FabioNamespace) + cfg.CheckManager.Check.SearchTag = fmt.Sprintf("service:%s", metrics4.FabioNamespace) + + metrics, err := cgm.NewCirconusMetrics(cfg) + if err != nil { + initError = fmt.Errorf("metrics: unable to initialize Circonus %s", err) + return + } + + circonus = &cgmRegistry{metrics, prefix} + + metrics.Start() + + log.Print("[INFO] Sending metrics to Circonus") + }) + + return circonus, initError +} + +type cgmRegistry struct { + metrics *cgm.CirconusMetrics + prefix string +} + +func (m *cgmRegistry) Names() []string { return nil } + +func (m *cgmRegistry) Get(string) interface{} { return nil } + +func (m *cgmRegistry) Register(string, interface{}) error { return nil } + +func (m *cgmRegistry) RunHealthchecks() {} + +func (m *cgmRegistry) GetOrRegister(string, interface{}) interface{} { return nil } + +func (m *cgmRegistry) Each(func(string, interface{})) {} + +func (m *cgmRegistry) Unregister(name string) {} + +func (m *cgmRegistry) UnregisterAll() {} + +func (m *cgmRegistry) GetCounter(name string) rcgm.Counter { + metricName := fmt.Sprintf("%s`%s", m.prefix, name) + return &cgmCounter{m.metrics, metricName} +} + +func (m *cgmRegistry) GetTimer(name string) rcgm.Timer { + metricName := fmt.Sprintf("%s`%s", m.prefix, name) + return &cgmTimer{m.metrics, metricName} +} + +type cgmCounter struct { + metrics *cgm.CirconusMetrics + name string +} + +func (c *cgmCounter) Inc(n int64) { + c.metrics.IncrementByValue(c.name, uint64(n)) +} + +func (c *cgmCounter) Dec(n int64) {} + +func (c *cgmCounter) Clear() {} + +func (c *cgmCounter) Count() int64 { + return 0 +} + +func (c *cgmCounter) Snapshot() rcgm.Counter { + return c +} + +type cgmTimer struct { + metrics *cgm.CirconusMetrics + name string +} + +func (t *cgmTimer) Percentile(nth float64) float64 { return 0 } + +func (t *cgmTimer) Rate1() float64 { return 0 } + +func (t *cgmTimer) Update(d time.Duration) { + t.metrics.Timing(t.name, float64(d)) +} + +func (t *cgmTimer) UpdateSince(start time.Time) {} + +func (t *cgmTimer) Count() int64 { + return 0 +} + +func (t *cgmTimer) Min() int64 { + return 0 +} + +func (t *cgmTimer) Max() int64 { + return 0 +} + +func (t *cgmTimer) Mean() float64 { + return 0 +} + +func (t *cgmTimer) Percentiles([]float64) []float64 { + return nil +} + +func (t *cgmTimer) Rate5() float64 { + return 0 +} + +func (t *cgmTimer) Rate15() float64 { + return 0 +} + +func (t *cgmTimer) Snapshot() rcgm.Timer { + return t +} + +func (t *cgmTimer) RateMean() float64 { + return 0 +} + +func (t *cgmTimer) StdDev() float64 { + return 0 +} + +func (t *cgmTimer) Sum() int64 { + return 0 +} + +func (t *cgmTimer) Time(func()) {} + +func (t *cgmTimer) Variance() float64 { + return 0 +} diff --git a/metrics4/graphite/metrics.go b/metrics4/graphite/metrics.go index fdba7ae4e..f46a8e312 100644 --- a/metrics4/graphite/metrics.go +++ b/metrics4/graphite/metrics.go @@ -9,9 +9,10 @@ import ( "github.com/fabiolb/fabio/metrics4/gm" rcgm "github.com/rcrowley/go-metrics" "net" + "time" ) -func NewProvider(cfg config.Graphite) (metrics4.Provider, error) { +func NewProvider(cfg config.Graphite, interval time.Duration) (metrics4.Provider, error) { if cfg.Addr == "" { return nil, errors.New(" graphite addr missing") } @@ -23,7 +24,7 @@ func NewProvider(cfg config.Graphite) (metrics4.Provider, error) { registry := rcgm.NewRegistry() - go graphite.Graphite(registry, cfg.Interval, metrics4.FabioNamespace, a) + go graphite.Graphite(registry, interval, metrics4.FabioNamespace, a) return gm.NewProvider(registry), nil } \ No newline at end of file diff --git a/metrics4/statsd/metrics.go b/metrics4/statsd/metrics.go index cbd143bf0..bfe543861 100644 --- a/metrics4/statsd/metrics.go +++ b/metrics4/statsd/metrics.go @@ -16,10 +16,10 @@ type Provider struct { sampleRate float64 } -func NewProvider(cfg config.StatsD) metrics4.Provider { +func NewProvider(cfg config.StatsD, interval time.Duration) metrics4.Provider { client := statsd.New(metrics4.FabioNamespace + ".", log.NewNopLogger()) - ticker := time.NewTicker(cfg.Interval) + ticker := time.NewTicker(interval) go client.SendLoop(ticker.C, "udp", cfg.Addr) diff --git a/metrics4/stdout/metrics.go b/metrics4/stdout/metrics.go index 3d72202fd..d5c64b3e6 100644 --- a/metrics4/stdout/metrics.go +++ b/metrics4/stdout/metrics.go @@ -1,21 +1,20 @@ package stdout import ( - "github.com/fabiolb/fabio/config" "github.com/fabiolb/fabio/metrics4" "github.com/fabiolb/fabio/metrics4/gm" rcgm "github.com/rcrowley/go-metrics" "log" "os" + "time" ) -func NewProvider(cfg config.StdOut) metrics4.Provider { +func NewProvider(interval time.Duration) metrics4.Provider { logger := log.New(os.Stdout, "localhost: ", log.Lmicroseconds) r := rcgm.NewRegistry() - go rcgm.Log(r, cfg.Interval, logger) + go rcgm.Log(r, interval, logger) return gm.NewProvider(r) } - From d006713c9505e9cd8c352673c0f767deec46c7bc Mon Sep 17 00:00:00 2001 From: maximka777 Date: Mon, 17 Dec 2018 18:32:35 +0300 Subject: [PATCH 27/30] Refactored Circonus to new Provider interface --- metrics4/circonus/metrics.go | 142 +++++++++++------------------------ 1 file changed, 43 insertions(+), 99 deletions(-) diff --git a/metrics4/circonus/metrics.go b/metrics4/circonus/metrics.go index a204a7607..577dc08dd 100644 --- a/metrics4/circonus/metrics.go +++ b/metrics4/circonus/metrics.go @@ -6,8 +6,7 @@ import ( cgm "github.com/circonus-labs/circonus-gometrics" "github.com/fabiolb/fabio/config" "github.com/fabiolb/fabio/metrics4" - "github.com/fabiolb/fabio/metrics4/gm" - rcgm "github.com/rcrowley/go-metrics" + "github.com/fabiolb/fabio/metrics4/untagged" "log" "os" "sync" @@ -15,20 +14,17 @@ import ( ) var ( - circonus *cgmRegistry - once sync.Once + metrics *cgm.CirconusMetrics + once sync.Once ) -func NewProvider(cfg config.Circonus, interval time.Duration) (metrics4.Provider, error) { - r, err := circonusRegistry("", cfg, interval) - if err != nil { - return nil, err - } - return gm.NewProvider(r), nil +type Provider struct { + c *cgm.CirconusMetrics } -func circonusRegistry(prefix string, circonusCfg config.Circonus, interval time.Duration) (rcgm.Registry, error) { +func NewProvider(circonusCfg config.Circonus, interval time.Duration) (metrics4.Provider, error) { var initError error + var metrics *cgm.CirconusMetrics once.Do(func() { if circonusCfg.APIKey == "" { @@ -64,129 +60,77 @@ func circonusRegistry(prefix string, circonusCfg config.Circonus, interval time. return } - circonus = &cgmRegistry{metrics, prefix} - metrics.Start() log.Print("[INFO] Sending metrics to Circonus") }) - return circonus, initError + return &Provider{metrics}, initError } -type cgmRegistry struct { - metrics *cgm.CirconusMetrics - prefix string +func (p *Provider) NewCounter(name string, labelsNames ... string) metrics4.Counter { + if len(labelsNames) == 0 { + return &Counter{p.c, name} + } + return untagged.NewCounter(p, name, labelsNames) } -func (m *cgmRegistry) Names() []string { return nil } - -func (m *cgmRegistry) Get(string) interface{} { return nil } - -func (m *cgmRegistry) Register(string, interface{}) error { return nil } - -func (m *cgmRegistry) RunHealthchecks() {} - -func (m *cgmRegistry) GetOrRegister(string, interface{}) interface{} { return nil } - -func (m *cgmRegistry) Each(func(string, interface{})) {} - -func (m *cgmRegistry) Unregister(name string) {} - -func (m *cgmRegistry) UnregisterAll() {} +func (p *Provider) NewGauge(name string, labelsNames ... string) metrics4.Gauge { + if len(labelsNames) == 0 { + return &Gauge{p.c, name} + } + return untagged.NewGauge(p, name, labelsNames) +} -func (m *cgmRegistry) GetCounter(name string) rcgm.Counter { - metricName := fmt.Sprintf("%s`%s", m.prefix, name) - return &cgmCounter{m.metrics, metricName} +func (p *Provider) NewTimer(name string, labelsNames ... string) metrics4.Timer { + if len(labelsNames) == 0 { + return &Timer{p.c.NewHistogram(name)} + } + return untagged.NewTimer(p, name, labelsNames) } -func (m *cgmRegistry) GetTimer(name string) rcgm.Timer { - metricName := fmt.Sprintf("%s`%s", m.prefix, name) - return &cgmTimer{m.metrics, metricName} +func (p *Provider) Close() error { + return nil } -type cgmCounter struct { +type Counter struct { metrics *cgm.CirconusMetrics name string } -func (c *cgmCounter) Inc(n int64) { - c.metrics.IncrementByValue(c.name, uint64(n)) -} - -func (c *cgmCounter) Dec(n int64) {} - -func (c *cgmCounter) Clear() {} - -func (c *cgmCounter) Count() int64 { - return 0 +func (c *Counter) Add(value float64) { + c.metrics.Add(c.name, uint64(value)) } -func (c *cgmCounter) Snapshot() rcgm.Counter { +func (c *Counter) With(labels ... string) metrics4.Counter { return c } -type cgmTimer struct { +type Gauge struct { metrics *cgm.CirconusMetrics name string } -func (t *cgmTimer) Percentile(nth float64) float64 { return 0 } - -func (t *cgmTimer) Rate1() float64 { return 0 } - -func (t *cgmTimer) Update(d time.Duration) { - t.metrics.Timing(t.name, float64(d)) -} - -func (t *cgmTimer) UpdateSince(start time.Time) {} - -func (t *cgmTimer) Count() int64 { - return 0 +func (g *Gauge) Add(value float64) { + g.metrics.Add(g.name, uint64(value)) } -func (t *cgmTimer) Min() int64 { - return 0 +func (g *Gauge) Set(value float64) { + g.metrics.Set(g.name, uint64(value)) } -func (t *cgmTimer) Max() int64 { - return 0 -} - -func (t *cgmTimer) Mean() float64 { - return 0 -} - -func (t *cgmTimer) Percentiles([]float64) []float64 { - return nil +func (g *Gauge) With(labels ... string) metrics4.Gauge { + return g } -func (t *cgmTimer) Rate5() float64 { - return 0 +type Timer struct { + h *cgm.Histogram } -func (t *cgmTimer) Rate15() float64 { - return 0 +func (t *Timer) Observe(duration float64) { + t.h.RecordValue(duration) } -func (t *cgmTimer) Snapshot() rcgm.Timer { - return t -} - -func (t *cgmTimer) RateMean() float64 { - return 0 -} - -func (t *cgmTimer) StdDev() float64 { - return 0 -} - -func (t *cgmTimer) Sum() int64 { - return 0 -} - -func (t *cgmTimer) Time(func()) {} - -func (t *cgmTimer) Variance() float64 { - return 0 +func (g *Timer) With(labels ... string) metrics4.Timer { + return g } From 8b23b1e140dc1ce9b65d9c69341dbfa68e3405df Mon Sep 17 00:00:00 2001 From: maximka777 Date: Tue, 18 Dec 2018 15:27:03 +0300 Subject: [PATCH 28/30] Metric prefix --- config/config.go | 5 +-- config/load.go | 7 +--- config/load_test.go | 4 +- logger/pattern.go | 6 +-- main.go | 10 +++-- metrics4/circonus/metrics.go | 15 ++++++- metrics4/graphite/metrics.go | 4 +- metrics4/metrics.go | 27 +++++++------ metrics4/names/names.go | 31 +------------- metrics4/prefix/prefix.go | 74 ++++++++++++++++++++++++++++++++++ metrics4/prometheus/metrics.go | 22 +++++----- metrics4/statsd/metrics.go | 8 +++- metrics4/untagged/untagged.go | 9 +++-- metrics_old/metrics.go | 14 +++---- registry/consul/service.go | 2 +- route/route.go | 2 +- route/table_registry_test.go | 14 +++---- route/target.go | 5 +-- 18 files changed, 159 insertions(+), 100 deletions(-) create mode 100644 metrics4/prefix/prefix.go diff --git a/config/config.go b/config/config.go index e05ba72b6..7ef0c555d 100644 --- a/config/config.go +++ b/config/config.go @@ -96,11 +96,8 @@ type Log struct { type Metrics struct { Target string - //Prefix string - //Names string + Prefix string Interval time.Duration - //Timeout time.Duration - //Retry time.Duration Prometheus Prometheus StatsD StatsD Graphite Graphite diff --git a/config/load.go b/config/load.go index ee675a8ec..22c62d5e7 100644 --- a/config/load.go +++ b/config/load.go @@ -146,15 +146,12 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c f.StringVar(&cfg.Metrics.Target, "metrics.target", defaultConfig.Metrics.Target, "metrics backend") - //f.StringVar(&cfg.Metrics.Prefix, "metrics.prefix", defaultConfig.Metrics.Prefix, "prefix for reported metrics") - //f.StringVar(&cfg.Metrics.Names, "metrics.names", defaultConfig.Metrics.Names, "route metric name template") + f.StringVar(&cfg.Metrics.Prefix, "metrics.prefix", defaultConfig.Metrics.Prefix, "prefix for reported metrics") f.DurationVar(&cfg.Metrics.Interval, "metrics.interval", defaultConfig.Metrics.Interval, "metrics reporting interval") - //f.DurationVar(&cfg.Metrics.Timeout, "metrics.timeout", defaultConfig.Metrics.Timeout, "timeout for metrics to become available") - //f.DurationVar(&cfg.Metrics.Retry, "metrics.retry", defaultConfig.Metrics.Retry, "retry interval during startup") f.StringVar(&cfg.Metrics.Prometheus.MetricsEndpoint, "metrics.prometheus.endpoint", defaultConfig.Metrics.Prometheus.MetricsEndpoint, "metrics endpoint for Prometheus") - f.StringVar(&cfg.Metrics.Graphite.Addr, "metrics.graphite.addr", defaultConfig.Metrics.Graphite.Addr, "graphite carbon receiver (plaintext) address") + f.StringVar(&cfg.Metrics.Graphite.Addr, "metrics.graphite.addr", defaultConfig.Metrics.Graphite.Addr, "graphite carbon receiver or aggregator (plaintext) address") f.StringVar(&cfg.Metrics.StatsD.Addr, "metrics.statsd.addr", defaultConfig.Metrics.StatsD.Addr, "statsd server address") f.Float64Var(&cfg.Metrics.StatsD.SampleRate, "metrics.statsd.sampleRate", defaultConfig.Metrics.StatsD.SampleRate, "statsd sample rate") diff --git a/config/load_test.go b/config/load_test.go index bf7c9f288..b4d6dfe1d 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -647,9 +647,9 @@ func TestLoad(t *testing.T) { }, }, { - args: []string{"-metrics.names", "some names"}, + args: []string{"-metrics.prefix", "some prefix"}, cfg: func(cfg *Config) *Config { - cfg.Metrics.Names = "some names" + cfg.Metrics.Names = "some prefix" return cfg }, }, diff --git a/logger/pattern.go b/logger/pattern.go index 664bf74b6..2d1455e12 100644 --- a/logger/pattern.go +++ b/logger/pattern.go @@ -326,9 +326,9 @@ func atoi(b *bytes.Buffer, i int64, pad int) { // parse parses a format string into a pattern based on the following rules: // -// The format string consists of text and fields. Field names start with a '$' -// and consist of ASCII characters [a-zA-Z0-9.-_]. Field names like -// '$header.name' will render the HTTP header 'name'. All other field names +// The format string consists of text and fields. Field prefix start with a '$' +// and consist of ASCII characters [a-zA-Z0-9.-_]. Field prefix like +// '$header.name' will render the HTTP header 'name'. All other field prefix // must exist in the fields map. func parse(format string, fields map[string]field) (p pattern, err error) { // text is a helper to add raw text to the log output. diff --git a/main.go b/main.go index b1e5a57d6..2f40b15d6 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/fabiolb/fabio/metrics4/circonus" "github.com/fabiolb/fabio/metrics4/graphite" + "github.com/fabiolb/fabio/metrics4/prefix" "github.com/fabiolb/fabio/metrics4/stdout" "io" "log" @@ -313,14 +314,15 @@ func startServers(cfg *config.Config, stats metrics4.Provider) { } func initMetrics(cfg *config.Config) metrics4.Provider { + prefix.InitPrefix(cfg.Metrics.Prefix) var p []metrics4.Provider for _, x := range strings.Split(cfg.Metrics.Target, ",") { x = strings.TrimSpace(x) switch x { case "prometheus": - p = append(p, prometheus.NewProvider()) + p = append(p, prometheus.NewProvider(prefix.GetPrefix())) case "graphite": - provider, err := graphite.NewProvider(cfg.Metrics.Graphite, cfg.Metrics.Interval) + provider, err := graphite.NewProvider(cfg.Metrics.Graphite, cfg.Metrics.Interval, prefix.GetPrefix()) if err != nil { exit.Fatalf("[FATAL] Cannot initialize graphite metrics: %s", err) @@ -328,11 +330,11 @@ func initMetrics(cfg *config.Config) metrics4.Provider { p = append(p, provider) case "statsd": - p = append(p, statsd.NewProvider(cfg.Metrics.StatsD, cfg.Metrics.Interval)) + p = append(p, statsd.NewProvider(cfg.Metrics.StatsD, cfg.Metrics.Interval, prefix.GetPrefix())) case "stdout": p = append(p, stdout.NewProvider(cfg.Metrics.Interval)) case "circonus": - provider, err := circonus.NewProvider(cfg.Metrics.Circonus, cfg.Metrics.Interval) + provider, err := circonus.NewProvider(cfg.Metrics.Circonus, cfg.Metrics.Interval, prefix.GetPrefix()) if err != nil { exit.Fatalf("[FATAL] Cannot initialize circonus metrics: %s", err) diff --git a/metrics4/circonus/metrics.go b/metrics4/circonus/metrics.go index 577dc08dd..c5897a585 100644 --- a/metrics4/circonus/metrics.go +++ b/metrics4/circonus/metrics.go @@ -20,9 +20,10 @@ var ( type Provider struct { c *cgm.CirconusMetrics + prefix string } -func NewProvider(circonusCfg config.Circonus, interval time.Duration) (metrics4.Provider, error) { +func NewProvider(circonusCfg config.Circonus, interval time.Duration, prefix string) (metrics4.Provider, error) { var initError error var metrics *cgm.CirconusMetrics @@ -65,10 +66,11 @@ func NewProvider(circonusCfg config.Circonus, interval time.Duration) (metrics4. log.Print("[INFO] Sending metrics to Circonus") }) - return &Provider{metrics}, initError + return &Provider{metrics, prefix}, initError } func (p *Provider) NewCounter(name string, labelsNames ... string) metrics4.Counter { + name = getPrefixName(p.prefix, name) if len(labelsNames) == 0 { return &Counter{p.c, name} } @@ -76,6 +78,7 @@ func (p *Provider) NewCounter(name string, labelsNames ... string) metrics4.Coun } func (p *Provider) NewGauge(name string, labelsNames ... string) metrics4.Gauge { + name = getPrefixName(p.prefix, name) if len(labelsNames) == 0 { return &Gauge{p.c, name} } @@ -83,6 +86,7 @@ func (p *Provider) NewGauge(name string, labelsNames ... string) metrics4.Gauge } func (p *Provider) NewTimer(name string, labelsNames ... string) metrics4.Timer { + name = getPrefixName(p.prefix, name) if len(labelsNames) == 0 { return &Timer{p.c.NewHistogram(name)} } @@ -93,6 +97,13 @@ func (p *Provider) Close() error { return nil } +func getPrefixName(prefix string, name string) string { + if len(prefix) == 0 { + return name + } + return fmt.Sprintf("%s`%s", prefix, name) +} + type Counter struct { metrics *cgm.CirconusMetrics name string diff --git a/metrics4/graphite/metrics.go b/metrics4/graphite/metrics.go index f46a8e312..36298d692 100644 --- a/metrics4/graphite/metrics.go +++ b/metrics4/graphite/metrics.go @@ -12,7 +12,7 @@ import ( "time" ) -func NewProvider(cfg config.Graphite, interval time.Duration) (metrics4.Provider, error) { +func NewProvider(cfg config.Graphite, interval time.Duration, prefix string) (metrics4.Provider, error) { if cfg.Addr == "" { return nil, errors.New(" graphite addr missing") } @@ -24,7 +24,7 @@ func NewProvider(cfg config.Graphite, interval time.Duration) (metrics4.Provider registry := rcgm.NewRegistry() - go graphite.Graphite(registry, interval, metrics4.FabioNamespace, a) + go graphite.Graphite(registry, interval, prefix, a) return gm.NewProvider(registry), nil } \ No newline at end of file diff --git a/metrics4/metrics.go b/metrics4/metrics.go index 9b87372ba..d66df8d2d 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -1,13 +1,12 @@ package metrics4 import ( + "github.com/fabiolb/fabio/metrics4/prefix" "github.com/go-kit/kit/metrics" "io" ) const FabioNamespace = "fabio" -const DotDelimiter = "." -const UnderscoreDelimiter = "_" type Counter = metrics.Counter @@ -18,7 +17,7 @@ type Timer = metrics.Histogram // Provider is an abstraction of a metrics backend. type Provider interface { // NewCounter creates a new counter object. - // labels - array of labels names + // labels - array of labels prefix NewCounter(name string, labelsNames ... string) Counter // NewGauge creates a new gauge object. @@ -33,7 +32,7 @@ type Provider interface { // MultiProvider wraps zero or more providers. type MultiProvider struct { - p []Provider + p []Provider } func NewMultiProvider(p []Provider) *MultiProvider { @@ -43,9 +42,9 @@ func NewMultiProvider(p []Provider) *MultiProvider { // NewCounter creates a MultiCounter with counter objects for all registered // providers. func (mp *MultiProvider) NewCounter(name string, labels ... string) Counter { - var c []Counter - for _, p := range mp.p { - c = append(c, p.NewCounter(name, labels...)) + c := make([]Counter, len(mp.p)) + for i, p := range mp.p { + c[i] = p.NewCounter(name, labels...) } return &MultiCounter{c} } @@ -53,9 +52,10 @@ func (mp *MultiProvider) NewCounter(name string, labels ... string) Counter { // NewGauge creates a MultiGauge with gauge objects for all registered // providers. func (mp *MultiProvider) NewGauge(name string, labels ... string) Gauge { - var g []Gauge - for _, p := range mp.p { - g = append(g, p.NewGauge(name, labels...)) + name = prefix.GetPrefixedName(name) + g := make([]Gauge, len(mp.p)) + for i, p := range mp.p { + g[i] = p.NewGauge(name, labels...) } return &MultiGauge{g} } @@ -63,9 +63,10 @@ func (mp *MultiProvider) NewGauge(name string, labels ... string) Gauge { // NewTimer creates a MultiTimer with timer objects for all registered // providers. func (mp *MultiProvider) NewTimer(name string, labels ... string) Timer { - var t []Timer - for _, p := range mp.p { - t = append(t, p.NewTimer(name, labels...)) + name = prefix.GetPrefixedName(name) + t := make([]Timer, len(mp.p)) + for i, p := range mp.p { + t[i] = p.NewTimer(name, labels...) } return &MultiTimer{t} } diff --git a/metrics4/names/names.go b/metrics4/names/names.go index 8ec1b64d1..730738348 100644 --- a/metrics4/names/names.go +++ b/metrics4/names/names.go @@ -1,9 +1,6 @@ package names -import ( - "net/url" - "strings" -) +import "net/url" type Service struct { Service string @@ -15,29 +12,3 @@ type Service struct { func (s Service) String() string { return s.Service } - -const DotSeparator = "." -const PipeSeparator = "|" - -func Flatten(name string, labels []string, separator string) string { - if len(labels) == 0 { - return name - } - return name + separator + strings.Join(labels, separator) -} - -// todo(fs): this function probably allocates like crazy. If on the stack then it might be ok. -// todo(fs): otherwise, give some love. -func Labels(labels []string, prefix, fieldsep, recsep string) string { - if len(labels) == 0 { - return "" - } - if len(labels)%2 != 0 { - labels = append(labels, "???") - } - var fields []string - for i := 0; i < len(labels); i += 2 { - fields = append(fields, labels[i]+fieldsep+labels[i+1]) - } - return prefix + strings.Join(fields, recsep) -} diff --git a/metrics4/prefix/prefix.go b/metrics4/prefix/prefix.go new file mode 100644 index 000000000..1d4a4cf82 --- /dev/null +++ b/metrics4/prefix/prefix.go @@ -0,0 +1,74 @@ +package prefix + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "sync" + "text/template" +) + +const DotDelimiter = "." + +const DefaultPrefix = "{{clean .Hostname}}.{{clean .Exec}}" + +var ( + prefix string + once sync.Once +) + +// clean creates safe prefix for graphite reporting by replacing +// some characters with underscores. +// TODO(fs): This may need updating for other metrics backends. +func clean(s string) string { + if s == "" { + return "_" + } + s = strings.Replace(s, ".", "_", -1) + s = strings.Replace(s, ":", "_", -1) + return strings.ToLower(s) +} + +func InitPrefix(tmpl string) { + once.Do(func() { + // Backward compatibility condition for old metrics.prefix parameter 'default' + if tmpl == "default" { + tmpl = DefaultPrefix + } + funcMap := template.FuncMap{ + "clean": clean, + } + t, err := template.New("prefix").Funcs(funcMap).Parse(tmpl) + if err != nil { + panic(err) + } + host, err := hostname() + if err != nil { + panic(err) + } + exe := filepath.Base(os.Args[0]) + + b := new(bytes.Buffer) + data := struct{ Hostname, Exec string }{host, exe} + if err := t.Execute(b, &data); err != nil { + panic(err) + } + prefix = b.String() + }) +} + + +func GetPrefix() string { + return prefix +} + +func GetPrefixedName(name string) string { + if len(prefix) == 0 { + return name + } + return prefix + "." + name +} + +// stubbed out for testing +var hostname = os.Hostname diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index e1b4e7602..cd4b301e7 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -9,21 +9,23 @@ import ( ) type Provider struct { - counters map[string]metrics4.Counter - gauges map[string]metrics4.Gauge - timers map[string]metrics4.Timer - mutex sync.Mutex + counters map[string]metrics4.Counter + gauges map[string]metrics4.Gauge + timers map[string]metrics4.Timer + mutex sync.Mutex + prefix string } func normalizeName(name string) string { return strings.Replace(name, ".", "_", -1) } -func NewProvider() metrics4.Provider { +func NewProvider(prefix string) metrics4.Provider { return &Provider{ counters: make(map[string]metrics4.Counter), - gauges: make(map[string]metrics4.Gauge), - timers: make(map[string]metrics4.Timer), + gauges: make(map[string]metrics4.Gauge), + timers: make(map[string]metrics4.Timer), + prefix: prefix, } } @@ -33,7 +35,7 @@ func (p *Provider) NewCounter(name string, labels ... string) metrics4.Counter { defer p.mutex.Unlock() if p.counters[name] == nil { p.counters[name] = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ - Namespace: metrics4.FabioNamespace, + Namespace: normalizeName(p.prefix), Name: name, }, labels) } @@ -47,7 +49,7 @@ func (p *Provider) NewGauge(name string, labels ... string) metrics4.Gauge { defer p.mutex.Unlock() if p.gauges[name] == nil { p.gauges[name] = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ - Namespace: metrics4.FabioNamespace, + Namespace: normalizeName(p.prefix), Name: name, }, labels) } @@ -61,7 +63,7 @@ func (p *Provider) NewTimer(name string, labels ... string) metrics4.Timer { defer p.mutex.Unlock() if p.timers[name] == nil { p.timers[name] = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ - Namespace: metrics4.FabioNamespace, + Namespace: normalizeName(p.prefix), Name: name, }, labels) } diff --git a/metrics4/statsd/metrics.go b/metrics4/statsd/metrics.go index bfe543861..3a706df93 100644 --- a/metrics4/statsd/metrics.go +++ b/metrics4/statsd/metrics.go @@ -16,8 +16,12 @@ type Provider struct { sampleRate float64 } -func NewProvider(cfg config.StatsD, interval time.Duration) metrics4.Provider { - client := statsd.New(metrics4.FabioNamespace + ".", log.NewNopLogger()) +func NewProvider(cfg config.StatsD, interval time.Duration, prefix string) metrics4.Provider { + if len(prefix) != 0 { + prefix = prefix + "." + } + + client := statsd.New(prefix, log.NewNopLogger()) ticker := time.NewTicker(interval) diff --git a/metrics4/untagged/untagged.go b/metrics4/untagged/untagged.go index 35f1f850b..d4bd08222 100644 --- a/metrics4/untagged/untagged.go +++ b/metrics4/untagged/untagged.go @@ -3,6 +3,7 @@ package untagged import ( "errors" "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/prefix" "strings" ) @@ -28,7 +29,7 @@ func makeNameFromLabels(labelsNames []string, labels []string) string { if err != nil { panic(err) } - return strings.Join(labels, metrics4.DotDelimiter) + return strings.Join(labels, prefix.DotDelimiter) } type metric struct { @@ -52,7 +53,7 @@ type counter struct { func (c *counter) Add(delta float64) {} func (c *counter) With(labels ... string) metrics4.Counter { - return c.m.p.NewCounter(c.m.name + metrics4.DotDelimiter + makeNameFromLabels(c.m.labelsNames, labels)) + return c.m.p.NewCounter(c.m.name + prefix.DotDelimiter + makeNameFromLabels(c.m.labelsNames, labels)) } func NewCounter(p metrics4.Provider, name string, labelsNames []string) metrics4.Counter { @@ -68,7 +69,7 @@ type timer struct { func (t *timer) Observe(value float64) {} func (t *timer) With(labels ... string) metrics4.Timer { - return t.m.p.NewTimer(t.m.name + metrics4.DotDelimiter + makeNameFromLabels(t.m.labelsNames, labels)) + return t.m.p.NewTimer(t.m.name + prefix.DotDelimiter + makeNameFromLabels(t.m.labelsNames, labels)) } func NewTimer(p metrics4.Provider, name string, labelsNames []string) metrics4.Timer { @@ -86,7 +87,7 @@ func (g *gauge) Add(value float64) {} func (g *gauge) Set(value float64) {} func (g *gauge) With(labels ... string) metrics4.Gauge { - return g.m.p.NewGauge(g.m.name + metrics4.DotDelimiter + makeNameFromLabels(g.m.labelsNames, labels)) + return g.m.p.NewGauge(g.m.name + prefix.DotDelimiter + makeNameFromLabels(g.m.labelsNames, labels)) } func NewGauge(p metrics4.Provider, name string, labelsNames []string) metrics4.Gauge { diff --git a/metrics_old/metrics.go b/metrics_old/metrics.go index 2be4c475b..205f9cd04 100644 --- a/metrics_old/metrics.go +++ b/metrics_old/metrics.go @@ -22,20 +22,20 @@ import ( // DefaultRegistry stores the metrics library provider. var DefaultRegistry Registry = NoopRegistry{} -// DefaultNames contains the default template for route metric names. +// DefaultNames contains the default template for route metric prefix. const DefaultNames = "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}" -// DefaulPrefix contains the default template for metrics prefix. +// DefaultPrefix contains the default template for metrics prefix. const DefaultPrefix = "{{clean .Hostname}}.{{clean .Exec}}" -// names stores the template for the route metric names. +// prefix stores the template for the route metric prefix. var names *template.Template // prefix stores the final prefix string to use it with metric collectors where applicable, i.e. Graphite/StatsD var prefix string func init() { - // make sure names is initialized to something + // make sure prefix is initialized to something var err error if names, err = parseNames(DefaultNames); err != nil { panic(err) @@ -50,7 +50,7 @@ func NewRegistry(cfg config.Metrics) (r Registry, err error) { } if names, err = parseNames(cfg.Names); err != nil { - return nil, fmt.Errorf("metrics: invalid names template. %s", err) + return nil, fmt.Errorf("metrics: invalid prefix template. %s", err) } switch cfg.Target { @@ -107,7 +107,7 @@ func parseNames(tmpl string) (*template.Template, error) { funcMap := template.FuncMap{ "clean": clean, } - t, err := template.New("names").Funcs(funcMap).Parse(tmpl) + t, err := template.New("prefix").Funcs(funcMap).Parse(tmpl) if err != nil { return nil, err } @@ -141,7 +141,7 @@ func TargetName(service, host, path string, targetURL *url.URL) (string, error) return name.String(), nil } -// clean creates safe names for graphite reporting by replacing +// clean creates safe prefix for graphite reporting by replacing // some characters with underscores. // TODO(fs): This may need updating for other metrics backends. func clean(s string) string { diff --git a/registry/consul/service.go b/registry/consul/service.go index 18bd4c38d..e2205b24b 100644 --- a/registry/consul/service.go +++ b/registry/consul/service.go @@ -112,7 +112,7 @@ func serviceConfig(client *api.Client, name string, passing map[string]bool, tag addr = svc.Address } - // add .local suffix on OSX for simple host names w/o domain + // add .local suffix on OSX for simple host prefix w/o domain if runtime.GOOS == "darwin" && !strings.Contains(addr, ".") && !strings.HasSuffix(addr, ".local") { addr += ".local" } diff --git a/route/route.go b/route/route.go index 4b2c1a71e..7a59958a8 100644 --- a/route/route.go +++ b/route/route.go @@ -2,6 +2,7 @@ package route import ( "fmt" + "github.com/fabiolb/fabio/metrics4/names" "log" "net/url" "reflect" @@ -9,7 +10,6 @@ import ( "strconv" "strings" - "github.com/fabiolb/fabio/metrics4/names" "github.com/gobwas/glob" ) diff --git a/route/table_registry_test.go b/route/table_registry_test.go index 5607fe3fe..ee3ad7f6c 100644 --- a/route/table_registry_test.go +++ b/route/table_registry_test.go @@ -20,16 +20,16 @@ package route // } // // func newStubRegistry() metrics.Registry { -// return &stubRegistry{names: make(map[string]bool)} +// return &stubRegistry{prefix: make(map[string]bool)} // } // // type stubRegistry struct { -// names map[string]bool +// prefix map[string]bool // } // // func (p *stubRegistry) Names() []string { // n := []string{} -// for k := range p.names { +// for k := range p.prefix { // n = append(n, k) // } // sort.Strings(n) @@ -37,19 +37,19 @@ package route // } // // func (p *stubRegistry) Unregister(name string) { -// delete(p.names, name) +// delete(p.prefix, name) // } // // func (p *stubRegistry) UnregisterAll() { -// p.names = map[string]bool{} +// p.prefix = map[string]bool{} // } // // func (p *stubRegistry) GetCounter(name string) metrics.Counter { -// p.names[name] = true +// p.prefix[name] = true // return metrics.NoopCounter{} // } // // func (p *stubRegistry) GetTimer(name string) metrics.Timer { -// p.names[name] = true +// p.prefix[name] = true // return metrics.NoopTimer{} // } diff --git a/route/target.go b/route/target.go index 9e602e441..b5422893a 100644 --- a/route/target.go +++ b/route/target.go @@ -2,10 +2,9 @@ package route import ( "github.com/fabiolb/fabio/metrics4" + "github.com/fabiolb/fabio/metrics4/names" "net/url" "strings" - - "github.com/fabiolb/fabio/metrics4/names" ) type Target struct { @@ -51,7 +50,7 @@ type Target struct { Weight float64 // TODO(max): Isn't it the same as Requests timer in HTTPProxy - // TimerStruct measures throughput and latency of this target + // Timer measures throughput and latency of this target Timer metrics4.Timer // TimerName is the name of the timer in the metrics registry From 0851ff31fc27aea1653099e641cce247c70777e2 Mon Sep 17 00:00:00 2001 From: maximka777 Date: Tue, 18 Dec 2018 15:40:49 +0300 Subject: [PATCH 29/30] Fixed issues after renaming names -> prefix --- config/load_test.go | 7 -- logger/pattern.go | 6 +- metrics4/metrics.go | 2 +- metrics_old/circonus.go | 128 ---------------------------- metrics_old/circonus_test.go | 87 ------------------- metrics_old/gometrics.go | 85 ------------------- metrics_old/metrics.go | 157 ----------------------------------- metrics_old/metrics_test.go | 58 ------------- metrics_old/noop.go | 36 -------- metrics_old/registry.go | 54 ------------ registry/consul/service.go | 2 +- route/table_registry_test.go | 14 ++-- 12 files changed, 12 insertions(+), 624 deletions(-) delete mode 100644 metrics_old/circonus.go delete mode 100644 metrics_old/circonus_test.go delete mode 100644 metrics_old/gometrics.go delete mode 100644 metrics_old/metrics.go delete mode 100644 metrics_old/metrics_test.go delete mode 100644 metrics_old/noop.go delete mode 100644 metrics_old/registry.go diff --git a/config/load_test.go b/config/load_test.go index b4d6dfe1d..adfcb5d3a 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -646,13 +646,6 @@ func TestLoad(t *testing.T) { return cfg }, }, - { - args: []string{"-metrics.prefix", "some prefix"}, - cfg: func(cfg *Config) *Config { - cfg.Metrics.Names = "some prefix" - return cfg - }, - }, { args: []string{"-metrics.interval", "5ms"}, cfg: func(cfg *Config) *Config { diff --git a/logger/pattern.go b/logger/pattern.go index 2d1455e12..664bf74b6 100644 --- a/logger/pattern.go +++ b/logger/pattern.go @@ -326,9 +326,9 @@ func atoi(b *bytes.Buffer, i int64, pad int) { // parse parses a format string into a pattern based on the following rules: // -// The format string consists of text and fields. Field prefix start with a '$' -// and consist of ASCII characters [a-zA-Z0-9.-_]. Field prefix like -// '$header.name' will render the HTTP header 'name'. All other field prefix +// The format string consists of text and fields. Field names start with a '$' +// and consist of ASCII characters [a-zA-Z0-9.-_]. Field names like +// '$header.name' will render the HTTP header 'name'. All other field names // must exist in the fields map. func parse(format string, fields map[string]field) (p pattern, err error) { // text is a helper to add raw text to the log output. diff --git a/metrics4/metrics.go b/metrics4/metrics.go index d66df8d2d..e4948b280 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -17,7 +17,7 @@ type Timer = metrics.Histogram // Provider is an abstraction of a metrics backend. type Provider interface { // NewCounter creates a new counter object. - // labels - array of labels prefix + // labels - array of labels names NewCounter(name string, labelsNames ... string) Counter // NewGauge creates a new gauge object. diff --git a/metrics_old/circonus.go b/metrics_old/circonus.go deleted file mode 100644 index c4bfe8f69..000000000 --- a/metrics_old/circonus.go +++ /dev/null @@ -1,128 +0,0 @@ -package metrics - -import ( - "errors" - "fmt" - "log" - "os" - "sync" - "time" - - cgm "github.com/circonus-labs/circonus-gometrics" - "github.com/fabiolb/fabio/config" -) - -var ( - circonus *cgmRegistry - once sync.Once -) - -const serviceName = "fabio" - -// circonusRegistry returns a provider that reports to Circonus. -func circonusRegistry(prefix string, circ config.Circonus, interval time.Duration) (Registry, error) { - var initError error - - once.Do(func() { - if circ.APIKey == "" { - initError = errors.New("metrics: Circonus API token key") - return - } - - if circ.APIApp == "" { - circ.APIApp = serviceName - } - - host, err := os.Hostname() - if err != nil { - initError = fmt.Errorf("metrics: unable to initialize Circonus %s", err) - return - } - - cfg := &cgm.Config{} - - cfg.CheckManager.API.TokenKey = circ.APIKey - cfg.CheckManager.API.TokenApp = circ.APIApp - cfg.CheckManager.API.URL = circ.APIURL - cfg.CheckManager.Check.ID = circ.CheckID - cfg.CheckManager.Broker.ID = circ.BrokerID - cfg.Interval = fmt.Sprintf("%.0fs", interval.Seconds()) - cfg.CheckManager.Check.InstanceID = host - cfg.CheckManager.Check.DisplayName = fmt.Sprintf("%s /%s", host, serviceName) - cfg.CheckManager.Check.SearchTag = fmt.Sprintf("service:%s", serviceName) - - metrics, err := cgm.NewCirconusMetrics(cfg) - if err != nil { - initError = fmt.Errorf("metrics: unable to initialize Circonus %s", err) - return - } - - circonus = &cgmRegistry{metrics, prefix} - - metrics.Start() - - log.Print("[INFO] Sending metrics to Circonus") - }) - - return circonus, initError -} - -type cgmRegistry struct { - metrics *cgm.CirconusMetrics - prefix string -} - -// Names is not supported by Circonus. -func (m *cgmRegistry) Names() []string { return nil } - -// Unregister is implicitly supported by Circonus, -// stop submitting the metric and it stops being sent to Circonus. -func (m *cgmRegistry) Unregister(name string) {} - -// UnregisterAll is implicitly supported by Circonus, -// stop submitting metrics and they will no longer be sent to Circonus. -func (m *cgmRegistry) UnregisterAll() {} - -// GetCounter returns a counter for the given metric name. -func (m *cgmRegistry) GetCounter(name string) Counter { - metricName := fmt.Sprintf("%s`%s", m.prefix, name) - return &cgmCounter{m.metrics, metricName} -} - -// GetTimer returns a timer for the given metric name. -func (m *cgmRegistry) GetTimer(name string) Timer { - metricName := fmt.Sprintf("%s`%s", m.prefix, name) - return &cgmTimer{m.metrics, metricName} -} - -type cgmCounter struct { - metrics *cgm.CirconusMetrics - name string -} - -// Inc increases the counter by n. -func (c *cgmCounter) Inc(n int64) { - c.metrics.IncrementByValue(c.name, uint64(n)) -} - -type cgmTimer struct { - metrics *cgm.CirconusMetrics - name string -} - -// Percentile is not supported by Circonus. -func (t *cgmTimer) Percentile(nth float64) float64 { return 0 } - -// Rate1 is not supported by Circonus. -func (t *cgmTimer) Rate1() float64 { return 0 } - -func (t *cgmTimer) Update(d time.Duration) { - t.metrics.Timing(t.name, float64(d)) -} - -// UpdateSince adds delta between start and current time as -// a sample to a histogram. The histogram is created if it -// does not already exist. -func (t *cgmTimer) UpdateSince(start time.Time) { - t.metrics.Timing(t.name, float64(time.Since(start))) -} diff --git a/metrics_old/circonus_test.go b/metrics_old/circonus_test.go deleted file mode 100644 index 808b3e183..000000000 --- a/metrics_old/circonus_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package metrics - -import ( - "os" - "testing" - "time" - - "github.com/fabiolb/fabio/config" -) - -func TestRegistry(t *testing.T) { - t.Log("Testing registry interface") - - p := &cgmRegistry{} - - t.Log("\tNames()") - names := p.Names() - if names != nil { - t.Errorf("Expected nil got '%+v'", names) - } - - t.Log("\tUnregister()") - p.Unregister("foo") - - t.Log("\tUnregisterAll()") - p.UnregisterAll() - - t.Log("\tGetTimer()") - timer := p.GetTimer("foo") - if timer == nil { - t.Error("Expected a timer, got nil") - } -} - -func TestTimer(t *testing.T) { - t.Log("Testing timer interface") - - timer := &cgmTimer{} - - t.Log("\tPercentile()") - pct := timer.Percentile(99.9) - if pct != 0 { - t.Errorf("Expected 0 got '%+v'", pct) - } - - t.Log("\tRate1()") - rate := timer.Rate1() - if rate != 0 { - t.Errorf("Expected 0 got '%+v'", rate) - } -} - -func TestAll(t *testing.T) { - start := time.Now() - - if os.Getenv("CIRCONUS_API_TOKEN") == "" { - t.Skip("skipping test; $CIRCONUS_API_TOKEN not set") - } - - t.Log("Testing cgm functionality -- this *will* create/use a check") - - cfg := config.Circonus{ - APIKey: os.Getenv("CIRCONUS_API_TOKEN"), - APIApp: os.Getenv("CIRCONUS_API_APP"), - APIURL: os.Getenv("CIRCONUS_API_URL"), - CheckID: os.Getenv("CIRCONUS_CHECK_ID"), - BrokerID: os.Getenv("CIRCONUS_BROKER_ID"), - } - - interval, err := time.ParseDuration("60s") - if err != nil { - t.Fatalf("Unable to parse interval %+v", err) - } - - circ, err := circonusRegistry("test", cfg, interval) - if err != nil { - t.Fatalf("Unable to initialize Circonus +%v", err) - } - - counter := circ.GetCounter("fooCounter") - counter.Inc(3) - - timer := circ.GetTimer("fooTimer") - timer.UpdateSince(start) - - circonus.metrics.Flush() -} diff --git a/metrics_old/gometrics.go b/metrics_old/gometrics.go deleted file mode 100644 index e219f430e..000000000 --- a/metrics_old/gometrics.go +++ /dev/null @@ -1,85 +0,0 @@ -package metrics - -import ( - "errors" - "fmt" - "log" - "net" - "os" - "sort" - "time" - - graphite "github.com/cyberdelia/go-metrics-graphite" - statsd "github.com/magiconair/go-metrics-statsd" - gm "github.com/rcrowley/go-metrics" -) - -// gmStdoutRegistry returns a go-metrics registry that reports to stdout. -func gmStdoutRegistry(interval time.Duration) (Registry, error) { - logger := log.New(os.Stderr, "localhost: ", log.Lmicroseconds) - r := gm.NewRegistry() - go gm.Log(r, interval, logger) - return &gmRegistry{r}, nil -} - -// gmGraphiteRegistry returns a go-metrics registry that reports to a Graphite server. -func gmGraphiteRegistry(prefix, addr string, interval time.Duration) (Registry, error) { - if addr == "" { - return nil, errors.New(" graphite addr missing") - } - - a, err := net.ResolveTCPAddr("tcp", addr) - if err != nil { - return nil, fmt.Errorf(" cannot connect to Graphite: %s", err) - } - - r := gm.NewRegistry() - go graphite.Graphite(r, interval, prefix, a) - return &gmRegistry{r}, nil -} - -// gmStatsDRegistry returns a go-metrics registry that reports to a StatsD server. -func gmStatsDRegistry(prefix, addr string, interval time.Duration) (Registry, error) { - if addr == "" { - return nil, errors.New(" statsd addr missing") - } - - a, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, fmt.Errorf(" cannot connect to StatsD: %s", err) - } - - r := gm.NewRegistry() - go statsd.StatsD(r, interval, prefix, a) - return &gmRegistry{r}, nil -} - -// gmRegistry implements the Registry interface -// using the github.com/rcrowley/go-metrics library. -type gmRegistry struct { - r gm.Registry -} - -func (p *gmRegistry) Names() (names []string) { - p.r.Each(func(name string, _ interface{}) { - names = append(names, name) - }) - sort.Strings(names) - return names -} - -func (p *gmRegistry) Unregister(name string) { - p.r.Unregister(name) -} - -func (p *gmRegistry) UnregisterAll() { - p.r.UnregisterAll() -} - -func (p *gmRegistry) GetCounter(name string) Counter { - return gm.GetOrRegisterCounter(name, p.r) -} - -func (p *gmRegistry) GetTimer(name string) Timer { - return gm.GetOrRegisterTimer(name, p.r) -} diff --git a/metrics_old/metrics.go b/metrics_old/metrics.go deleted file mode 100644 index 205f9cd04..000000000 --- a/metrics_old/metrics.go +++ /dev/null @@ -1,157 +0,0 @@ -// Package metrics provides functions for collecting -// and managing metrics through different metrics libraries. -// -// Metrics library implementations must implement the -// Registry interface in the package. -package metrics - -import ( - "bytes" - "fmt" - "log" - "net/url" - "os" - "path/filepath" - "strings" - "text/template" - - "github.com/fabiolb/fabio/config" - "github.com/fabiolb/fabio/exit" -) - -// DefaultRegistry stores the metrics library provider. -var DefaultRegistry Registry = NoopRegistry{} - -// DefaultNames contains the default template for route metric prefix. -const DefaultNames = "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}" - -// DefaultPrefix contains the default template for metrics prefix. -const DefaultPrefix = "{{clean .Hostname}}.{{clean .Exec}}" - -// prefix stores the template for the route metric prefix. -var names *template.Template - -// prefix stores the final prefix string to use it with metric collectors where applicable, i.e. Graphite/StatsD -var prefix string - -func init() { - // make sure prefix is initialized to something - var err error - if names, err = parseNames(DefaultNames); err != nil { - panic(err) - } -} - -// NewRegistry creates a new metrics registry. -func NewRegistry(cfg config.Metrics) (r Registry, err error) { - - if prefix, err = parsePrefix(cfg.Prefix); err != nil { - return nil, fmt.Errorf("metrics: invalid Prefix template. %s", err) - } - - if names, err = parseNames(cfg.Names); err != nil { - return nil, fmt.Errorf("metrics: invalid prefix template. %s", err) - } - - switch cfg.Target { - case "stdout": - log.Printf("[INFO] Sending metrics to stdout") - return gmStdoutRegistry(cfg.Interval) - - case "graphite": - log.Printf("[INFO] Sending metrics to Graphite on %s as %q", cfg.GraphiteAddr, prefix) - return gmGraphiteRegistry(prefix, cfg.GraphiteAddr, cfg.Interval) - - case "statsd": - log.Printf("[INFO] Sending metrics to StatsD on %s as %q", cfg.StatsDAddr, prefix) - return gmStatsDRegistry(prefix, cfg.StatsDAddr, cfg.Interval) - - case "circonus": - return circonusRegistry(prefix, cfg.Circonus, cfg.Interval) - - default: - exit.Fatal("[FATAL] Invalid metrics target ", cfg.Target) - } - panic("unreachable") -} - -// parsePrefix parses the prefix metric template -func parsePrefix(tmpl string) (string, error) { - // Backward compatibility condition for old metrics.prefix parameter 'default' - if tmpl == "default" { - tmpl = DefaultPrefix - } - funcMap := template.FuncMap{ - "clean": clean, - } - t, err := template.New("prefix").Funcs(funcMap).Parse(tmpl) - if err != nil { - return "", err - } - host, err := hostname() - if err != nil { - return "", err - } - exe := filepath.Base(os.Args[0]) - - b := new(bytes.Buffer) - data := struct{ Hostname, Exec string }{host, exe} - if err := t.Execute(b, &data); err != nil { - return "", err - } - return b.String(), nil -} - -// parseNames parses the route metric name template. -func parseNames(tmpl string) (*template.Template, error) { - funcMap := template.FuncMap{ - "clean": clean, - } - t, err := template.New("prefix").Funcs(funcMap).Parse(tmpl) - if err != nil { - return nil, err - } - testURL, err := url.Parse("http://127.0.0.1:12345/") - if err != nil { - return nil, err - } - if _, err := TargetName("testservice", "test.example.com", "/test", testURL); err != nil { - return nil, err - } - return t, nil -} - -// TargetName returns the metrics name from the given parameters. -func TargetName(service, host, path string, targetURL *url.URL) (string, error) { - if names == nil { - return "", nil - } - - var name bytes.Buffer - - data := struct { - Service, Host, Path string - TargetURL *url.URL - }{service, host, path, targetURL} - - if err := names.Execute(&name, data); err != nil { - return "", err - } - - return name.String(), nil -} - -// clean creates safe prefix for graphite reporting by replacing -// some characters with underscores. -// TODO(fs): This may need updating for other metrics backends. -func clean(s string) string { - if s == "" { - return "_" - } - s = strings.Replace(s, ".", "_", -1) - s = strings.Replace(s, ":", "_", -1) - return strings.ToLower(s) -} - -// stubbed out for testing -var hostname = os.Hostname diff --git a/metrics_old/metrics_test.go b/metrics_old/metrics_test.go deleted file mode 100644 index 8f8daf47f..000000000 --- a/metrics_old/metrics_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package metrics - -import ( - "net/url" - "os" - "testing" -) - -func TestParsePrefix(t *testing.T) { - hostname = func() (string, error) { return "myhost", nil } - os.Args = []string{"./myapp"} - got, err := parsePrefix("{{clean .Hostname}}.{{clean .Exec}}") - if err != nil { - t.Fatalf("%v", err) - } - want := "myhost.myapp" - if got != want { - t.Errorf("ParsePrefix: got %v want %v", got, want) - } - - got, err = parsePrefix("default") - if err != nil { - t.Fatalf("%v", err) - } - want = "myhost.myapp" - if got != want { - t.Errorf("ParsePrefix Old default style: got %v want %v", got, want) - } -} - -func TestTargetName(t *testing.T) { - tests := []struct { - service, host, path, target string - name string - }{ - {"s", "h", "p", "http://foo.com/bar", "s.h.p.foo_com"}, - {"s", "", "p", "http://foo.com/bar", "s._.p.foo_com"}, - {"s", "", "", "http://foo.com/bar", "s._._.foo_com"}, - {"", "", "", "http://foo.com/bar", "_._._.foo_com"}, - {"", "", "", "http://foo.com:1234/bar", "_._._.foo_com_1234"}, - {"", "", "", "http://1.2.3.4:1234/bar", "_._._.1_2_3_4_1234"}, - } - - for i, tt := range tests { - u, err := url.Parse(tt.target) - if err != nil { - t.Fatalf("%d: %v", i, err) - } - - got, err := TargetName(tt.service, tt.host, tt.path, u) - if err != nil { - t.Fatalf("%d: %v", i, err) - } - if want := tt.name; got != want { - t.Errorf("%d: got %q want %q", i, got, want) - } - } -} diff --git a/metrics_old/noop.go b/metrics_old/noop.go deleted file mode 100644 index 738517f9f..000000000 --- a/metrics_old/noop.go +++ /dev/null @@ -1,36 +0,0 @@ -package metrics - -import "time" - -// NoopRegistry is a stub implementation of the Registry interface. -type NoopRegistry struct{} - -func (p NoopRegistry) Names() []string { return nil } - -func (p NoopRegistry) Unregister(name string) {} - -func (p NoopRegistry) UnregisterAll() {} - -func (p NoopRegistry) GetCounter(name string) Counter { return noopCounter } - -func (p NoopRegistry) GetTimer(name string) Timer { return noopTimer } - -var noopCounter = NoopCounter{} - -// NoopCounter is a stub implementation of the Counter interface. -type NoopCounter struct{} - -func (c NoopCounter) Inc(n int64) {} - -var noopTimer = NoopTimer{} - -// NoopTimer is a stub implementation of the Timer interface. -type NoopTimer struct{} - -func (t NoopTimer) Update(time.Duration) {} - -func (t NoopTimer) UpdateSince(time.Time) {} - -func (t NoopTimer) Rate1() float64 { return 0 } - -func (t NoopTimer) Percentile(nth float64) float64 { return 0 } diff --git a/metrics_old/registry.go b/metrics_old/registry.go deleted file mode 100644 index e60924a30..000000000 --- a/metrics_old/registry.go +++ /dev/null @@ -1,54 +0,0 @@ -package metrics - -import "time" - -// Registry defines an interface for metrics values which -// can be implemented by different metrics libraries. -// An implementation must be safe to be used by multiple -// go routines. -type Registry interface { - // Names returns the list of registered metrics acquired - // through the GetXXX() functions. It should return them - // sorted in alphabetical order. - Names() []string - - // Unregister removes the registered metric and stops - // reporting it to an external backend. - Unregister(name string) - - // UnregisterAll removes all registered metrics and stops - // reporting them to an external backend. - UnregisterAll() - - // GetCounter returns a counter metric for the given name. - // If the metric does not exist yet it should be created - // otherwise the existing metric should be returned. - GetCounter(name string) Counter - - // GetTimer returns a timer metric for the given name. - // If the metric does not exist yet it should be created - // otherwise the existing metric should be returned. - GetTimer(name string) Timer -} - -// Counter defines a metric for counting events. -type Counter interface { - // Inc increases the counter value by 'n'. - Inc(n int64) -} - -// Timer defines a metric for counting and timing durations for events. -type Timer interface { - // Percentile returns the nth percentile of the duration. - Percentile(nth float64) float64 - - // Rate1 returns the 1min rate. - Rate1() float64 - - // Update counts an event and records the duration. - Update(time.Duration) - - // UpdateSince counts an event and records the duration - // as the delta between 'start' and the function is called. - UpdateSince(start time.Time) -} diff --git a/registry/consul/service.go b/registry/consul/service.go index e2205b24b..18bd4c38d 100644 --- a/registry/consul/service.go +++ b/registry/consul/service.go @@ -112,7 +112,7 @@ func serviceConfig(client *api.Client, name string, passing map[string]bool, tag addr = svc.Address } - // add .local suffix on OSX for simple host prefix w/o domain + // add .local suffix on OSX for simple host names w/o domain if runtime.GOOS == "darwin" && !strings.Contains(addr, ".") && !strings.HasSuffix(addr, ".local") { addr += ".local" } diff --git a/route/table_registry_test.go b/route/table_registry_test.go index ee3ad7f6c..5607fe3fe 100644 --- a/route/table_registry_test.go +++ b/route/table_registry_test.go @@ -20,16 +20,16 @@ package route // } // // func newStubRegistry() metrics.Registry { -// return &stubRegistry{prefix: make(map[string]bool)} +// return &stubRegistry{names: make(map[string]bool)} // } // // type stubRegistry struct { -// prefix map[string]bool +// names map[string]bool // } // // func (p *stubRegistry) Names() []string { // n := []string{} -// for k := range p.prefix { +// for k := range p.names { // n = append(n, k) // } // sort.Strings(n) @@ -37,19 +37,19 @@ package route // } // // func (p *stubRegistry) Unregister(name string) { -// delete(p.prefix, name) +// delete(p.names, name) // } // // func (p *stubRegistry) UnregisterAll() { -// p.prefix = map[string]bool{} +// p.names = map[string]bool{} // } // // func (p *stubRegistry) GetCounter(name string) metrics.Counter { -// p.prefix[name] = true +// p.names[name] = true // return metrics.NoopCounter{} // } // // func (p *stubRegistry) GetTimer(name string) metrics.Timer { -// p.prefix[name] = true +// p.names[name] = true // return metrics.NoopTimer{} // } From 5d2d3c88984789d075be4969dabf12171a7a0ddd Mon Sep 17 00:00:00 2001 From: maximka777 Date: Tue, 18 Dec 2018 15:58:22 +0300 Subject: [PATCH 30/30] Refactored Prometheus prefix --- metrics4/metrics.go | 3 --- metrics4/prometheus/metrics.go | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/metrics4/metrics.go b/metrics4/metrics.go index e4948b280..26daee7ac 100644 --- a/metrics4/metrics.go +++ b/metrics4/metrics.go @@ -1,7 +1,6 @@ package metrics4 import ( - "github.com/fabiolb/fabio/metrics4/prefix" "github.com/go-kit/kit/metrics" "io" ) @@ -52,7 +51,6 @@ func (mp *MultiProvider) NewCounter(name string, labels ... string) Counter { // NewGauge creates a MultiGauge with gauge objects for all registered // providers. func (mp *MultiProvider) NewGauge(name string, labels ... string) Gauge { - name = prefix.GetPrefixedName(name) g := make([]Gauge, len(mp.p)) for i, p := range mp.p { g[i] = p.NewGauge(name, labels...) @@ -63,7 +61,6 @@ func (mp *MultiProvider) NewGauge(name string, labels ... string) Gauge { // NewTimer creates a MultiTimer with timer objects for all registered // providers. func (mp *MultiProvider) NewTimer(name string, labels ... string) Timer { - name = prefix.GetPrefixedName(name) t := make([]Timer, len(mp.p)) for i, p := range mp.p { t[i] = p.NewTimer(name, labels...) diff --git a/metrics4/prometheus/metrics.go b/metrics4/prometheus/metrics.go index cd4b301e7..790c061c3 100644 --- a/metrics4/prometheus/metrics.go +++ b/metrics4/prometheus/metrics.go @@ -17,7 +17,9 @@ type Provider struct { } func normalizeName(name string) string { - return strings.Replace(name, ".", "_", -1) + name = strings.Replace(name, ".", "_", -1) + name = strings.Replace(name, "-", "_", -1) + return name } func NewProvider(prefix string) metrics4.Provider {