Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add first class support for Histograms #63

Merged
merged 6 commits into from
Dec 13, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions metrics/adapters/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@ import (
)

type cache struct {
lock sync.Mutex
counters map[string]metrics.Counter
gauges map[string]metrics.Gauge
timers map[string]metrics.Timer
lock sync.Mutex
counters map[string]metrics.Counter
gauges map[string]metrics.Gauge
timers map[string]metrics.Timer
histograms map[string]metrics.Histogram
}

func newCache() *cache {
return &cache{
counters: make(map[string]metrics.Counter),
gauges: make(map[string]metrics.Gauge),
timers: make(map[string]metrics.Timer),
counters: make(map[string]metrics.Counter),
gauges: make(map[string]metrics.Gauge),
timers: make(map[string]metrics.Timer),
histograms: make(map[string]metrics.Histogram),
}
}

Expand Down Expand Up @@ -67,3 +69,14 @@ func (r *cache) getOrSetTimer(name string, create func() metrics.Timer) metrics.
}
return t
}

func (r *cache) getOrSetHistogram(name string, create func() metrics.Histogram) metrics.Histogram {
r.lock.Lock()
defer r.lock.Unlock()
t, ok := r.histograms[name]
if !ok {
t = create()
r.histograms[name] = t
}
return t
}
7 changes: 6 additions & 1 deletion metrics/adapters/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func TestCache(t *testing.T) {
f := metricstest.NewFactory(100 * time.Second)
c1 := f.Counter(metrics.Options{Name: "x"})
g1 := f.Gauge(metrics.Options{Name: "y"})
t1 := f.Timer(metrics.Options{Name: "z"})
t1 := f.Timer(metrics.TimerOptions{Name: "z"})
h1 := f.Histogram(metrics.HistogramOptions{Name: "h"})

c := newCache()

Expand All @@ -38,11 +39,15 @@ func TestCache(t *testing.T) {
assert.Equal(t, g1, g2)
t2 := c.getOrSetTimer("z", func() metrics.Timer { return t1 })
assert.Equal(t, t1, t2)
h2 := c.getOrSetHistogram("h", func() metrics.Histogram { return h1 })
assert.Equal(t, h1, h2)

c3 := c.getOrSetCounter("x", func() metrics.Counter { panic("c1") })
assert.Equal(t, c1, c3)
g3 := c.getOrSetGauge("y", func() metrics.Gauge { panic("g1") })
assert.Equal(t, g1, g3)
t3 := c.getOrSetTimer("z", func() metrics.Timer { panic("t1") })
assert.Equal(t, t1, t3)
h3 := c.getOrSetHistogram("h", func() metrics.Histogram { panic("h1") })
assert.Equal(t, h1, h3)
}
40 changes: 33 additions & 7 deletions metrics/adapters/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import (

// FactoryWithTags creates metrics with fully qualified name and tags.
type FactoryWithTags interface {
Counter(name string, tags map[string]string, help string) metrics.Counter
Gauge(name string, tags map[string]string, help string) metrics.Gauge
Timer(name string, tags map[string]string, help string) metrics.Timer
Counter(options metrics.Options) metrics.Counter
Gauge(options metrics.Options) metrics.Gauge
Timer(options metrics.TimerOptions) metrics.Timer
Histogram(options metrics.HistogramOptions) metrics.Histogram
}

// Options affect how the adapter factory behaves.
Expand Down Expand Up @@ -66,21 +67,46 @@ type factory struct {
func (f *factory) Counter(options metrics.Options) metrics.Counter {
fullName, fullTags, key := f.getKey(options.Name, options.Tags)
return f.cache.getOrSetCounter(key, func() metrics.Counter {
return f.factory.Counter(fullName, fullTags, options.Help)
return f.factory.Counter(metrics.Options{
Name: fullName,
Tags: fullTags,
Help: options.Help,
})
})
}

func (f *factory) Gauge(options metrics.Options) metrics.Gauge {
fullName, fullTags, key := f.getKey(options.Name, options.Tags)
return f.cache.getOrSetGauge(key, func() metrics.Gauge {
return f.factory.Gauge(fullName, fullTags, options.Help)
return f.factory.Gauge(metrics.Options{
Name: fullName,
Tags: fullTags,
Help: options.Help,
})
})
}

func (f *factory) Timer(options metrics.Options) metrics.Timer {
func (f *factory) Timer(options metrics.TimerOptions) metrics.Timer {
fullName, fullTags, key := f.getKey(options.Name, options.Tags)
return f.cache.getOrSetTimer(key, func() metrics.Timer {
return f.factory.Timer(fullName, fullTags, options.Help)
return f.factory.Timer(metrics.TimerOptions{
Name: fullName,
Tags: fullTags,
Help: options.Help,
Buckets: options.Buckets,
})
})
}

func (f *factory) Histogram(options metrics.HistogramOptions) metrics.Histogram {
fullName, fullTags, key := f.getKey(options.Name, options.Tags)
return f.cache.getOrSetHistogram(key, func() metrics.Histogram {
return f.factory.Histogram(metrics.HistogramOptions{
Name: fullName,
Tags: fullTags,
Help: options.Help,
Buckets: options.Buckets,
})
})
}

Expand Down
79 changes: 53 additions & 26 deletions metrics/adapters/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ func TestSubScope(t *testing.T) {

func TestFactory(t *testing.T) {
var (
counterPrefix = "counter_"
gaugePrefix = "gauge_"
timerPrefix = "timer_"
counterPrefix = "counter_"
gaugePrefix = "gauge_"
timerPrefix = "timer_"
histogramPrefix = "histogram_"

tagsA = map[string]string{"a": "b"}
tagsX = map[string]string{"x": "y"}
Expand All @@ -55,6 +56,8 @@ func TestFactory(t *testing.T) {
testCases := []struct {
name string
tags map[string]string
buckets []float64
durationBuckets []time.Duration
namespace string
nsTags map[string]string
fullName string
Expand Down Expand Up @@ -93,9 +96,15 @@ func TestFactory(t *testing.T) {
Name: gaugePrefix + testCase.name,
Tags: testCase.tags,
})
timer := f.Timer(metrics.Options{
Name: timerPrefix + testCase.name,
Tags: testCase.tags,
timer := f.Timer(metrics.TimerOptions{
Name: timerPrefix + testCase.name,
Tags: testCase.tags,
Buckets: testCase.durationBuckets,
})
histogram := f.Histogram(metrics.HistogramOptions{
Name: histogramPrefix + testCase.name,
Tags: testCase.tags,
Buckets: testCase.buckets,
})

assert.Equal(t, counter, f.Counter(metrics.Options{
Expand All @@ -106,45 +115,63 @@ func TestFactory(t *testing.T) {
Name: gaugePrefix + testCase.name,
Tags: testCase.tags,
}))
assert.Equal(t, timer, f.Timer(metrics.Options{
Name: timerPrefix + testCase.name,
Tags: testCase.tags,
assert.Equal(t, timer, f.Timer(metrics.TimerOptions{
Name: timerPrefix + testCase.name,
Tags: testCase.tags,
Buckets: testCase.durationBuckets,
}))
assert.Equal(t, histogram, f.Histogram(metrics.HistogramOptions{
Name: histogramPrefix + testCase.name,
Tags: testCase.tags,
Buckets: testCase.buckets,
}))

assert.Equal(t, fmt.Sprintf(testCase.fullName, counterPrefix), ff.counter)
assert.Equal(t, fmt.Sprintf(testCase.fullName, gaugePrefix), ff.gauge)
assert.Equal(t, fmt.Sprintf(testCase.fullName, timerPrefix), ff.timer)
assert.Equal(t, fmt.Sprintf(testCase.fullName, histogramPrefix), ff.histogram)
})
}
}

type fakeTagless struct {
factory metrics.Factory
counter string
gauge string
timer string
factory metrics.Factory
counter string
gauge string
timer string
histogram string
}

func (f *fakeTagless) Counter(name string, help string) metrics.Counter {
f.counter = name
func (f *fakeTagless) Counter(options TaglessOptions) metrics.Counter {
f.counter = options.Name
return f.factory.Counter(metrics.Options{
Name: name,
Help: help,
Name: options.Name,
Help: options.Help,
})
}

func (f *fakeTagless) Gauge(name string, help string) metrics.Gauge {
f.gauge = name
func (f *fakeTagless) Gauge(options TaglessOptions) metrics.Gauge {
f.gauge = options.Name
return f.factory.Gauge(metrics.Options{
Name: name,
Help: help,
Name: options.Name,
Help: options.Help,
})
}

func (f *fakeTagless) Timer(options TaglessTimerOptions) metrics.Timer {
f.timer = options.Name
return f.factory.Timer(metrics.TimerOptions{
Name: options.Name,
Help: options.Help,
Buckets: options.Buckets,
})
}

func (f *fakeTagless) Timer(name string, help string) metrics.Timer {
f.timer = name
return f.factory.Timer(metrics.Options{
Name: name,
Help: help,
func (f *fakeTagless) Histogram(options TaglessHistogramOptions) metrics.Histogram {
f.histogram = options.Name
return f.factory.Histogram(metrics.HistogramOptions{
Name: options.Name,
Help: options.Help,
Buckets: options.Buckets,
})
}
70 changes: 57 additions & 13 deletions metrics/adapters/tagless.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,39 @@

package adapters

import "github.com/uber/jaeger-lib/metrics"
import (
"time"

"github.com/uber/jaeger-lib/metrics"
)

// TaglessOptions defines the information associated with a metric
type TaglessOptions struct {
Name string
Help string
}

// TaglessTimerOptions defines the information associated with a metric
type TaglessTimerOptions struct {
Name string
Help string
Buckets []time.Duration
}

// TaglessHistogramOptions defines the information associated with a metric
type TaglessHistogramOptions struct {
Name string
Help string
Buckets []float64
}

// FactoryWithoutTags creates metrics based on name only, without tags.
// Suitable for integrating with statsd-like backends that don't support tags.
type FactoryWithoutTags interface {
Counter(name string, help string) metrics.Counter
Gauge(name string, help string) metrics.Gauge
Timer(name string, help string) metrics.Timer
Counter(options TaglessOptions) metrics.Counter
Gauge(options TaglessOptions) metrics.Gauge
Timer(options TaglessTimerOptions) metrics.Timer
Histogram(options TaglessHistogramOptions) metrics.Histogram
}
yurishkuro marked this conversation as resolved.
Show resolved Hide resolved

// WrapFactoryWithoutTags creates a real metrics.Factory that supports subscopes.
Expand All @@ -41,19 +66,38 @@ type tagless struct {
factory FactoryWithoutTags
}

func (f *tagless) Counter(name string, tags map[string]string, help string) metrics.Counter {
fullName := f.getFullName(name, tags)
return f.factory.Counter(fullName, help)
func (f *tagless) Counter(options metrics.Options) metrics.Counter {
fullName := f.getFullName(options.Name, options.Tags)
return f.factory.Counter(TaglessOptions{
Name: fullName,
Help: options.Help,
})
}

func (f *tagless) Gauge(options metrics.Options) metrics.Gauge {
fullName := f.getFullName(options.Name, options.Tags)
return f.factory.Gauge(TaglessOptions{
Name: fullName,
Help: options.Help,
})
}

func (f *tagless) Gauge(name string, tags map[string]string, help string) metrics.Gauge {
fullName := f.getFullName(name, tags)
return f.factory.Gauge(fullName, help)
func (f *tagless) Timer(options metrics.TimerOptions) metrics.Timer {
fullName := f.getFullName(options.Name, options.Tags)
return f.factory.Timer(TaglessTimerOptions{
Name: fullName,
Help: options.Help,
Buckets: options.Buckets,
})
}

func (f *tagless) Timer(name string, tags map[string]string, help string) metrics.Timer {
fullName := f.getFullName(name, tags)
return f.factory.Timer(fullName, help)
func (f *tagless) Histogram(options metrics.HistogramOptions) metrics.Histogram {
fullName := f.getFullName(options.Name, options.Tags)
return f.factory.Histogram(TaglessHistogramOptions{
Name: fullName,
Help: options.Help,
Buckets: options.Buckets,
})
}

func (f *tagless) getFullName(name string, tags map[string]string) string {
Expand Down
18 changes: 12 additions & 6 deletions metrics/expvar/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,20 @@ type factory struct {
factory xkit.Factory
}

func (f *factory) Counter(name string, help string) metrics.Counter {
return xkit.NewCounter(f.factory.Counter(name))
func (f *factory) Counter(options adapters.TaglessOptions) metrics.Counter {
return xkit.NewCounter(f.factory.Counter(options.Name))
}

func (f *factory) Gauge(name string, help string) metrics.Gauge {
return xkit.NewGauge(f.factory.Gauge(name))
func (f *factory) Gauge(options adapters.TaglessOptions) metrics.Gauge {
return xkit.NewGauge(f.factory.Gauge(options.Name))
}

func (f *factory) Timer(name string, help string) metrics.Timer {
return xkit.NewTimer(f.factory.Histogram(name))
func (f *factory) Timer(options adapters.TaglessTimerOptions) metrics.Timer {
// TODO: Provide support for buckets
return xkit.NewTimer(f.factory.Histogram(options.Name))
}

func (f *factory) Histogram(options adapters.TaglessHistogramOptions) metrics.Histogram {
// TODO: Provide support for buckets
return xkit.NewHistogram(f.factory.Histogram(options.Name))
}
Loading