forked from Symantec/tricorder
/
api.go
433 lines (388 loc) · 16.9 KB
/
api.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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
package tricorder
import (
"errors"
"github.com/Symantec/tricorder/go/tricorder/messages"
"github.com/Symantec/tricorder/go/tricorder/units"
"time"
)
// CollectorServiceName contains the name of the service that collects tricorder
// metrics. This is used in environments where ingress to applications is
// routinely blocked, and the applications need to call out to the collector.
// See the github.com/Symantec/Dominator/lib/net/reverseconnection package for
// more information.
const CollectorServiceName = "Scotty"
var (
// RegisterMetric returns this if given path is already in use.
ErrPathInUse = errors.New("tricorder: Path in use")
// RegisterMetric returns this if passed unit is wrong.
ErrWrongUnit = errors.New("tricorder: Wrong unit")
// RegisterMetric returns this if passed metric type is not supported.
ErrWrongType = errors.New("tricorder: Metric not of a valid type")
)
// DirectoryGroup combines a group and directory for the purpose of
// registering metrics.
type DirectoryGroup struct {
Group *Group
Directory *DirectorySpec
}
// RegisterMetric works just like the package level RegisterMetric
// except that path is relative to dg.Directory, and the metric being
// registered becomes part of the dg.Group group.
func (dg DirectoryGroup) RegisterMetric(
path string,
metric interface{},
unit units.Unit,
description string) error {
return dg.Directory.RegisterMetricInGroup(
path, metric, dg.Group, unit, description)
}
// A group represents a collection of variables for metrics that are all
// updated by a common function. Each time a client sends a request for one or
// more metrics backed by variables within a particular group, tricorder
// calls that group’s update function one time before reading any of the
// variables in that group to respond to the client. However, to provide
// a consistent view of the variables within a group, tricorder will never
// call a group’s update function once it has begun reading variables in that
// group to service an in-process request. If tricorder does happen to
// receive an incoming request for metrics from a given group after tricorder
// has begun reading variables in that same group to service another
// in-process request, tricorder will skip calling the group’s update
// function for the incoming request. In this case, the two requests will
// read the same data from that group.
type Group region
var (
// The default group. Its update function does nothing and returns
// the current system time.
DefaultGroup = NewGroup()
)
// NewGroup creates a new group with the default update function.
// The default update function does nothing and returns the current system
// time.
func NewGroup() *Group {
return (*Group)(newDefaultRegion())
}
// RegisterUpdateFunc registers an update function with group while
// clearing any previously registered update function
func (g *Group) RegisterUpdateFunc(updateFunc func() time.Time) {
(*region)(g).registerUpdateFunc(updateFunc)
}
// RegisterMetric registers metric in this group. It is the same as
// calling RegisterMetricInGroup(path, metric, g, unit, description)
func (g *Group) RegisterMetric(
path string,
metric interface{},
unit units.Unit,
description string) error {
return root.registerMetric(
newPathSpec(path), metric, (*region)(g), unit, description)
}
// ReadMyMetrics reads all the current tricorder metrics in this process
// at or under path. If no metrics found under path, ReadMyMetrics returns
// an empty slice
func ReadMyMetrics(path string) messages.MetricList {
return readMyMetrics(path)
}
// RegisterMetric registers a single metric with the health system in the
// default group.
//
// If metric is a distribution type such as *CumulativeDistribution or
// *NonCumulativeDistribution that has no assigned unit, then RegisterMetric
// makes unit be the assigned unit of the distribution being registered.
//
// path is the absolute path of the metric e.g "/proc/rpc";
// metric is the metric to register;
// unit is the unit of measurement for the metric;
// description is the description of the metric.
//
// RegisterMetric returns ErrPathInUse if path already represents a metric
// or a directory.
// RegisterMetric returns ErrWrongUnit if metric is a distribution type
// such as *CumulativeDistribution or *NonCumulativeDistribution that already
// has an assigned unit and unit does not match that assigned unit.
// RegisterMetric returns ErrWrongType if metric is not of a valid type.
func RegisterMetric(
path string,
metric interface{},
unit units.Unit,
description string) error {
return root.registerMetric(
newPathSpec(path),
metric,
(*region)(DefaultGroup),
unit,
description)
}
// RegisterMetricInGroup works just like RegisterMetric but allows
// the caller to specify the group to which the variable or callback function
// being registered belongs.
func RegisterMetricInGroup(
path string,
metric interface{},
g *Group,
unit units.Unit,
description string) error {
return root.registerMetric(
newPathSpec(path), metric, (*region)(g), unit, description)
}
// UnregisterPath unregisters the metric or DirectorySpec at the given path.
// UnregisterPath ignores requests to unregister the root path.
func UnregisterPath(path string) {
root.unregisterPath(newPathSpec(path))
}
// Bucketer represents the organization of values into buckets for
// distributions. Bucketer instances are immutable.
type Bucketer struct {
pieces []*bucketPiece
}
var (
// Ranges in powers of two
PowersOfTwo = NewExponentialBucketer(20, 1.0, 2.0)
// Ranges in powers of four
PowersOfFour = NewExponentialBucketer(11, 1.0, 4.0)
// Ranges in powers of 10
PowersOfTen = NewExponentialBucketer(7, 1.0, 10.0)
)
// NewExponentialBucketer returns a Bucketer representing buckets on
// a geometric scale. NewExponentialBucketer(25, 3.0, 1.7) means 25 buckets
// starting with <3.0; 3.0 - 5.1; 5.1 - 8.67; 8.67 - 14.739 etc.
// NewExponentialBucketer panics if count < 2 or if start <= 0 or if scale <= 1.
func NewExponentialBucketer(count int, start, scale float64) *Bucketer {
return newBucketerFromEndpoints(
newExponentialBucketerStream(count, start, scale))
}
// NewLinearBucketer returns a Bucketer representing bucktes on
// a linear scale. NewLinearBucketer(5, 0, 10) means 5 buckets
// starting with <0; 0-10; 10-20; 20-30; >=30.
// NewLinearBucketer panics if count < 2 or if increment <= 0.
func NewLinearBucketer(count int, start, increment float64) *Bucketer {
return newBucketerFromEndpoints(
newLinearBucketerStream(count, start, increment))
}
// NewArbitraryBucketer returns a Bucketer representing specific endpoints
// NewArbitraryBucketer(10.0, 20.0, 30.0) means 4 buckets:
// <10.0; 10.0 - 20.0; 20.0 - 30.0; >= 30.0.
// NewArbitraryBucketer panics if it is called with no arguments.
// It is the caller's responsibility to ensure that the arguments are in
// ascending order.
func NewArbitraryBucketer(endpoints ...float64) *Bucketer {
return newBucketerFromEndpoints(endpoints)
}
// NewGeometricBucketer returns a Bucketer representing endpoints
// of the form 10^k, 2*10^k, 5*10^k. lower is the lower bound of
// the endpoints; upper is the upper bound of the endpoints.
// NewGeometricBucker(0.5, 50) ==>
// <0.5; 0.5-1; 1-2; 2-5; 5-10; 10-20; 20-50; >=50
func NewGeometricBucketer(lower, upper float64) *Bucketer {
return newBucketerFromEndpoints(
newGeometricBucketerStream(lower, upper))
}
// NewCumulativeDistribution creates a new CumulativeDistribution that uses
// this bucketer to distribute values.
func (b *Bucketer) NewCumulativeDistribution() *CumulativeDistribution {
return (*CumulativeDistribution)(newDistribution(b, false))
}
// NewNonCumulativeDistribution creates a new NonCumulativeDistribution that
// uses this bucketer to distribute values.
func (b *Bucketer) NewNonCumulativeDistribution() *NonCumulativeDistribution {
return (*NonCumulativeDistribution)(newDistribution(b, true))
}
// CumulativeDistribution represents a metric that is a distribution of
// values. Cumulative distributions only receive new values.
type CumulativeDistribution distribution
// Add adds a single value to this CumulativeDistribution instance.
// value can be a float32, float64, or a time.Duration.
// If a time.Duration, Add converts it to this instance's assigned unit.
// Add panics if value is not a float32, float64, or time.Duration or
// this instance has no assigned unit.
func (c *CumulativeDistribution) Add(value interface{}) {
(*distribution)(c).Add(value)
}
// Unlike in CumulativeDistributions,values in NonCumulativeDistributions
// can change shifting from bucket to bucket.
type NonCumulativeDistribution distribution
// Add adds a single value to this NonCumulativeDistribution instance.
// value can be a float32, float64, or a time.Duration.
// If a time.Duration, Add converts it to this instance's assigned unit.
// Add panics if value is not a float32, float64, or time.Duration or
// this instance has no assigned unit.
func (c *NonCumulativeDistribution) Add(value interface{}) {
(*distribution)(c).Add(value)
}
// Update updates a value in this NonCumulativeDistribution instance.
// oldValue and newValue can be a float32, float64, or a time.Duration.
// If a time.Duration, Update converts them this instance's assigned unit.
// The reliability of Update() depends on the caller providing the correct
// old value of what is being changed. Failure to do this results in
// undefined behavior.
// Update updates all distribution statistics in the expected way; however,
// it updates min and max such that min only gets smaller and max only gets
// larger. If update Updates values such that they fall into a narrower
// range than before, min and max remain unchanged to indicate the all-time
// min and all-time max. To have min and max reflect the current min and max
// instead of the all-time min and max, see UpdateMinMax().
// Update panics if oldValue and newValue are not a float32, float64,
// or time.Duration or this instance has no assigned unit.
func (d *NonCumulativeDistribution) Update(oldValue, newValue interface{}) {
(*distribution)(d).Update(oldValue, newValue)
}
// UpdateMinMax() estimates the current min and max of this distribution.
// and updates min and max accordingly.
// As this call may be expensive, clients need not use unless both are
// true:
// 1) the client has made calls to Update which narrowed the current
// min and max.
// 2) The client wants min and max to reflect the current min and max
// instead of the all-time min and max.
// This method only estimates. The only guarantees that it makes
// upon returning are:
// original_min <= min <= current_min and old_max >= max >= current_max.
// In fact, calling this method may do nothing at all which would still be
// correct behavior.
func (d *NonCumulativeDistribution) UpdateMinMax() {
}
// Remove removes a value from this NonCumulativeDistribution instance.
// valueToBeRemoved can be a float32, float64, or a time.Duration.
// If a time.Duration, Remove converts it to this instance's assigned unit.
// The reliability of Remove() depends on the caller providing a value
// already in the distribution. Failure to do this results in
// undefined behavior.
// Remove updates all distribution statistics in the expected way; however,
// it leaves min and max unchanged.
// To have min and max reflect the current min and max
// instead of the all-time min and max, see UpdateMinMax().
// Remove panics if oldValue and newValue are not a float32, float64,
// or time.Duration or this instance has no assigned unit.
func (d *NonCumulativeDistribution) Remove(valueToBeRemoved interface{}) {
(*distribution)(d).Remove(valueToBeRemoved)
}
// Sum returns the sum of the values in this distribution.
func (d *NonCumulativeDistribution) Sum() float64 {
return (*distribution)(d).Sum()
}
// Count returns the number of values in this distribution
func (d *NonCumulativeDistribution) Count() uint64 {
return (*distribution)(d).Count()
}
const (
// Indicates that passed slice may change.
MutableSlice = true
// Indicates that passed slice will never change.
ImmutableSlice = false
)
// List represents a metric that is a list of values of the same type.
// List instances are safe to use with multiple goroutines.
type List listType
// NewList returns a new list containing the values in aSlice.
//
// aSlice must be a slice of any type that tricorder supports that
// represents a single value. For example aSlice can be an []int32,
// []int64, or []time.Time, but it cannot be a []*tricorder.List.
// Moreover, aSlice cannot be a slice of pointers such as []*int64.
// NewList panics if aSlice is not a slice or is a slice of an
// unsupported type.
//
// If caller passes an []int or []uint for aSlice, NewList converts it
// internally to either an []int32, []int64, []uint32, []uint64 depending
// on whether or not the architecture is 32 or 64 bit. This conversion happens
// even if sliceIsMutable is false and requires creating a copy of the slice.
// Therefore, we recommend that for aSlice caller always use
// either []int32 or []int64 instead []int or either []uint32 or []uint64
// instead of []uint.
//
// sliceIsMutable lets tricorder know whether or not caller plans to
// modify aSlice in the future. If caller passes ImmutableSlice or
// false for sliceIsMutable and later modifies aSlice, the results
// are undefined. If caller passes MutableSlice or true and later
// modifies aSlice, tricorder will continue to report the original
// values in aSlice.
//
// To change the values in a List, caller must use the Change method.
func NewList(aSlice interface{}, sliceIsMutable bool) *List {
return (*List)(newListWithTimeStamp(aSlice, sliceIsMutable, time.Now()))
}
// Change updates this instance so that it contains only the values
// found in aSlice.
//
// Change panics if it would change the type of elements this instance
// contains. For instance, creating a list with a []int64 and then calling
// Change with a []string panics.
//
// The parameters aSlice and sliceIsMutable work the same way as in
// NewList.
func (l *List) Change(aSlice interface{}, sliceIsMutable bool) {
(*listType)(l).ChangeWithTimeStamp(aSlice, sliceIsMutable, time.Now())
}
// DirectorySpec represents a specific directory in the heirarchy of
// metrics.
type DirectorySpec directory
// RegisterDirectory returns the the DirectorySpec registered with path.
// If nothing is registered with path, RegisterDirectory registers a
// new DirectorySpec with path and returns it.
// RegisterDirectory returns ErrPathInUse if path is already associated
// with a metric.
func RegisterDirectory(path string) (dirSpec *DirectorySpec, err error) {
r, e := root.registerDirectory(newPathSpec(path))
return (*DirectorySpec)(r), e
}
// RegisterMetric works just like the package level RegisterMetric
// except that path is relative to this DirectorySpec.
func (d *DirectorySpec) RegisterMetric(
path string,
metric interface{},
unit units.Unit,
description string) error {
return (*directory)(d).registerMetric(
newPathSpec(path),
metric,
(*region)(DefaultGroup),
unit,
description)
}
// RegisterMetricInGroup works just like the package level
// RegisterMetricWithGroup except that path is relative to this
// DirectorySpec.
func (d *DirectorySpec) RegisterMetricInGroup(
path string,
metric interface{},
g *Group,
unit units.Unit,
description string) error {
return (*directory)(d).registerMetric(newPathSpec(path), metric, (*region)(g), unit, description)
}
// RegisterDirectory works just like the package level RegisterDirectory
// except that path is relative to this DirectorySpec.
func (d *DirectorySpec) RegisterDirectory(
path string) (dirSpec *DirectorySpec, err error) {
r, e := (*directory)(d).registerDirectory(newPathSpec(path))
return (*DirectorySpec)(r), e
}
// Returns the absolute path this object represents
func (d *DirectorySpec) AbsPath() string {
return (*directory)(d).AbsPath()
}
// UnregisterPath works just like the package level UnregisterPath
// except that path is relative to this DirectorySpec.
func (d *DirectorySpec) UnregisterPath(path string) {
(*directory)(d).unregisterPath(newPathSpec(path))
}
// UnregisterDirectory unregisters this DirectorySpec instance along with
// all metrics and directories within it. The caller can unregister any
// DirectorySpec instance except the one representing the top level directory.
// That DirectorySpec instance simply ignores calls to UnregisterDirectory.
// Metrics registered with an unregistered DirectorySpec instance will not
// be reported.
func (d *DirectorySpec) UnregisterDirectory() {
(*directory)(d).unregisterDirectory()
}
// RegisterFlags registers each application flag as a metric under /proc/flags
// in the default group.
func RegisterFlags() {
registerFlags()
}
// SetFlagUnit sets the unit for a specific flag. If the flag is a
// time.Duration, the default unit is units.Second; otherwise the default
// unit is units.None. If the client wishes to override the default unit for
// a flag, they call this before calling RegisterFlags.
func SetFlagUnit(name string, unit units.Unit) {
setFlagUnit(name, unit)
}