-
Notifications
You must be signed in to change notification settings - Fork 616
/
metrics.go
157 lines (131 loc) · 4.02 KB
/
metrics.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 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 names.
const DefaultNames = "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}"
// DefaulPrefix contains the default template for metrics prefix.
const DefaultPrefix = "{{clean .Hostname}}.{{clean .Exec}}"
// names stores the template for the route metric names.
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
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 names 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("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, 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
// 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