Skip to content

Commit 46d6f21

Browse files
extemporalgenomekevinburkesegment
authored andcommitted
all: reimplement stats/v4 in terms of stats/v5
This releases a new, v4-compatible stats library that just points at the underlying v5 library. Because types are not comparable across major versions and many different libraries import segmentio/stats, this required every library to update simultaneously, which was proving difficult for some teams. This should smooth the upgrade path for teams that cannot yet upgrade to stats/v5 - they can use type aliases in order to continue pointing at a v4 major version.
1 parent 2a099d8 commit 46d6f21

16 files changed

Lines changed: 177 additions & 1744 deletions

File tree

datadog/server_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ import (
1010
"time"
1111

1212
"github.com/segmentio/stats/v4"
13+
"github.com/segmentio/stats/v4/statstest"
1314
)
1415

1516
func TestServer(t *testing.T) {
17+
statstest.DisableVersionReporting(t)
18+
1619
engine := stats.NewEngine("datadog.test", nil)
1720

1821
a := uint32(0)

engine.go

Lines changed: 36 additions & 256 deletions
Original file line numberDiff line numberDiff line change
@@ -1,308 +1,88 @@
11
package stats
22

33
import (
4-
"os"
5-
"path/filepath"
6-
"reflect"
7-
"sync"
84
"time"
9-
)
10-
11-
// An Engine carries the context for producing metrics, it is configured by
12-
// setting the exported fields or using the helper methods to create sub-engines
13-
// that inherit the configuration of the base they were created from.
14-
//
15-
// The program must not modify the engine's handler, prefix, or tags after it
16-
// started using it. If changes need to be made new engines must be created by
17-
// calls to WithPrefix or WithTags.
18-
type Engine struct {
19-
// The measure handler that the engine forwards measures to.
20-
Handler Handler
21-
22-
// A prefix set on all metric names produced by the engine.
23-
Prefix string
24-
25-
// A list of tags set on all metrics produced by the engine.
26-
//
27-
// The list of tags has to be sorted. This is automatically managed by the
28-
// helper methods WithPrefix, WithTags and the NewEngine function. A program
29-
// that manipulates this field directly has to respect this requirement.
30-
Tags []Tag
315

32-
// Indicates whether to allow duplicated tags from the tags list before sending.
33-
// This option is turned off by default, ensuring that duplicate tags are removed.
34-
// Turn it on if you need to send the same tag multiple times with different values,
35-
// which is a special use case.
36-
AllowDuplicateTags bool
6+
statsv5 "github.com/segmentio/stats/v5"
7+
)
378

38-
// This cache keeps track of the generated measure structures to avoid
39-
// rebuilding them every time a same measure type is seen by the engine.
40-
//
41-
// The cached values include the engine prefix in the measure names, which
42-
// is why the cache must be local to the engine.
43-
cache measureCache
44-
}
9+
// Engine behaves like [stats/v5.Engine].
10+
type Engine = statsv5.Engine
4511

46-
// NewEngine creates and returns a new engine configured with prefix, handler,
47-
// and tags.
12+
// NewEngine behaves like [stats/v5.NewEngine].
4813
func NewEngine(prefix string, handler Handler, tags ...Tag) *Engine {
49-
return &Engine{
50-
Handler: handler,
51-
Prefix: prefix,
52-
Tags: SortTags(copyTags(tags)),
53-
}
54-
}
55-
56-
// Register adds handler to eng.
57-
func (eng *Engine) Register(handler Handler) {
58-
if eng.Handler == Discard {
59-
eng.Handler = handler
60-
} else {
61-
eng.Handler = MultiHandler(eng.Handler, handler)
62-
}
63-
}
64-
65-
// Flush flushes eng's handler (if it implements the Flusher interface).
66-
func (eng *Engine) Flush() {
67-
flush(eng.Handler)
68-
}
69-
70-
// WithPrefix returns a copy of the engine with prefix appended to eng's current
71-
// prefix and tags set to the merge of eng's current tags and those passed as
72-
// argument. Both eng and the returned engine share the same handler.
73-
func (eng *Engine) WithPrefix(prefix string, tags ...Tag) *Engine {
74-
return &Engine{
75-
Handler: eng.Handler,
76-
Prefix: eng.makeName(prefix),
77-
Tags: mergeTags(eng.Tags, tags),
78-
}
79-
}
80-
81-
// WithTags returns a copy of the engine with tags set to the merge of eng's
82-
// current tags and those passed as arguments. Both eng and the returned engine
83-
// share the same handler.
84-
func (eng *Engine) WithTags(tags ...Tag) *Engine {
85-
return eng.WithPrefix("", tags...)
86-
}
87-
88-
// Incr increments by one the counter identified by name and tags.
89-
func (eng *Engine) Incr(name string, tags ...Tag) {
90-
eng.Add(name, 1, tags...)
91-
}
92-
93-
// IncrAt increments by one the counter identified by name and tags.
94-
func (eng *Engine) IncrAt(time time.Time, name string, tags ...Tag) {
95-
eng.AddAt(time, name, 1, tags...)
96-
}
97-
98-
// Add increments by value the counter identified by name and tags.
99-
func (eng *Engine) Add(name string, value interface{}, tags ...Tag) {
100-
eng.measure(time.Now(), name, value, Counter, tags...)
101-
}
102-
103-
// AddAt increments by value the counter identified by name and tags.
104-
func (eng *Engine) AddAt(t time.Time, name string, value interface{}, tags ...Tag) {
105-
eng.measure(t, name, value, Counter, tags...)
106-
}
107-
108-
// Set sets to value the gauge identified by name and tags.
109-
func (eng *Engine) Set(name string, value interface{}, tags ...Tag) {
110-
eng.measure(time.Now(), name, value, Gauge, tags...)
111-
}
112-
113-
// SetAt sets to value the gauge identified by name and tags.
114-
func (eng *Engine) SetAt(t time.Time, name string, value interface{}, tags ...Tag) {
115-
eng.measure(t, name, value, Gauge, tags...)
116-
}
117-
118-
// Observe reports value for the histogram identified by name and tags.
119-
func (eng *Engine) Observe(name string, value interface{}, tags ...Tag) {
120-
eng.measure(time.Now(), name, value, Histogram, tags...)
121-
}
122-
123-
// ObserveAt reports value for the histogram identified by name and tags.
124-
func (eng *Engine) ObserveAt(t time.Time, name string, value interface{}, tags ...Tag) {
125-
eng.measure(t, name, value, Histogram, tags...)
126-
}
127-
128-
// Clock returns a new clock identified by name and tags.
129-
func (eng *Engine) Clock(name string, tags ...Tag) *Clock {
130-
return eng.ClockAt(name, time.Now(), tags...)
131-
}
132-
133-
// ClockAt returns a new clock identified by name and tags with a specified
134-
// start time.
135-
func (eng *Engine) ClockAt(name string, start time.Time, tags ...Tag) *Clock {
136-
cpy := make([]Tag, len(tags), len(tags)+1) // clock always appends a stamp.
137-
copy(cpy, tags)
138-
return &Clock{
139-
name: name,
140-
first: start,
141-
last: start,
142-
tags: cpy,
143-
eng: eng,
144-
}
14+
return statsv5.NewEngine(prefix, handler, tags...)
14515
}
14616

147-
func (eng *Engine) measure(t time.Time, name string, value interface{}, ftype FieldType, tags ...Tag) {
148-
name, field := splitMeasureField(name)
149-
mp := measureArrayPool.Get().(*[1]Measure)
150-
151-
m := &(*mp)[0]
152-
m.Name = eng.makeName(name) // TODO: figure out how to optimize this
153-
m.Fields = append(m.Fields[:0], MakeField(field, value, ftype))
154-
m.Tags = append(m.Tags[:0], eng.Tags...)
155-
m.Tags = append(m.Tags, tags...)
156-
157-
if len(tags) != 0 && !eng.AllowDuplicateTags && !TagsAreSorted(m.Tags) {
158-
SortTags(m.Tags)
159-
}
160-
161-
eng.Handler.HandleMeasures(t, (*mp)[:]...)
17+
// DefaultEngine behaves like [stats/v5.DefaultEngine].
18+
var DefaultEngine = statsv5.DefaultEngine
16219

163-
for i := range m.Fields {
164-
m.Fields[i] = Field{}
165-
}
166-
167-
for i := range m.Tags {
168-
m.Tags[i] = Tag{}
169-
}
170-
171-
m.Name = ""
172-
measureArrayPool.Put(mp)
173-
}
174-
175-
func (eng *Engine) makeName(name string) string {
176-
return concat(eng.Prefix, name)
177-
}
178-
179-
var measureArrayPool = sync.Pool{
180-
New: func() interface{} { return new([1]Measure) },
181-
}
182-
183-
// Report calls ReportAt with time.Now() as first argument.
184-
func (eng *Engine) Report(metrics interface{}, tags ...Tag) {
185-
eng.ReportAt(time.Now(), metrics, tags...)
186-
}
187-
188-
// ReportAt reports a set of metrics for a given time. The metrics must be of
189-
// type struct, pointer to struct, or a slice or array to one of those. See
190-
// MakeMeasures for details about how to make struct types exposing metrics.
191-
func (eng *Engine) ReportAt(time time.Time, metrics interface{}, tags ...Tag) {
192-
var tb *tagsBuffer
193-
194-
if len(tags) == 0 {
195-
// fast path for the common case where there are no dynamic tags
196-
tags = eng.Tags
197-
} else {
198-
tb = tagsPool.Get().(*tagsBuffer)
199-
tb.append(tags...)
200-
tb.append(eng.Tags...)
201-
if !eng.AllowDuplicateTags {
202-
tb.sort()
203-
}
204-
tags = tb.tags
205-
}
206-
207-
mb := measurePool.Get().(*measuresBuffer)
208-
mb.measures = appendMeasures(mb.measures[:0], &eng.cache, eng.Prefix, reflect.ValueOf(metrics), tags...)
209-
210-
ms := mb.measures
211-
eng.Handler.HandleMeasures(time, ms...)
212-
213-
for i := range ms {
214-
ms[i].reset()
215-
}
216-
217-
if tb != nil {
218-
tb.reset()
219-
tagsPool.Put(tb)
220-
}
221-
222-
measurePool.Put(mb)
223-
}
224-
225-
// DefaultEngine is the engine used by global helper functions.
226-
var DefaultEngine = NewEngine(progname(), Discard)
227-
228-
// Register adds handler to the default engine.
20+
// Register behaves like [stats/v5.Register].
22921
func Register(handler Handler) {
230-
DefaultEngine.Register(handler)
22+
statsv5.Register(handler)
23123
}
23224

233-
// Flush flushes the default engine.
25+
// Flush behaves like [stats/v5.Flush].
23426
func Flush() {
235-
DefaultEngine.Flush()
27+
statsv5.Flush()
23628
}
23729

238-
// WithPrefix returns a copy of the engine with prefix appended to default
239-
// engine's current prefix and tags set to the merge of eng's current tags and
240-
// those passed as argument. Both the default engine and the returned engine
241-
// share the same handler.
30+
// WithPrefix behaves like [stats/v5.WithPrefix].
24231
func WithPrefix(prefix string, tags ...Tag) *Engine {
243-
return DefaultEngine.WithPrefix(prefix, tags...)
32+
return statsv5.WithPrefix(prefix, tags...)
24433
}
24534

246-
// WithTags returns a copy of the engine with tags set to the merge of the
247-
// default engine's current tags and those passed as arguments. Both the default
248-
// engine and the returned engine share the same handler.
35+
// WithTags behaves like [stats/v5.WithTags].
24936
func WithTags(tags ...Tag) *Engine {
250-
return DefaultEngine.WithTags(tags...)
37+
return statsv5.WithTags(tags...)
25138
}
25239

253-
// Incr increments by one the counter identified by name and tags.
40+
// Incr behaves like [stats/v5.Incr].
25441
func Incr(name string, tags ...Tag) {
255-
DefaultEngine.Incr(name, tags...)
42+
statsv5.Incr(name, tags...)
25643
}
25744

258-
// IncrAt increments by one the counter identified by name and tags.
45+
// IncrAt behaves like [stats/v5.IncrAt].
25946
func IncrAt(time time.Time, name string, tags ...Tag) {
260-
DefaultEngine.IncrAt(time, name, tags...)
47+
statsv5.IncrAt(time, name, tags...)
26148
}
26249

263-
// Add increments by value the counter identified by name and tags.
50+
// Add behaves like [stats/v5.Add].
26451
func Add(name string, value interface{}, tags ...Tag) {
265-
DefaultEngine.Add(name, value, tags...)
52+
statsv5.Add(name, value, tags...)
26653
}
26754

268-
// AddAt increments by value the counter identified by name and tags.
55+
// AddAt behaves like [stats/v5.AddAt].
26956
func AddAt(time time.Time, name string, value interface{}, tags ...Tag) {
270-
DefaultEngine.AddAt(time, name, value, tags...)
57+
statsv5.AddAt(time, name, value, tags...)
27158
}
27259

273-
// Set sets to value the gauge identified by name and tags.
60+
// Set behaves like [stats/v5.Set].
27461
func Set(name string, value interface{}, tags ...Tag) {
275-
DefaultEngine.Set(name, value, tags...)
62+
statsv5.Set(name, value, tags...)
27663
}
27764

278-
// SetAt sets to value the gauge identified by name and tags.
65+
// SetAt behaves like [stats/v5.SetAt].
27966
func SetAt(time time.Time, name string, value interface{}, tags ...Tag) {
280-
DefaultEngine.SetAt(time, name, value, tags...)
67+
statsv5.SetAt(time, name, value, tags...)
28168
}
28269

283-
// Observe reports value for the histogram identified by name and tags.
70+
// Observe behaves like [stats/v5.Observe].
28471
func Observe(name string, value interface{}, tags ...Tag) {
285-
DefaultEngine.Observe(name, value, tags...)
72+
statsv5.Observe(name, value, tags...)
28673
}
28774

288-
// ObserveAt reports value for the histogram identified by name and tags.
75+
// ObserveAt behaves like [stats/v5.ObserveAt].
28976
func ObserveAt(time time.Time, name string, value interface{}, tags ...Tag) {
290-
DefaultEngine.ObserveAt(time, name, value, tags...)
77+
statsv5.ObserveAt(time, name, value, tags...)
29178
}
29279

293-
// Report is a helper function that delegates to DefaultEngine.
80+
// Report behaves like [stats/v5.Report].
29481
func Report(metrics interface{}, tags ...Tag) {
295-
DefaultEngine.Report(metrics, tags...)
82+
statsv5.Report(metrics, tags...)
29683
}
29784

298-
// ReportAt is a helper function that delegates to DefaultEngine.
85+
// ReportAt behaves like [stats/v5.ReportAt].
29986
func ReportAt(time time.Time, metrics interface{}, tags ...Tag) {
300-
DefaultEngine.ReportAt(time, metrics, tags...)
301-
}
302-
303-
func progname() (name string) {
304-
if args := os.Args; len(args) != 0 {
305-
name = filepath.Base(args[0])
306-
}
307-
return
87+
statsv5.ReportAt(time, metrics, tags...)
30888
}

engine_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
)
1717

1818
func TestEngine(t *testing.T) {
19+
statstest.DisableVersionReporting(t)
20+
1921
tests := []struct {
2022
scenario string
2123
function func(*testing.T, *stats.Engine)

0 commit comments

Comments
 (0)