-
Notifications
You must be signed in to change notification settings - Fork 0
/
measurable.go
189 lines (154 loc) · 5.41 KB
/
measurable.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// Package measurable provides a functionality-free integration nexus for
// metric registration.
//
// Measurable is a Go package for connecting service metrics and metric consumers.
//
// The most noteworthy feature of measurable is that it doesn't do anything.
// It contains no functionality for defining or exporting metrics.
//
// The purpose of measurable is to act as an integration nexus
// (https://www.devever.net/~hl/nexuses), essentially a matchmaker between
// application metrics and metric consumers. This creates the important feature
// that your application's metrics can be defined completely independently of
// *how* those metrics are defined.
//
// Measurable doesn't implement any metric definition or export logic because it
// strives to be a neutral intermediary, which abstracts the interface between
// measurables and measurable consumers
//
// Pursuant to this, package measurable is this and only this: an interface
// Measurable which all metrics must implement, and a facility for registering
// Measurables and visiting them.
package measurable // import "gopkg.in/hlandau/measurable.v1"
import "sync"
import "fmt"
// Measurable is the interface which must be implemented by any metric item to
// be used with package measurable. In the current version, v1, it contains
// only the MsName() and MsType() methods. All other functionality must be
// obtained by interface upgrades.
type Measurable interface {
// Returns the name of the metric. Names should be in the style
// "alpha.beta.gamma-delta", for example "foo.http.requests.count". That is,
// names should be lowercase, should express a hierarchy separated by dots,
// and have words separated by dashes.
//
// Some Measurable consumers may mutate these names to satisfy naming
// restrictions applied by some graphing systems.
MsName() string
// Return the Measurable type. You can, of course, invent your own Measurable
// types, though consumers won't necessarily know what to do with them.
MsType() Type
}
var measurablesMutex sync.RWMutex
var measurables = map[string]Measurable{}
// Registers a top-level Configurable.
func Register(measurable Measurable) {
measurablesMutex.Lock()
defer measurablesMutex.Unlock()
if measurable == nil {
panic("cannot register nil measurable")
}
name := measurable.MsName()
if name == "" {
panic("measurable cannot have empty name")
}
_, exists := measurables[name]
if exists {
panic(fmt.Sprintf("A measurable with the same name already exists: %s", name))
}
measurables[name] = measurable
callRegistrationHooks(measurable, RegisterEvent)
}
func Unregister(measurableName string) {
measurablesMutex.Lock()
defer measurablesMutex.Unlock()
measurable, ok := measurables[measurableName]
if !ok {
return
}
callRegistrationHooks(measurable, UnregisterEvent)
delete(measurables, measurableName)
}
func Get(measurableName string) Measurable {
measurablesMutex.RLock()
defer measurablesMutex.RUnlock()
return measurables[measurableName]
}
// Visits all registered top-level Measurables.
//
// Returning a non-nil error short-circuits the iteration process and returns
// that error.
func Visit(do func(measurable Measurable) error) error {
measurablesMutex.Lock()
defer measurablesMutex.Unlock()
for _, measurable := range measurables {
err := do(measurable)
if err != nil {
return err
}
}
return nil
}
// Represents a measurable type.
type Type uint32
const (
// A CounterType Measurable represents a non-negative integral value
// that monotonously increases. It must implement `MsInt64() int64`.
CounterType Type = 0x436E7472
// A GaugeType Measurable represents an integral value that varies over
// time. It must implement `MsInt64() int64`.
GaugeType = 0x47617567
)
// Registration hooks.
type HookEvent int
const (
// This event is issued when a measurable is registered.
RegisterEvent HookEvent = iota
// This event is issued when a registration hook is registered. It is issued
// for every measurable which has already been registered.
RegisterCatchupEvent
// This event is issued when a measurable is unregistered.
UnregisterEvent
)
type HookFunc func(measurable Measurable, hookEvent HookEvent)
var hooksMutex sync.RWMutex
var hooks = map[interface{}]HookFunc{}
// Register for notifications on metric registration. The key must be usable as
// a key in a map and identifies the hook. No other hook with the same key must
// already exist.
//
// NOTE: The hook will be called for all registrations which already exist.
// This ensures that no registrations are missed in a threadsafe manner.
// For these calls, the event will be EventRegisterCatchup.
//
// The hook must not register or unregister registration hooks or metrics.
func RegisterHook(key interface{}, hook HookFunc) {
measurablesMutex.RLock()
defer measurablesMutex.RUnlock()
registerHook(key, hook)
for _, m := range measurables {
hook(m, RegisterCatchupEvent)
}
}
func registerHook(key interface{}, hook HookFunc) {
hooksMutex.Lock()
defer hooksMutex.Unlock()
_, exists := hooks[key]
if exists {
panic(fmt.Sprintf("A metric registration hook with the same key already exists: %+v", key))
}
hooks[key] = hook
}
// Unregister an existing hook.
func UnregisterHook(key interface{}) {
hooksMutex.Lock()
defer hooksMutex.Unlock()
delete(hooks, key)
}
func callRegistrationHooks(measurable Measurable, event HookEvent) {
hooksMutex.RLock()
defer hooksMutex.RUnlock()
for _, v := range hooks {
v(measurable, event)
}
}