Skip to content

Commit

Permalink
PR #154: Make route metric names configurable
Browse files Browse the repository at this point in the history
This patch makes the metric names for the service routes
configurable by using a template to generate them.
  • Loading branch information
maier authored and magiconair committed Sep 2, 2016
1 parent 18fb6d6 commit fc624c9
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 9 deletions.
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type Runtime struct {
type Metrics struct {
Target string
Prefix string
Names string
Interval time.Duration
GraphiteAddr string
StatsDAddr string
Expand Down
1 change: 1 addition & 0 deletions config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var Default = &Config{
},
Metrics: Metrics{
Prefix: "default",
Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}",
Interval: 30 * time.Second,
CirconusAPIApp: "fabio",
},
Expand Down
1 change: 1 addition & 0 deletions config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func load(p *properties.Properties) (cfg *Config, err error) {
f.DurationVar(&cfg.Proxy.FlushInterval, "proxy.flushinterval", Default.Proxy.FlushInterval, "flush interval for streaming responses")
f.StringVar(&cfg.Metrics.Target, "metrics.target", Default.Metrics.Target, "metrics backend")
f.StringVar(&cfg.Metrics.Prefix, "metrics.prefix", Default.Metrics.Prefix, "prefix for reported metrics")
f.StringVar(&cfg.Metrics.Names, "metrics.names", Default.Metrics.Names, "route metric name template")
f.DurationVar(&cfg.Metrics.Interval, "metrics.interval", Default.Metrics.Interval, "metrics reporting interval")
f.StringVar(&cfg.Metrics.GraphiteAddr, "metrics.graphite.addr", Default.Metrics.GraphiteAddr, "graphite server address")
f.StringVar(&cfg.Metrics.StatsDAddr, "metrics.statsd.addr", Default.Metrics.StatsDAddr, "statsd server address")
Expand Down
2 changes: 2 additions & 0 deletions config/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ registry.consul.register.checkTimeout = 10s
registry.consul.service.status = a,b
metrics.target = graphite
metrics.prefix = someprefix
metrics.names = {{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}
metrics.interval = 5s
metrics.graphite.addr = 5.6.7.8:9999
metrics.statsd.addr = 6.7.8.9:9999
Expand Down Expand Up @@ -125,6 +126,7 @@ aws.apigw.cert.cn = furb
Metrics: Metrics{
Target: "graphite",
Prefix: "someprefix",
Names: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}",
Interval: 5 * time.Second,
GraphiteAddr: "5.6.7.8:9999",
StatsDAddr: "6.7.8.9:9999",
Expand Down
34 changes: 34 additions & 0 deletions fabio.properties
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,40 @@
# metrics.prefix = default


# metrics.names configures the template for the route metric names.
# The value is expanded by the text/template package and provides
# the following variables:
#
# - Service: the service name
# - Host: the host part of the URL prefix
# - Path: the path part of the URL prefix
# - TargetURL: the URL of the target
#
# The following additional functions are defined:
#
# - clean: lowercase value and replace '.' and ':' with '_'
#
# Given a route rule of
#
# route add testservice www.example.com/ http://10.1.2.3:12345/
#
# the template variables are:
#
# .Service = testservice
# .Host = www.example.com
# .Path = /
# .TargetURL.Host = 10.1.2.3:12345
#
# which results to the following metric name when using the
# default template:
#
# testservice.www_example_com./.10_1_2_3_12345
#
# The default is
#
# metrics.names = {{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}


# metrics.interval configures the interval in which metrics are
# reported.
#
Expand Down
64 changes: 57 additions & 7 deletions metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
package metrics

import (
"bytes"
"fmt"
"log"
"net/url"
"os"
"path/filepath"
"strings"
"text/template"

"github.com/eBay/fabio/config"
"github.com/eBay/fabio/exit"
Expand All @@ -19,13 +22,31 @@ import (
// DefaultRegistry stores the metrics library provider.
var DefaultRegistry Registry = NoopRegistry{}

// DefaultNames contains the default template for route metric names.
const DefaultNames = "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}"

// names stores the template for the route metric names.
var names *template.Template

func init() {
// make sure names 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) {
prefix := cfg.Prefix
if prefix == "default" {
prefix = defaultPrefix()
}

if names, err = parseNames(cfg.Names); err != nil {
return nil, fmt.Errorf("metrics: invalid names template. %s", err)
}

switch cfg.Target {
case "stdout":
log.Printf("[INFO] Sending metrics to stdout")
Expand Down Expand Up @@ -54,14 +75,43 @@ func NewRegistry(cfg config.Metrics) (r Registry, err error) {
panic("unreachable")
}

// parseNames parses the route metric name template.
func parseNames(tmpl string) (*template.Template, error) {
funcMap := template.FuncMap{
"clean": clean,
}
t, err := template.New("names").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 {
return strings.Join([]string{
clean(service),
clean(host),
clean(path),
clean(targetURL.Host),
}, ".")
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 names for graphite reporting by replacing
Expand Down
7 changes: 6 additions & 1 deletion metrics/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ func TestTargetName(t *testing.T) {
if err != nil {
t.Fatalf("%d: %v", i, err)
}
if got, want := TargetName(tt.service, tt.host, tt.path, u), tt.name; got != want {

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)
}
}
Expand Down
7 changes: 6 additions & 1 deletion route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package route

import (
"fmt"
"log"
"net/url"
"sort"
"strings"
Expand Down Expand Up @@ -46,7 +47,11 @@ func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float6
fixedWeight = 0
}

name := metrics.TargetName(service, r.Host, r.Path, targetURL)
name, err := metrics.TargetName(service, r.Host, r.Path, targetURL)
if err != nil {
log.Printf("[ERROR] Invalid metrics name: %s", err)
name = "unknown"
}
timer := ServiceRegistry.GetTimer(name)

t := &Target{Service: service, Tags: tags, URL: targetURL, FixedWeight: fixedWeight, Timer: timer, timerName: name}
Expand Down

0 comments on commit fc624c9

Please sign in to comment.