forked from uber-go/tally
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Return existing scopes when using Tagged and SubScope (uber-go#25)
- Loading branch information
1 parent
29de42b
commit 70e704c
Showing
7 changed files
with
1,262 additions
and
266 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright (c) 2017 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
package tally | ||
|
||
import ( | ||
"bytes" | ||
"sort" | ||
) | ||
|
||
const ( | ||
prefixSplitter = '+' | ||
keyPairSplitter = ',' | ||
keyNameSplitter = '=' | ||
) | ||
|
||
var ( | ||
keyGenPool = newKeyGenerationPool(1024, 1024, 32) | ||
nilString = "" | ||
) | ||
|
||
type keyGenerationPool struct { | ||
bufferPool *ObjectPool | ||
stringsPool *ObjectPool | ||
} | ||
|
||
// KeyForStringMap generates a unique key for a map string set combination. | ||
func KeyForStringMap( | ||
stringMap map[string]string, | ||
) string { | ||
return KeyForPrefixedStringMap(nilString, stringMap) | ||
} | ||
|
||
// KeyForPrefixedStringMap generates a unique key for a | ||
// a prefix and a map string set combination. | ||
func KeyForPrefixedStringMap( | ||
prefix string, | ||
stringMap map[string]string, | ||
) string { | ||
keys := keyGenPool.stringsPool.Get().([]string) | ||
for k := range stringMap { | ||
keys = append(keys, k) | ||
} | ||
|
||
sort.Strings(keys) | ||
|
||
buf := keyGenPool.bufferPool.Get().(*bytes.Buffer) | ||
|
||
if prefix != nilString { | ||
buf.WriteString(prefix) | ||
buf.WriteByte(prefixSplitter) | ||
} | ||
|
||
sortedKeysLen := len(stringMap) | ||
for i := 0; i < sortedKeysLen; i++ { | ||
buf.WriteString(keys[i]) | ||
buf.WriteByte(keyNameSplitter) | ||
buf.WriteString(stringMap[keys[i]]) | ||
if i != sortedKeysLen-1 { | ||
buf.WriteByte(keyPairSplitter) | ||
} | ||
} | ||
|
||
key := buf.String() | ||
keyGenPool.release(buf, keys) | ||
return key | ||
} | ||
|
||
func newKeyGenerationPool(size, blen, slen int) *keyGenerationPool { | ||
b := NewObjectPool(size) | ||
b.Init(func() interface{} { | ||
return bytes.NewBuffer(make([]byte, 0, blen)) | ||
}) | ||
|
||
s := NewObjectPool(size) | ||
s.Init(func() interface{} { | ||
return make([]string, 0, slen) | ||
}) | ||
|
||
return &keyGenerationPool{ | ||
bufferPool: b, | ||
stringsPool: s, | ||
} | ||
} | ||
|
||
func (s *keyGenerationPool) release(b *bytes.Buffer, strs []string) { | ||
b.Reset() | ||
s.bufferPool.Put(b) | ||
|
||
for i := range strs { | ||
strs[i] = nilString | ||
} | ||
s.stringsPool.Put(strs[:0]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Copyright (c) 2017 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
package tally | ||
|
||
// ObjectPool is an minimalistic object pool to avoid | ||
// any circular dependencies on any other object pool. | ||
type ObjectPool struct { | ||
values chan interface{} | ||
alloc func() interface{} | ||
} | ||
|
||
// NewObjectPool creates a new pool. | ||
func NewObjectPool(size int) *ObjectPool { | ||
return &ObjectPool{ | ||
values: make(chan interface{}, size), | ||
} | ||
} | ||
|
||
// Init initializes the object pool. | ||
func (p *ObjectPool) Init(alloc func() interface{}) { | ||
p.alloc = alloc | ||
|
||
for i := 0; i < cap(p.values); i++ { | ||
p.values <- p.alloc() | ||
} | ||
} | ||
|
||
// Get gets an object from the pool. | ||
func (p *ObjectPool) Get() interface{} { | ||
var v interface{} | ||
select { | ||
case v = <-p.values: | ||
default: | ||
v = p.alloc() | ||
} | ||
return v | ||
} | ||
|
||
// Put puts an object back to the pool. | ||
func (p *ObjectPool) Put(obj interface{}) { | ||
select { | ||
case p.values <- obj: | ||
default: | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,117 @@ | ||
# Example Usage | ||
# A buffered Prometheus reporter | ||
|
||
`main.go`: | ||
See `examples/prometheus_main.go` for an end to end example. | ||
|
||
## Options | ||
|
||
You can use a specific Prometheus registry, and you can use | ||
either histograms or summaries for timers. | ||
|
||
The reporter options are: | ||
|
||
```go | ||
// Options is a set of options for the tally reporter. | ||
type Options struct { | ||
// Registerer is the prometheus registerer to register | ||
// metrics with. Use nil to specify the default registerer. | ||
Registerer prom.Registerer | ||
|
||
// DefaultTimerType is the default type timer type to create | ||
// when using timers. It's default value is a histogram timer type. | ||
DefaultTimerType TimerType | ||
|
||
// DefaultHistogramBuckets is the default histogram buckets | ||
// to use. Use nil to specify the default histogram buckets. | ||
DefaultHistogramBuckets []float64 | ||
|
||
// DefaultSummaryObjectives is the default summary objectives | ||
// to use. Use nil to specify the default summary objectives. | ||
DefaultSummaryObjectives map[float64]float64 | ||
|
||
// OnRegisterError defines a method to call to when registering | ||
// a metric with the registerer fails. Use nil to specify | ||
// to panic by default when registering a metric fails. | ||
OnRegisterError func(err error) | ||
} | ||
``` | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"math/rand" | ||
"net/http" | ||
"time" | ||
The timer types are: | ||
|
||
```go | ||
// TimerType describes a type of timer | ||
type TimerType int | ||
|
||
"github.com/uber-go/tally" | ||
"github.com/uber-go/tally/prometheus" | ||
const ( | ||
// HistogramTimerType is a timer type that reports into a histogram | ||
HistogramTimerType TimerType = iota | ||
// SummaryTimerType is a timer type that reports into a summary | ||
SummaryTimerType | ||
) | ||
``` | ||
|
||
You can also pre-register help description text ahead of using a metric | ||
that will be named and tagged identically with `tally`. You can also | ||
access the Prometheus HTTP handler directly. | ||
|
||
func main() { | ||
r := prometheus.NewReporter(nil) | ||
// note the "_" separator. Prometheus doesnt like metrics with "." in them. | ||
scope, finisher := tally.NewRootScope("prefix", map[string]string{}, r, 1*time.Second, "_") | ||
defer finisher.Close() | ||
counter := scope.Counter("test_counter") | ||
gauge := scope.Gauge("test_gauge") | ||
histogram := scope.Timer("test_histogram") | ||
go func() { | ||
for { | ||
counter.Inc(1) | ||
time.Sleep(1000000) | ||
} | ||
}() | ||
go func() { | ||
for { | ||
gauge.Update(rand.Float64() * 1000) | ||
time.Sleep(1000000) | ||
} | ||
}() | ||
go func() { | ||
for { | ||
sw := histogram.Start() | ||
time.Sleep(time.Duration(rand.Float64() * 1000 * 1000)) | ||
sw.Stop() | ||
time.Sleep(1000000) | ||
} | ||
}() | ||
http.Handle("/metrics", r.HTTPHandler()) | ||
fmt.Printf("Serving :8080/metrics\n") | ||
fmt.Printf("%v\n", http.ListenAndServe(":8080", nil)) | ||
select {} | ||
The returned reporter interface: | ||
|
||
```go | ||
// Reporter is a Prometheus backed tally reporter. | ||
type Reporter interface { | ||
tally.CachedStatsReporter | ||
|
||
// HTTPHandler provides the Prometheus HTTP scrape handler. | ||
HTTPHandler() http.Handler | ||
|
||
// RegisterCounter is a helper method to initialize a counter | ||
// in the Prometheus backend with a given help text. | ||
// If not called explicitly, the Reporter will create one for | ||
// you on first use, with a not super helpful HELP string. | ||
RegisterCounter( | ||
name string, | ||
tagKeys []string, | ||
desc string, | ||
) (*prom.CounterVec, error) | ||
|
||
// RegisterGauge is a helper method to initialize a gauge | ||
// in the prometheus backend with a given help text. | ||
// If not called explicitly, the Reporter will create one for | ||
// you on first use, with a not super helpful HELP string. | ||
RegisterGauge( | ||
name string, | ||
tagKeys []string, | ||
desc string, | ||
) (*prom.GaugeVec, error) | ||
|
||
// RegisterTimer is a helper method to initialize a timer | ||
// summary or histogram vector in the prometheus backend | ||
// with a given help text. | ||
// If not called explicitly, the Reporter will create one for | ||
// you on first use, with a not super helpful HELP string. | ||
// You may pass opts as nil to get the default timer type | ||
// and objectives/buckets. | ||
// You may also pass objectives/buckets as nil in opts to | ||
// get the default objectives/buckets for the specified | ||
// timer type. | ||
RegisterTimer( | ||
name string, | ||
tagKeys []string, | ||
desc string, | ||
opts *RegisterTimerOptions, | ||
) (TimerUnion, error) | ||
} | ||
``` | ||
|
||
The register timer options: | ||
|
||
```go | ||
// RegisterTimerOptions provides options when registering a timer on demand. | ||
// By default you can pass nil for the options to get the reporter defaults. | ||
type RegisterTimerOptions struct { | ||
TimerType TimerType | ||
HistogramBuckets []float64 | ||
SummaryObjectives map[float64]float64 | ||
} | ||
``` | ||
|
||
|
Oops, something went wrong.