From 7249c29b86ac36e568cc9994add22e779fdc5b07 Mon Sep 17 00:00:00 2001 From: matt maier <maier@users.noreply.github.com> Date: Wed, 31 Aug 2016 14:18:12 -0400 Subject: [PATCH 1/2] Add user configurable route metric name capability --- config/config.go | 21 +++++++++++---------- config/default.go | 7 ++++--- config/load.go | 1 + config/load_test.go | 22 ++++++++++++---------- fabio.properties | 21 +++++++++++++++++++++ metrics/metrics.go | 17 +++++++++++------ 6 files changed, 60 insertions(+), 29 deletions(-) diff --git a/config/config.go b/config/config.go index d3b2a7d3a..877ce64c2 100644 --- a/config/config.go +++ b/config/config.go @@ -68,16 +68,17 @@ type Runtime struct { } type Metrics struct { - Target string - Prefix string - Interval time.Duration - GraphiteAddr string - StatsDAddr string - CirconusAPIKey string - CirconusAPIApp string - CirconusAPIURL string - CirconusCheckID string - CirconusBrokerID string + Target string + Prefix string + Interval time.Duration + GraphiteAddr string + StatsDAddr string + CirconusAPIKey string + CirconusAPIApp string + CirconusAPIURL string + CirconusCheckID string + CirconusBrokerID string + RouteMetricNameTemplate string } type Registry struct { diff --git a/config/default.go b/config/default.go index 48cd63ec9..1f7ce0c57 100644 --- a/config/default.go +++ b/config/default.go @@ -40,9 +40,10 @@ var Default = &Config{ Color: "light-green", }, Metrics: Metrics{ - Prefix: "default", - Interval: 30 * time.Second, - CirconusAPIApp: "fabio", + Prefix: "default", + Interval: 30 * time.Second, + CirconusAPIApp: "fabio", + RouteMetricNameTemplate: "$service$.$host$.$path$.$target$", }, CertSources: map[string]CertSource{}, } diff --git a/config/load.go b/config/load.go index 4a174b4d4..102d711fc 100644 --- a/config/load.go +++ b/config/load.go @@ -115,6 +115,7 @@ func load(p *properties.Properties) (cfg *Config, err error) { f.StringVar(&cfg.Metrics.CirconusAPIURL, "metrics.circonus.apiurl", Default.Metrics.CirconusAPIURL, "Circonus API URL") f.StringVar(&cfg.Metrics.CirconusBrokerID, "metrics.circonus.brokerid", Default.Metrics.CirconusBrokerID, "Circonus Broker ID") f.StringVar(&cfg.Metrics.CirconusCheckID, "metrics.circonus.checkid", Default.Metrics.CirconusCheckID, "Circonus Check ID") + f.StringVar(&cfg.Metrics.RouteMetricNameTemplate, "metrics.routemetricnametemplate", Default.Metrics.RouteMetricNameTemplate, "route metric name template") f.StringVar(&cfg.Registry.Backend, "registry.backend", Default.Registry.Backend, "registry backend") f.StringVar(&cfg.Registry.File.Path, "registry.file.path", Default.Registry.File.Path, "path to file based routing table") f.StringVar(&cfg.Registry.Static.Routes, "registry.static.routes", Default.Registry.Static.Routes, "static routes") diff --git a/config/load_test.go b/config/load_test.go index ca93a0fd9..2a455e9b4 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -53,6 +53,7 @@ metrics.circonus.apiapp = circonus-apiapp metrics.circonus.apiurl = circonus-apiurl metrics.circonus.brokerid = circonus-brokerid metrics.circonus.checkid = circonus-checkid +metrics.routemetricnametemplate = $service$.$host$.$path$.$target$ runtime.gogc = 666 runtime.gomaxprocs = 12 ui.addr = 7.8.9.0:1234 @@ -123,16 +124,17 @@ aws.apigw.cert.cn = furb }, }, Metrics: Metrics{ - Target: "graphite", - Prefix: "someprefix", - Interval: 5 * time.Second, - GraphiteAddr: "5.6.7.8:9999", - StatsDAddr: "6.7.8.9:9999", - CirconusAPIKey: "circonus-apikey", - CirconusAPIApp: "circonus-apiapp", - CirconusAPIURL: "circonus-apiurl", - CirconusBrokerID: "circonus-brokerid", - CirconusCheckID: "circonus-checkid", + Target: "graphite", + Prefix: "someprefix", + Interval: 5 * time.Second, + GraphiteAddr: "5.6.7.8:9999", + StatsDAddr: "6.7.8.9:9999", + CirconusAPIKey: "circonus-apikey", + CirconusAPIApp: "circonus-apiapp", + CirconusAPIURL: "circonus-apiurl", + CirconusBrokerID: "circonus-brokerid", + CirconusCheckID: "circonus-checkid", + RouteMetricNameTemplate: "$service$.$host$.$path$.$target$", }, Runtime: Runtime{ GOGC: 666, diff --git a/fabio.properties b/fabio.properties index a0150f554..dc0757f43 100644 --- a/fabio.properties +++ b/fabio.properties @@ -564,6 +564,27 @@ # metrics.circonus.checkid = +# metrics.routemetricnametemplate provides the ability to customize +# the route metric names. Variables available for template expansion +# are: service host path and target. +# +# Given a route rule of: route add testservice www.example.com/ http://10.1.2.3:12345/ +# The template variables would be: +# +# $service$=testservice +# $host$=www_example_com +# $path$=/ +# $target$=10_1_2_3_12345 +# +# The resulting metric name (using the default template): +# +# testservice.www_example_com./.10_1_2_3_12345 +# +# The default is +# +# metrics.routemetricnametemplate = $service$.$host$.$path$.$target$ + + # runtime.gogc configures GOGC (the GC target percentage). # # Setting runtime.gogc is equivalent to setting the GOGC diff --git a/metrics/metrics.go b/metrics/metrics.go index 0bc1c8ef7..d09b1b7eb 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -19,6 +19,8 @@ import ( // DefaultRegistry stores the metrics library provider. var DefaultRegistry Registry = NoopRegistry{} +var routeMetricNameTemplate string + // NewRegistry creates a new metrics registry. func NewRegistry(cfg config.Metrics) (r Registry, err error) { prefix := cfg.Prefix @@ -26,6 +28,8 @@ func NewRegistry(cfg config.Metrics) (r Registry, err error) { prefix = defaultPrefix() } + routeMetricNameTemplate = cfg.RouteMetricNameTemplate + switch cfg.Target { case "stdout": log.Printf("[INFO] Sending metrics to stdout") @@ -56,12 +60,13 @@ func NewRegistry(cfg config.Metrics) (r Registry, err error) { // 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), - }, ".") + name := routeMetricNameTemplate + name = strings.Replace(name, "$service$", clean(service), -1) + name = strings.Replace(name, "$host$", clean(host), -1) + name = strings.Replace(name, "$path$", clean(path), -1) + name = strings.Replace(name, "$target$", clean(targetURL.Host), -1) + + return name } // clean creates safe names for graphite reporting by replacing From 9caa276909cca6985050e8bb8cf437d4fd4f07a0 Mon Sep 17 00:00:00 2001 From: matt maier <maier@users.noreply.github.com> Date: Thu, 1 Sep 2016 10:43:14 -0400 Subject: [PATCH 2/2] Update to use text/template --- config/default.go | 2 +- config/load_test.go | 4 +-- fabio.properties | 12 +++++--- metrics/metrics.go | 67 +++++++++++++++++++++++++++++++++++------ metrics/metrics_test.go | 19 +++++++++++- route/route.go | 2 +- 6 files changed, 86 insertions(+), 20 deletions(-) diff --git a/config/default.go b/config/default.go index 1f7ce0c57..3c76d440f 100644 --- a/config/default.go +++ b/config/default.go @@ -43,7 +43,7 @@ var Default = &Config{ Prefix: "default", Interval: 30 * time.Second, CirconusAPIApp: "fabio", - RouteMetricNameTemplate: "$service$.$host$.$path$.$target$", + RouteMetricNameTemplate: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", }, CertSources: map[string]CertSource{}, } diff --git a/config/load_test.go b/config/load_test.go index 2a455e9b4..15eb0af3f 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -53,7 +53,7 @@ metrics.circonus.apiapp = circonus-apiapp metrics.circonus.apiurl = circonus-apiurl metrics.circonus.brokerid = circonus-brokerid metrics.circonus.checkid = circonus-checkid -metrics.routemetricnametemplate = $service$.$host$.$path$.$target$ +metrics.routemetricnametemplate = {{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}} runtime.gogc = 666 runtime.gomaxprocs = 12 ui.addr = 7.8.9.0:1234 @@ -134,7 +134,7 @@ aws.apigw.cert.cn = furb CirconusAPIURL: "circonus-apiurl", CirconusBrokerID: "circonus-brokerid", CirconusCheckID: "circonus-checkid", - RouteMetricNameTemplate: "$service$.$host$.$path$.$target$", + RouteMetricNameTemplate: "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", }, Runtime: Runtime{ GOGC: 666, diff --git a/fabio.properties b/fabio.properties index dc0757f43..cb5845a0d 100644 --- a/fabio.properties +++ b/fabio.properties @@ -571,10 +571,12 @@ # Given a route rule of: route add testservice www.example.com/ http://10.1.2.3:12345/ # The template variables would be: # -# $service$=testservice -# $host$=www_example_com -# $path$=/ -# $target$=10_1_2_3_12345 +# .Service = testservice +# .Host = www.example.com +# .Path = / +# .TargetURL.Host = 10.1.2.3:12345 +# +# Function: clean - lowercases value and replaces '.' and ':' with '_' # # The resulting metric name (using the default template): # @@ -582,7 +584,7 @@ # # The default is # -# metrics.routemetricnametemplate = $service$.$host$.$path$.$target$ +# metrics.routemetricnametemplate = {{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}} # runtime.gogc configures GOGC (the GC target percentage). diff --git a/metrics/metrics.go b/metrics/metrics.go index d09b1b7eb..5b16ddd20 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -6,11 +6,13 @@ package metrics import ( + "bytes" "log" "net/url" "os" "path/filepath" "strings" + "text/template" "github.com/eBay/fabio/config" "github.com/eBay/fabio/exit" @@ -19,7 +21,12 @@ import ( // DefaultRegistry stores the metrics library provider. var DefaultRegistry Registry = NoopRegistry{} -var routeMetricNameTemplate string +var routeMetricNameTemplate *template.Template + +type routeMetricNameAttributes struct { + Service, Host, Path string + TargetURL *url.URL +} // NewRegistry creates a new metrics registry. func NewRegistry(cfg config.Metrics) (r Registry, err error) { @@ -28,7 +35,17 @@ func NewRegistry(cfg config.Metrics) (r Registry, err error) { prefix = defaultPrefix() } - routeMetricNameTemplate = cfg.RouteMetricNameTemplate + funcMap := template.FuncMap{ + "clean": clean, + } + + routeMetricNameTemplate, err = template.New("routeMetricName").Funcs(funcMap).Parse(cfg.RouteMetricNameTemplate) + if err != nil { + return nil, err + } + if err := verifyTemplate(); err != nil { + return nil, err + } switch cfg.Target { case "stdout": @@ -59,14 +76,21 @@ func NewRegistry(cfg config.Metrics) (r Registry, err error) { } // TargetName returns the metrics name from the given parameters. -func TargetName(service, host, path string, targetURL *url.URL) string { - name := routeMetricNameTemplate - name = strings.Replace(name, "$service$", clean(service), -1) - name = strings.Replace(name, "$host$", clean(host), -1) - name = strings.Replace(name, "$path$", clean(path), -1) - name = strings.Replace(name, "$target$", clean(targetURL.Host), -1) - - return name +func TargetName(service, host, path string, targetURL *url.URL) (string, error) { + var name bytes.Buffer + + data := &routeMetricNameAttributes{ + Service: service, + Host: host, + Path: path, + TargetURL: targetURL, + } + + if err := routeMetricNameTemplate.Execute(&name, data); err != nil { + return "", err + } + + return name.String(), nil } // clean creates safe names for graphite reporting by replacing @@ -94,3 +118,26 @@ func defaultPrefix() string { exe := filepath.Base(os.Args[0]) return clean(host) + "." + clean(exe) } + +// verifyTemplate checks the route metric name template syntax +func verifyTemplate() error { + var name bytes.Buffer + + testURL, err := url.Parse("http://127.0.0.1:12345/") + if err != nil { + return err + } + + data := &routeMetricNameAttributes{ + Service: "testservice", + Host: "test.example.com", + Path: "/test", + TargetURL: testURL, + } + + if err := routeMetricNameTemplate.Execute(&name, data); err != nil { + return err + } + + return nil +} diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index 5783ed610..184254ee3 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -4,6 +4,7 @@ import ( "net/url" "os" "testing" + "text/template" ) func TestDefaultPrefix(t *testing.T) { @@ -27,12 +28,28 @@ func TestTargetName(t *testing.T) { {"", "", "", "http://1.2.3.4:1234/bar", "_._._.1_2_3_4_1234"}, } + funcMap := template.FuncMap{ + "clean": clean, + } + + var err error + ts := "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}" + routeMetricNameTemplate, err = template.New("routeMetricName").Funcs(funcMap).Parse(ts) + if err != nil { + t.Fatalf("Template %s: %v", ts, err) + } + for i, tt := range tests { u, err := url.Parse(tt.target) 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) } } diff --git a/route/route.go b/route/route.go index 23b602704..1a026a569 100644 --- a/route/route.go +++ b/route/route.go @@ -46,7 +46,7 @@ func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float6 fixedWeight = 0 } - name := metrics.TargetName(service, r.Host, r.Path, targetURL) + name, _ := metrics.TargetName(service, r.Host, r.Path, targetURL) timer := ServiceRegistry.GetTimer(name) t := &Target{Service: service, Tags: tags, URL: targetURL, FixedWeight: fixedWeight, Timer: timer, timerName: name}