diff --git a/Makefile b/Makefile index bf7378e7..6670a707 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ cover: .PHONY: coveralls coveralls: - goveralls -service=travis-ci . + goveralls -service=travis-ci || echo "Coveralls failed" .PHONY: bench BENCH ?= . diff --git a/README.md b/README.md index 0559ff55..7df5f0a8 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,20 @@ Fast, buffered, hierarchical stats collection in Go. ## Abstract -Tally provides a common interface for emitting metrics, while letting you not worry about the velocity of metrics emission. +Tally provides a common interface for emitting metrics, while letting you not worry about the velocity of metrics emission. -By default it buffers counters and gauges at a specified interval but does not buffer timer values. This is primarily so timer values can have all their values sampled if desired and if not they can be sampled as histograms. +By default it buffers counters, gauges and histograms at a specified interval but does not buffer timer values. This is primarily so timer values can have all their values sampled if desired and if not they can be sampled as summaries or histograms independently by a reporter. ## Structure - Scope: Keeps track of metrics, and their common metadata. -- Metrics: Counters, Gauges, Timers. +- Metrics: Counters, Gauges, Timers and Histograms. - Reporter: Implemented by you. Accepts aggregated values from the scope. Forwards the aggregated values to your metrics ingestion pipeline. + - The reporters already available listed alphabetically are: + - `github.com/uber-go/tally/m3`: Report m3 metrics, timers are not sampled and forwarded directly. + - `github.com/uber-go/tally/multi`: Report to multiple reporters, you can multi-write metrics to other reporters simply. + - `github.com/uber-go/tally/prometheus`: Report prometheus metrics, timers by default are made summaries with an option to make them histograms instead. + - `github.com/uber-go/tally/statsd`: Report statsd metrics, no support for tags. ### Acquire a Scope ### ```go @@ -24,8 +29,12 @@ tags := map[string]string{ "dc": "east-1", "type": "master", } -reportEvery := 1 * time.Second -scope := tally.NewRootScope("some_prefix", tags, reporter, reportEvery, tally.DefaultSeparator) +reportEvery := time.Second + +scope := tally.NewRootScope(tally.ScopeOptions{ + Tags: tags, + Reporter: reporter, +}, reportEvery) ``` ### Get/Create a metric, use it ### @@ -43,43 +52,85 @@ Use the inbuilt statsd reporter: ```go import ( + "io" "github.com/cactus/go-statsd-client/statsd" "github.com/uber-go/tally" tallystatsd "github.com/uber-go/tally/statsd" // ... ) -client, err := statsd.NewClient("statsd.aggregator.local:1234", "") -// ... - -opts := tallystatsd.NewOptions().SetSampleRate(1.0) -reporter = tallystatsd.NewStatsdReporter(client, opts) -tags := map[string]string{ - "dc": "east-1", - "type": "master", +func newScope() (tally.Scope, io.Closer) { + statter, _ := statsd.NewBufferedClient("127.0.0.1:8125", + "stats", 100*time.Millisecond, 1440) + + reporter := tallystatsd.NewReporter(statter, tallystatsd.Options{ + SampleRate: 1.0, + }) + + scope, closer := tally.NewRootScope(tally.ScopeOptions{ + Prefix: "my-service", + Tags: map[string]string{}, + Reporter: r, + }, time.Second) + + return scope, closer } -reportEvery := 1 * time.Second -scope := tally.NewRootScope("some_prefix", tags, reporter, reportEvery, tally.DefaultSeparator) ``` Implement your own reporter using the `StatsReporter` interface: ```go + +// BaseStatsReporter implements the shared reporter methods. +type BaseStatsReporter interface { + Capabilities() Capabilities + Flush() +} + +// StatsReporter is a backend for Scopes to report metrics to. type StatsReporter interface { + BaseStatsReporter + // ReportCounter reports a counter value - ReportCounter(name string, tags map[string]string, value int64) + ReportCounter( + name string, + tags map[string]string, + value int64, + ) // ReportGauge reports a gauge value - ReportGauge(name string, tags map[string]string, value float64) + ReportGauge( + name string, + tags map[string]string, + value float64, + ) // ReportTimer reports a timer value - ReportTimer(name string, tags map[string]string, interval time.Duration) - - // Capabilities returns a description of metrics reporting capabilities - Capabilities() Capabilities - - // Flush is expected to be called by a Scope when it completes a round or reporting - Flush() + ReportTimer( + name string, + tags map[string]string, + interval time.Duration, + ) + + // ReportHistogramValueSamples reports histogram samples for a bucket + ReportHistogramValueSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound, + bucketUpperBound float64, + samples int64, + ) + + // ReportHistogramDurationSamples reports histogram samples for a bucket + ReportHistogramDurationSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound, + bucketUpperBound time.Duration, + samples int64, + ) } ``` @@ -87,24 +138,44 @@ Or implement your own metrics implementation that matches the tally `Scope` inte ```go type Scope interface { - // Counter returns the Counter object corresponding to the name + // Counter returns the Counter object corresponding to the name. Counter(name string) Counter - // Gauge returns the Gauge object corresponding to the name + // Gauge returns the Gauge object corresponding to the name. Gauge(name string) Gauge - // Timer returns the Timer object corresponding to the name + // Timer returns the Timer object corresponding to the name. Timer(name string) Timer - // Tagged returns a new child scope with the given tags and current tags + // Histogram returns the Histogram object corresponding to the name. + // To use default value and duration buckets configured for the scope + // simply pass tally.DefaultBuckets or nil. + // You can use tally.ValueBuckets{x, y, ...} for value buckets. + // You can use tally.DurationBuckets{x, y, ...} for duration buckets. + // You can use tally.MustMakeLinearValueBuckets(start, width, count) for linear values. + // You can use tally.MustMakeLinearDurationBuckets(start, width, count) for linear durations. + // You can use tally.MustMakeExponentialValueBuckets(start, factor, count) for exponential values. + // You can use tally.MustMakeExponentialDurationBuckets(start, factor, count) for exponential durations. + Histogram(name string, buckets Buckets) Histogram + + // Tagged returns a new child scope with the given tags and current tags. Tagged(tags map[string]string) Scope - // SubScope returns a new child scope appending a further name prefix + // SubScope returns a new child scope appending a further name prefix. SubScope(name string) Scope - // Capabilities returns a description of metrics reporting capabilities + // Capabilities returns a description of metrics reporting capabilities. Capabilities() Capabilities } + +// Capabilities is a description of metrics reporting capabilities. +type Capabilities interface { + // Reporting returns whether the reporter has the ability to actively report. + Reporting() bool + + // Tagging returns whether the reporter has the capability for tagged metrics. + Tagging() bool +} ``` ## Performance diff --git a/example/main.go b/example/main.go index b684cf73..cdc2c6a2 100644 --- a/example/main.go +++ b/example/main.go @@ -81,10 +81,6 @@ func (r *printStatsReporter) Tagging() bool { return false } -func (r *printStatsReporter) Histograms() bool { - return true -} - func (r *printStatsReporter) Flush() { fmt.Printf("flush\n") } diff --git a/m3/reporter.go b/m3/reporter.go index 46f2b092..0c72e21a 100644 --- a/m3/reporter.go +++ b/m3/reporter.go @@ -463,10 +463,6 @@ func (r *reporter) Tagging() bool { return true } -func (r *reporter) Histograms() bool { - return true -} - func (r *reporter) process() { mets := make([]*m3thrift.Metric, 0, (r.freeBytes / 10)) bytes := int32(0) diff --git a/multi/reporter.go b/multi/reporter.go index 9e634d94..0c0fac7d 100644 --- a/multi/reporter.go +++ b/multi/reporter.go @@ -247,7 +247,6 @@ func (r multiBaseReporters) Capabilities() tally.Capabilities { for _, r := range r { c.reporting = c.reporting && r.Capabilities().Reporting() c.tagging = c.tagging && r.Capabilities().Tagging() - c.histograms = c.histograms && r.Capabilities().Histograms() } return c } @@ -271,7 +270,3 @@ func (c *capabilities) Reporting() bool { func (c *capabilities) Tagging() bool { return c.tagging } - -func (c *capabilities) Histograms() bool { - return c.histograms -} diff --git a/multi/reporter_test.go b/multi/reporter_test.go index 15cda1c9..c4374df4 100644 --- a/multi/reporter_test.go +++ b/multi/reporter_test.go @@ -333,10 +333,6 @@ func (r *capturingStatsReporter) Tagging() bool { return true } -func (r *capturingStatsReporter) Histograms() bool { - return true -} - func (r *capturingStatsReporter) Flush() { r.flush++ } diff --git a/prometheus/reporter.go b/prometheus/reporter.go index 296fb230..32b7bbe6 100644 --- a/prometheus/reporter.go +++ b/prometheus/reporter.go @@ -555,10 +555,6 @@ func (r *reporter) Tagging() bool { return true } -func (r *reporter) Histograms() bool { - return true -} - // Flush does nothing for prometheus func (r *reporter) Flush() {} diff --git a/reporter.go b/reporter.go index ad889760..63844758 100644 --- a/reporter.go +++ b/reporter.go @@ -24,7 +24,10 @@ import "time" // BaseStatsReporter implements the shared reporter methods. type BaseStatsReporter interface { + // Capabilities returns the capabilities description of the reporter. Capabilities() Capabilities + + // Flush asks the reporter to flush all reported values. Flush() } diff --git a/scope_test.go b/scope_test.go index e412264c..2199ad19 100644 --- a/scope_test.go +++ b/scope_test.go @@ -245,7 +245,7 @@ func (r *testStatsReporter) ReportHistogramDurationSamples( } func (r *testStatsReporter) Capabilities() Capabilities { - return capabilitiesReportingNoTaggingNoHistograms // TODO: add support for test stats reporter for histograms + return capabilitiesReportingNoTagging } func (r *testStatsReporter) Flush() {} diff --git a/stats.go b/stats.go index f9ef4f36..d6c7361c 100644 --- a/stats.go +++ b/stats.go @@ -30,31 +30,22 @@ import ( var ( capabilitiesNone = &capabilities{ - reporting: false, - tagging: false, - histograms: false, + reporting: false, + tagging: false, } - capabilitiesReportingNoTaggingNoHistograms = &capabilities{ - reporting: true, - tagging: false, - histograms: false, + capabilitiesReportingNoTagging = &capabilities{ + reporting: true, + tagging: false, } - capabilitiesReportingTaggingNoHistograms = &capabilities{ - reporting: true, - tagging: true, - histograms: false, - } - capabilitiesReportingTaggingHistograms = &capabilities{ - reporting: true, - tagging: true, - histograms: true, + capabilitiesReportingTagging = &capabilities{ + reporting: true, + tagging: true, } ) type capabilities struct { - reporting bool - tagging bool - histograms bool + reporting bool + tagging bool } func (c *capabilities) Reporting() bool { @@ -65,10 +56,6 @@ func (c *capabilities) Tagging() bool { return c.tagging } -func (c *capabilities) Histograms() bool { - return c.histograms -} - type counter struct { prev int64 curr int64 @@ -270,7 +257,7 @@ func (r *timerNoReporterSink) ReportHistogramDurationSamples( } func (r *timerNoReporterSink) Capabilities() Capabilities { - return capabilitiesReportingTaggingNoHistograms + return capabilitiesReportingTagging } func (r *timerNoReporterSink) Flush() { diff --git a/stats_test.go b/stats_test.go index c42b6ad3..e4222406 100644 --- a/stats_test.go +++ b/stats_test.go @@ -78,7 +78,7 @@ func (r *statsTestReporter) ReportHistogramDurationSamples( } func (r *statsTestReporter) Capabilities() Capabilities { - return capabilitiesReportingNoTaggingNoHistograms + return capabilitiesReportingNoTagging } func (r *statsTestReporter) Flush() {} diff --git a/statsd/reporter.go b/statsd/reporter.go index a6f61f96..78ba7004 100644 --- a/statsd/reporter.go +++ b/statsd/reporter.go @@ -151,10 +151,6 @@ func (r *cactusStatsReporter) Tagging() bool { return false } -func (r *cactusStatsReporter) Histograms() bool { - return true -} - func (r *cactusStatsReporter) Flush() { // no-op } diff --git a/statsd/reporter_test.go b/statsd/reporter_test.go index de65fbe4..75b7647b 100644 --- a/statsd/reporter_test.go +++ b/statsd/reporter_test.go @@ -30,5 +30,4 @@ func TestCapabilities(t *testing.T) { r := NewReporter(nil, Options{}) assert.True(t, r.Capabilities().Reporting()) assert.False(t, r.Capabilities().Tagging()) - assert.True(t, r.Capabilities().Histograms()) } diff --git a/types.go b/types.go index 20a49a87..5d225ce8 100644 --- a/types.go +++ b/types.go @@ -130,7 +130,4 @@ type Capabilities interface { // Tagging returns whether the reporter has the capability for tagged metrics. Tagging() bool - - // Histograms returns whether the reporter has the capability for histogram metrics. - Histograms() bool }