Skip to content

Commit

Permalink
Return existing scopes when using Tagged and SubScope (uber-go#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
robskillington authored Feb 6, 2017
1 parent 29de42b commit 70e704c
Show file tree
Hide file tree
Showing 7 changed files with 1,262 additions and 266 deletions.
111 changes: 111 additions & 0 deletions key_gen.go
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])
}
63 changes: 63 additions & 0 deletions pool.go
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:
}
}
154 changes: 107 additions & 47 deletions prometheus/README.md
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
}
```


Loading

0 comments on commit 70e704c

Please sign in to comment.