mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Delete metrics directory
Signed-off-by: Isabel Schöps Thiel @IsabelSchoepd <155141998+IST-Github@users.noreply.github.com>
This commit is contained in:
parent
4178b93799
commit
d9efc41814
76 changed files with 0 additions and 7824 deletions
|
|
@ -1 +0,0 @@
|
|||
This repo has been forked from https://github.com/rcrowley/go-metrics at commit e181e09
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
Copyright 2012 Richard Crowley. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY RICHARD CROWLEY ``AS IS'' AND ANY EXPRESS
|
||||
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL RICHARD CROWLEY OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation
|
||||
are those of the authors and should not be interpreted as representing
|
||||
official policies, either expressed or implied, of Richard Crowley.
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
go-metrics
|
||||
==========
|
||||
|
||||

|
||||
|
||||
Go port of Coda Hale's Metrics library: <https://github.com/dropwizard/metrics>.
|
||||
|
||||
Documentation: <https://godoc.org/github.com/rcrowley/go-metrics>.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Create and update metrics:
|
||||
|
||||
```go
|
||||
c := metrics.NewCounter()
|
||||
metrics.Register("foo", c)
|
||||
c.Inc(47)
|
||||
|
||||
g := metrics.NewGauge()
|
||||
metrics.Register("bar", g)
|
||||
g.Update(47)
|
||||
|
||||
r := NewRegistry()
|
||||
g := metrics.NewRegisteredFunctionalGauge("cache-evictions", r, func() int64 { return cache.getEvictionsCount() })
|
||||
|
||||
s := metrics.NewExpDecaySample(1028, 0.015) // or metrics.NewUniformSample(1028)
|
||||
h := metrics.NewHistogram(s)
|
||||
metrics.Register("baz", h)
|
||||
h.Update(47)
|
||||
|
||||
m := metrics.NewMeter()
|
||||
metrics.Register("quux", m)
|
||||
m.Mark(47)
|
||||
|
||||
t := metrics.NewTimer()
|
||||
metrics.Register("bang", t)
|
||||
t.Time(func() {})
|
||||
t.Update(47)
|
||||
```
|
||||
|
||||
Register() is not threadsafe. For threadsafe metric registration use
|
||||
GetOrRegister:
|
||||
|
||||
```go
|
||||
t := metrics.GetOrRegisterTimer("account.create.latency", nil)
|
||||
t.Time(func() {})
|
||||
t.Update(47)
|
||||
```
|
||||
|
||||
**NOTE:** Be sure to unregister short-lived meters and timers otherwise they will
|
||||
leak memory:
|
||||
|
||||
```go
|
||||
// Will call Stop() on the Meter to allow for garbage collection
|
||||
metrics.Unregister("quux")
|
||||
// Or similarly for a Timer that embeds a Meter
|
||||
metrics.Unregister("bang")
|
||||
```
|
||||
|
||||
Periodically log every metric in human-readable form to standard error:
|
||||
|
||||
```go
|
||||
go metrics.Log(metrics.DefaultRegistry, 5 * time.Second, log.New(os.Stderr, "metrics: ", log.Lmicroseconds))
|
||||
```
|
||||
|
||||
Periodically log every metric in slightly-more-parseable form to syslog:
|
||||
|
||||
```go
|
||||
w, _ := syslog.Dial("unixgram", "/dev/log", syslog.LOG_INFO, "metrics")
|
||||
go metrics.Syslog(metrics.DefaultRegistry, 60e9, w)
|
||||
```
|
||||
|
||||
Periodically emit every metric to Graphite using the [Graphite client](https://github.com/cyberdelia/go-metrics-graphite):
|
||||
|
||||
```go
|
||||
|
||||
import "github.com/cyberdelia/go-metrics-graphite"
|
||||
|
||||
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:2003")
|
||||
go graphite.Graphite(metrics.DefaultRegistry, 10e9, "metrics", addr)
|
||||
```
|
||||
|
||||
Periodically emit every metric into InfluxDB:
|
||||
|
||||
**NOTE:** this has been pulled out of the library due to constant fluctuations
|
||||
in the InfluxDB API. In fact, all client libraries are on their way out. see
|
||||
issues [#121](https://github.com/rcrowley/go-metrics/issues/121) and
|
||||
[#124](https://github.com/rcrowley/go-metrics/issues/124) for progress and details.
|
||||
|
||||
```go
|
||||
import "github.com/vrischmann/go-metrics-influxdb"
|
||||
|
||||
go influxdb.InfluxDB(metrics.DefaultRegistry,
|
||||
10e9,
|
||||
"127.0.0.1:8086",
|
||||
"database-name",
|
||||
"username",
|
||||
"password"
|
||||
)
|
||||
```
|
||||
|
||||
Periodically upload every metric to Librato using the [Librato client](https://github.com/mihasya/go-metrics-librato):
|
||||
|
||||
**Note**: the client included with this repository under the `librato` package
|
||||
has been deprecated and moved to the repository linked above.
|
||||
|
||||
```go
|
||||
import "github.com/mihasya/go-metrics-librato"
|
||||
|
||||
go librato.Librato(metrics.DefaultRegistry,
|
||||
10e9, // interval
|
||||
"example@example.com", // account owner email address
|
||||
"token", // Librato API token
|
||||
"hostname", // source
|
||||
[]float64{0.95}, // percentiles to send
|
||||
time.Millisecond, // time unit
|
||||
)
|
||||
```
|
||||
|
||||
Periodically emit every metric to StatHat:
|
||||
|
||||
```go
|
||||
import "github.com/rcrowley/go-metrics/stathat"
|
||||
|
||||
go stathat.Stathat(metrics.DefaultRegistry, 10e9, "example@example.com")
|
||||
```
|
||||
|
||||
Maintain all metrics along with expvars at `/debug/metrics`:
|
||||
|
||||
This uses the same mechanism as [the official expvar](https://golang.org/pkg/expvar/)
|
||||
but exposed under `/debug/metrics`, which shows a json representation of all your usual expvars
|
||||
as well as all your go-metrics.
|
||||
|
||||
|
||||
```go
|
||||
import "github.com/rcrowley/go-metrics/exp"
|
||||
|
||||
exp.Exp(metrics.DefaultRegistry)
|
||||
```
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
```sh
|
||||
go get github.com/rcrowley/go-metrics
|
||||
```
|
||||
|
||||
StatHat support additionally requires their Go client:
|
||||
|
||||
```sh
|
||||
go get github.com/stathat/go
|
||||
```
|
||||
|
||||
Publishing Metrics
|
||||
------------------
|
||||
|
||||
Clients are available for the following destinations:
|
||||
|
||||
* Librato - https://github.com/mihasya/go-metrics-librato
|
||||
* Graphite - https://github.com/cyberdelia/go-metrics-graphite
|
||||
* InfluxDB - https://github.com/vrischmann/go-metrics-influxdb
|
||||
* Ganglia - https://github.com/appscode/metlia
|
||||
* Prometheus - https://github.com/deathowl/go-metrics-prometheus
|
||||
* DataDog - https://github.com/syntaqx/go-metrics-datadog
|
||||
* SignalFX - https://github.com/pascallouisperez/go-metrics-signalfx
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package metrics
|
||||
|
||||
// Config contains the configuration for the metric collection.
|
||||
type Config struct {
|
||||
Enabled bool `toml:",omitempty"`
|
||||
EnabledExpensive bool `toml:",omitempty"`
|
||||
HTTP string `toml:",omitempty"`
|
||||
Port int `toml:",omitempty"`
|
||||
EnableInfluxDB bool `toml:",omitempty"`
|
||||
InfluxDBEndpoint string `toml:",omitempty"`
|
||||
InfluxDBDatabase string `toml:",omitempty"`
|
||||
InfluxDBUsername string `toml:",omitempty"`
|
||||
InfluxDBPassword string `toml:",omitempty"`
|
||||
InfluxDBTags string `toml:",omitempty"`
|
||||
|
||||
EnableInfluxDBV2 bool `toml:",omitempty"`
|
||||
InfluxDBToken string `toml:",omitempty"`
|
||||
InfluxDBBucket string `toml:",omitempty"`
|
||||
InfluxDBOrganization string `toml:",omitempty"`
|
||||
}
|
||||
|
||||
// DefaultConfig is the default config for metrics used in go-ethereum.
|
||||
var DefaultConfig = Config{
|
||||
Enabled: false,
|
||||
EnabledExpensive: false,
|
||||
HTTP: "127.0.0.1",
|
||||
Port: 6060,
|
||||
EnableInfluxDB: false,
|
||||
InfluxDBEndpoint: "http://localhost:8086",
|
||||
InfluxDBDatabase: "geth",
|
||||
InfluxDBUsername: "test",
|
||||
InfluxDBPassword: "test",
|
||||
InfluxDBTags: "host=localhost",
|
||||
|
||||
// influxdbv2-specific flags
|
||||
EnableInfluxDBV2: false,
|
||||
InfluxDBToken: "test",
|
||||
InfluxDBBucket: "geth",
|
||||
InfluxDBOrganization: "geth",
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type CounterSnapshot interface {
|
||||
Count() int64
|
||||
}
|
||||
|
||||
// Counters hold an int64 value that can be incremented and decremented.
|
||||
type Counter interface {
|
||||
Clear()
|
||||
Dec(int64)
|
||||
Inc(int64)
|
||||
Snapshot() CounterSnapshot
|
||||
}
|
||||
|
||||
// GetOrRegisterCounter returns an existing Counter or constructs and registers
|
||||
// a new StandardCounter.
|
||||
func GetOrRegisterCounter(name string, r Registry) Counter {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewCounter).(Counter)
|
||||
}
|
||||
|
||||
// GetOrRegisterCounterForced returns an existing Counter or constructs and registers a
|
||||
// new Counter no matter the global switch is enabled or not.
|
||||
// Be sure to unregister the counter from the registry once it is of no use to
|
||||
// allow for garbage collection.
|
||||
func GetOrRegisterCounterForced(name string, r Registry) Counter {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewCounterForced).(Counter)
|
||||
}
|
||||
|
||||
// NewCounter constructs a new StandardCounter.
|
||||
func NewCounter() Counter {
|
||||
if !Enabled {
|
||||
return NilCounter{}
|
||||
}
|
||||
return new(StandardCounter)
|
||||
}
|
||||
|
||||
// NewCounterForced constructs a new StandardCounter and returns it no matter if
|
||||
// the global switch is enabled or not.
|
||||
func NewCounterForced() Counter {
|
||||
return new(StandardCounter)
|
||||
}
|
||||
|
||||
// NewRegisteredCounter constructs and registers a new StandardCounter.
|
||||
func NewRegisteredCounter(name string, r Registry) Counter {
|
||||
c := NewCounter()
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// NewRegisteredCounterForced constructs and registers a new StandardCounter
|
||||
// and launches a goroutine no matter the global switch is enabled or not.
|
||||
// Be sure to unregister the counter from the registry once it is of no use to
|
||||
// allow for garbage collection.
|
||||
func NewRegisteredCounterForced(name string, r Registry) Counter {
|
||||
c := NewCounterForced()
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// counterSnapshot is a read-only copy of another Counter.
|
||||
type counterSnapshot int64
|
||||
|
||||
// Count returns the count at the time the snapshot was taken.
|
||||
func (c counterSnapshot) Count() int64 { return int64(c) }
|
||||
|
||||
// NilCounter is a no-op Counter.
|
||||
type NilCounter struct{}
|
||||
|
||||
func (NilCounter) Clear() {}
|
||||
func (NilCounter) Dec(i int64) {}
|
||||
func (NilCounter) Inc(i int64) {}
|
||||
func (NilCounter) Snapshot() CounterSnapshot { return (*emptySnapshot)(nil) }
|
||||
|
||||
// StandardCounter is the standard implementation of a Counter and uses the
|
||||
// sync/atomic package to manage a single int64 value.
|
||||
type StandardCounter atomic.Int64
|
||||
|
||||
// Clear sets the counter to zero.
|
||||
func (c *StandardCounter) Clear() {
|
||||
(*atomic.Int64)(c).Store(0)
|
||||
}
|
||||
|
||||
// Dec decrements the counter by the given amount.
|
||||
func (c *StandardCounter) Dec(i int64) {
|
||||
(*atomic.Int64)(c).Add(-i)
|
||||
}
|
||||
|
||||
// Inc increments the counter by the given amount.
|
||||
func (c *StandardCounter) Inc(i int64) {
|
||||
(*atomic.Int64)(c).Add(i)
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the counter.
|
||||
func (c *StandardCounter) Snapshot() CounterSnapshot {
|
||||
return counterSnapshot((*atomic.Int64)(c).Load())
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type CounterFloat64Snapshot interface {
|
||||
Count() float64
|
||||
}
|
||||
|
||||
// CounterFloat64 holds a float64 value that can be incremented and decremented.
|
||||
type CounterFloat64 interface {
|
||||
Clear()
|
||||
Dec(float64)
|
||||
Inc(float64)
|
||||
Snapshot() CounterFloat64Snapshot
|
||||
}
|
||||
|
||||
// GetOrRegisterCounterFloat64 returns an existing CounterFloat64 or constructs and registers
|
||||
// a new StandardCounterFloat64.
|
||||
func GetOrRegisterCounterFloat64(name string, r Registry) CounterFloat64 {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewCounterFloat64).(CounterFloat64)
|
||||
}
|
||||
|
||||
// GetOrRegisterCounterFloat64Forced returns an existing CounterFloat64 or constructs and registers a
|
||||
// new CounterFloat64 no matter the global switch is enabled or not.
|
||||
// Be sure to unregister the counter from the registry once it is of no use to
|
||||
// allow for garbage collection.
|
||||
func GetOrRegisterCounterFloat64Forced(name string, r Registry) CounterFloat64 {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewCounterFloat64Forced).(CounterFloat64)
|
||||
}
|
||||
|
||||
// NewCounterFloat64 constructs a new StandardCounterFloat64.
|
||||
func NewCounterFloat64() CounterFloat64 {
|
||||
if !Enabled {
|
||||
return NilCounterFloat64{}
|
||||
}
|
||||
return &StandardCounterFloat64{}
|
||||
}
|
||||
|
||||
// NewCounterFloat64Forced constructs a new StandardCounterFloat64 and returns it no matter if
|
||||
// the global switch is enabled or not.
|
||||
func NewCounterFloat64Forced() CounterFloat64 {
|
||||
return &StandardCounterFloat64{}
|
||||
}
|
||||
|
||||
// NewRegisteredCounterFloat64 constructs and registers a new StandardCounterFloat64.
|
||||
func NewRegisteredCounterFloat64(name string, r Registry) CounterFloat64 {
|
||||
c := NewCounterFloat64()
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// NewRegisteredCounterFloat64Forced constructs and registers a new StandardCounterFloat64
|
||||
// and launches a goroutine no matter the global switch is enabled or not.
|
||||
// Be sure to unregister the counter from the registry once it is of no use to
|
||||
// allow for garbage collection.
|
||||
func NewRegisteredCounterFloat64Forced(name string, r Registry) CounterFloat64 {
|
||||
c := NewCounterFloat64Forced()
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// counterFloat64Snapshot is a read-only copy of another CounterFloat64.
|
||||
type counterFloat64Snapshot float64
|
||||
|
||||
// Count returns the value at the time the snapshot was taken.
|
||||
func (c counterFloat64Snapshot) Count() float64 { return float64(c) }
|
||||
|
||||
type NilCounterFloat64 struct{}
|
||||
|
||||
func (NilCounterFloat64) Clear() {}
|
||||
func (NilCounterFloat64) Count() float64 { return 0.0 }
|
||||
func (NilCounterFloat64) Dec(i float64) {}
|
||||
func (NilCounterFloat64) Inc(i float64) {}
|
||||
func (NilCounterFloat64) Snapshot() CounterFloat64Snapshot { return NilCounterFloat64{} }
|
||||
|
||||
// StandardCounterFloat64 is the standard implementation of a CounterFloat64 and uses the
|
||||
// atomic to manage a single float64 value.
|
||||
type StandardCounterFloat64 struct {
|
||||
floatBits atomic.Uint64
|
||||
}
|
||||
|
||||
// Clear sets the counter to zero.
|
||||
func (c *StandardCounterFloat64) Clear() {
|
||||
c.floatBits.Store(0)
|
||||
}
|
||||
|
||||
// Dec decrements the counter by the given amount.
|
||||
func (c *StandardCounterFloat64) Dec(v float64) {
|
||||
atomicAddFloat(&c.floatBits, -v)
|
||||
}
|
||||
|
||||
// Inc increments the counter by the given amount.
|
||||
func (c *StandardCounterFloat64) Inc(v float64) {
|
||||
atomicAddFloat(&c.floatBits, v)
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the counter.
|
||||
func (c *StandardCounterFloat64) Snapshot() CounterFloat64Snapshot {
|
||||
v := math.Float64frombits(c.floatBits.Load())
|
||||
return counterFloat64Snapshot(v)
|
||||
}
|
||||
|
||||
func atomicAddFloat(fbits *atomic.Uint64, v float64) {
|
||||
for {
|
||||
loadedBits := fbits.Load()
|
||||
newBits := math.Float64bits(math.Float64frombits(loadedBits) + v)
|
||||
if fbits.CompareAndSwap(loadedBits, newBits) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkCounterFloat64(b *testing.B) {
|
||||
c := NewCounterFloat64()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.Inc(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCounterFloat64Parallel(b *testing.B) {
|
||||
c := NewCounterFloat64()
|
||||
b.ResetTimer()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.Inc(1.0)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if have, want := c.Snapshot().Count(), 10.0*float64(b.N); have != want {
|
||||
b.Fatalf("have %f want %f", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterFloat64Clear(t *testing.T) {
|
||||
c := NewCounterFloat64()
|
||||
c.Inc(1.0)
|
||||
c.Clear()
|
||||
if count := c.Snapshot().Count(); count != 0 {
|
||||
t.Errorf("c.Count(): 0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterFloat64Dec1(t *testing.T) {
|
||||
c := NewCounterFloat64()
|
||||
c.Dec(1.0)
|
||||
if count := c.Snapshot().Count(); count != -1.0 {
|
||||
t.Errorf("c.Count(): -1.0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterFloat64Dec2(t *testing.T) {
|
||||
c := NewCounterFloat64()
|
||||
c.Dec(2.0)
|
||||
if count := c.Snapshot().Count(); count != -2.0 {
|
||||
t.Errorf("c.Count(): -2.0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterFloat64Inc1(t *testing.T) {
|
||||
c := NewCounterFloat64()
|
||||
c.Inc(1.0)
|
||||
if count := c.Snapshot().Count(); count != 1.0 {
|
||||
t.Errorf("c.Count(): 1.0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterFloat64Inc2(t *testing.T) {
|
||||
c := NewCounterFloat64()
|
||||
c.Inc(2.0)
|
||||
if count := c.Snapshot().Count(); count != 2.0 {
|
||||
t.Errorf("c.Count(): 2.0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterFloat64Snapshot(t *testing.T) {
|
||||
c := NewCounterFloat64()
|
||||
c.Inc(1.0)
|
||||
snapshot := c.Snapshot()
|
||||
c.Inc(1.0)
|
||||
if count := snapshot.Count(); count != 1.0 {
|
||||
t.Errorf("c.Count(): 1.0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterFloat64Zero(t *testing.T) {
|
||||
c := NewCounterFloat64()
|
||||
if count := c.Snapshot().Count(); count != 0 {
|
||||
t.Errorf("c.Count(): 0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrRegisterCounterFloat64(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
NewRegisteredCounterFloat64("foo", r).Inc(47.0)
|
||||
if c := GetOrRegisterCounterFloat64("foo", r).Snapshot(); c.Count() != 47.0 {
|
||||
t.Fatal(c)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkCounter(b *testing.B) {
|
||||
c := NewCounter()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.Inc(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterClear(t *testing.T) {
|
||||
c := NewCounter()
|
||||
c.Inc(1)
|
||||
c.Clear()
|
||||
if count := c.Snapshot().Count(); count != 0 {
|
||||
t.Errorf("c.Count(): 0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterDec1(t *testing.T) {
|
||||
c := NewCounter()
|
||||
c.Dec(1)
|
||||
if count := c.Snapshot().Count(); count != -1 {
|
||||
t.Errorf("c.Count(): -1 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterDec2(t *testing.T) {
|
||||
c := NewCounter()
|
||||
c.Dec(2)
|
||||
if count := c.Snapshot().Count(); count != -2 {
|
||||
t.Errorf("c.Count(): -2 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterInc1(t *testing.T) {
|
||||
c := NewCounter()
|
||||
c.Inc(1)
|
||||
if count := c.Snapshot().Count(); count != 1 {
|
||||
t.Errorf("c.Count(): 1 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterInc2(t *testing.T) {
|
||||
c := NewCounter()
|
||||
c.Inc(2)
|
||||
if count := c.Snapshot().Count(); count != 2 {
|
||||
t.Errorf("c.Count(): 2 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterSnapshot(t *testing.T) {
|
||||
c := NewCounter()
|
||||
c.Inc(1)
|
||||
snapshot := c.Snapshot()
|
||||
c.Inc(1)
|
||||
if count := snapshot.Count(); count != 1 {
|
||||
t.Errorf("c.Count(): 1 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterZero(t *testing.T) {
|
||||
c := NewCounter()
|
||||
if count := c.Snapshot().Count(); count != 0 {
|
||||
t.Errorf("c.Count(): 0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrRegisterCounter(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
NewRegisteredCounter("foo", r).Inc(47)
|
||||
if c := GetOrRegisterCounter("foo", r).Snapshot(); c.Count() != 47 {
|
||||
t.Fatal(c)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package metrics
|
||||
|
||||
// CPUStats is the system and process CPU stats.
|
||||
// All values are in seconds.
|
||||
type CPUStats struct {
|
||||
GlobalTime float64 // Time spent by the CPU working on all processes
|
||||
GlobalWait float64 // Time spent by waiting on disk for all processes
|
||||
LocalTime float64 // Time spent by the CPU working on this process
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build ios || js
|
||||
// +build ios js
|
||||
|
||||
package metrics
|
||||
|
||||
// ReadCPUStats retrieves the current CPU stats. Internally this uses `gosigar`,
|
||||
// which is not supported on the platforms in this file.
|
||||
func ReadCPUStats(stats *CPUStats) {}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !ios && !js
|
||||
// +build !ios,!js
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
)
|
||||
|
||||
// ReadCPUStats retrieves the current CPU stats.
|
||||
func ReadCPUStats(stats *CPUStats) {
|
||||
// passing false to request all cpu times
|
||||
timeStats, err := cpu.Times(false)
|
||||
if err != nil {
|
||||
log.Error("Could not read cpu stats", "err", err)
|
||||
return
|
||||
}
|
||||
if len(timeStats) == 0 {
|
||||
log.Error("Empty cpu stats")
|
||||
return
|
||||
}
|
||||
// requesting all cpu times will always return an array with only one time stats entry
|
||||
timeStat := timeStats[0]
|
||||
stats.GlobalTime = timeStat.User + timeStat.Nice + timeStat.System
|
||||
stats.GlobalWait = timeStat.Iowait
|
||||
stats.LocalTime = getProcessCPUTime()
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build windows || js
|
||||
// +build windows js
|
||||
|
||||
package metrics
|
||||
|
||||
// getProcessCPUTime returns 0 on Windows as there is no system call to resolve
|
||||
// the actual process' CPU time.
|
||||
func getProcessCPUTime() float64 {
|
||||
return 0
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !windows && !js
|
||||
// +build !windows,!js
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
syscall "golang.org/x/sys/unix"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// getProcessCPUTime retrieves the process' CPU time since program startup.
|
||||
func getProcessCPUTime() float64 {
|
||||
var usage syscall.Rusage
|
||||
if err := syscall.Getrusage(syscall.RUSAGE_SELF, &usage); err != nil {
|
||||
log.Warn("Failed to retrieve CPU time", "err", err)
|
||||
return 0
|
||||
}
|
||||
return float64(usage.Utime.Sec+usage.Stime.Sec) + float64(usage.Utime.Usec+usage.Stime.Usec)/1000000 //nolint:unconvert
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
debugMetrics struct {
|
||||
GCStats struct {
|
||||
LastGC Gauge
|
||||
NumGC Gauge
|
||||
Pause Histogram
|
||||
//PauseQuantiles Histogram
|
||||
PauseTotal Gauge
|
||||
}
|
||||
ReadGCStats Timer
|
||||
}
|
||||
gcStats debug.GCStats
|
||||
)
|
||||
|
||||
// Capture new values for the Go garbage collector statistics exported in
|
||||
// debug.GCStats. This is designed to be called as a goroutine.
|
||||
func CaptureDebugGCStats(r Registry, d time.Duration) {
|
||||
for range time.Tick(d) {
|
||||
CaptureDebugGCStatsOnce(r)
|
||||
}
|
||||
}
|
||||
|
||||
// Capture new values for the Go garbage collector statistics exported in
|
||||
// debug.GCStats. This is designed to be called in a background goroutine.
|
||||
// Giving a registry which has not been given to RegisterDebugGCStats will
|
||||
// panic.
|
||||
//
|
||||
// Be careful (but much less so) with this because debug.ReadGCStats calls
|
||||
// the C function runtime·lock(runtime·mheap) which, while not a stop-the-world
|
||||
// operation, isn't something you want to be doing all the time.
|
||||
func CaptureDebugGCStatsOnce(r Registry) {
|
||||
lastGC := gcStats.LastGC
|
||||
t := time.Now()
|
||||
debug.ReadGCStats(&gcStats)
|
||||
debugMetrics.ReadGCStats.UpdateSince(t)
|
||||
|
||||
debugMetrics.GCStats.LastGC.Update(gcStats.LastGC.UnixNano())
|
||||
debugMetrics.GCStats.NumGC.Update(gcStats.NumGC)
|
||||
if lastGC != gcStats.LastGC && 0 < len(gcStats.Pause) {
|
||||
debugMetrics.GCStats.Pause.Update(int64(gcStats.Pause[0]))
|
||||
}
|
||||
//debugMetrics.GCStats.PauseQuantiles.Update(gcStats.PauseQuantiles)
|
||||
debugMetrics.GCStats.PauseTotal.Update(int64(gcStats.PauseTotal))
|
||||
}
|
||||
|
||||
// Register metrics for the Go garbage collector statistics exported in
|
||||
// debug.GCStats. The metrics are named by their fully-qualified Go symbols,
|
||||
// i.e. debug.GCStats.PauseTotal.
|
||||
func RegisterDebugGCStats(r Registry) {
|
||||
debugMetrics.GCStats.LastGC = NewGauge()
|
||||
debugMetrics.GCStats.NumGC = NewGauge()
|
||||
debugMetrics.GCStats.Pause = NewHistogram(NewExpDecaySample(1028, 0.015))
|
||||
//debugMetrics.GCStats.PauseQuantiles = NewHistogram(NewExpDecaySample(1028, 0.015))
|
||||
debugMetrics.GCStats.PauseTotal = NewGauge()
|
||||
debugMetrics.ReadGCStats = NewTimer()
|
||||
|
||||
r.Register("debug.GCStats.LastGC", debugMetrics.GCStats.LastGC)
|
||||
r.Register("debug.GCStats.NumGC", debugMetrics.GCStats.NumGC)
|
||||
r.Register("debug.GCStats.Pause", debugMetrics.GCStats.Pause)
|
||||
//r.Register("debug.GCStats.PauseQuantiles", debugMetrics.GCStats.PauseQuantiles)
|
||||
r.Register("debug.GCStats.PauseTotal", debugMetrics.GCStats.PauseTotal)
|
||||
r.Register("debug.ReadGCStats", debugMetrics.ReadGCStats)
|
||||
}
|
||||
|
||||
// Allocate an initial slice for gcStats.Pause to avoid allocations during
|
||||
// normal operation.
|
||||
func init() {
|
||||
gcStats.Pause = make([]time.Duration, 11)
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkDebugGCStats(b *testing.B) {
|
||||
r := NewRegistry()
|
||||
RegisterDebugGCStats(r)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
CaptureDebugGCStatsOnce(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebugGCStatsBlocking(t *testing.T) {
|
||||
if g := runtime.GOMAXPROCS(0); g < 2 {
|
||||
t.Skipf("skipping TestDebugGCMemStatsBlocking with GOMAXPROCS=%d\n", g)
|
||||
return
|
||||
}
|
||||
ch := make(chan int)
|
||||
go testDebugGCStatsBlocking(ch)
|
||||
var gcStats debug.GCStats
|
||||
t0 := time.Now()
|
||||
debug.ReadGCStats(&gcStats)
|
||||
t1 := time.Now()
|
||||
t.Log("i++ during debug.ReadGCStats:", <-ch)
|
||||
go testDebugGCStatsBlocking(ch)
|
||||
d := t1.Sub(t0)
|
||||
t.Log(d)
|
||||
time.Sleep(d)
|
||||
t.Log("i++ during time.Sleep:", <-ch)
|
||||
}
|
||||
|
||||
func testDebugGCStatsBlocking(ch chan int) {
|
||||
i := 0
|
||||
for {
|
||||
select {
|
||||
case ch <- i:
|
||||
return
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package metrics
|
||||
|
||||
// DiskStats is the per process disk io stats.
|
||||
type DiskStats struct {
|
||||
ReadCount int64 // Number of read operations executed
|
||||
ReadBytes int64 // Total number of bytes read
|
||||
WriteCount int64 // Number of write operations executed
|
||||
WriteBytes int64 // Total number of byte written
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the Linux implementation of process disk IO counter retrieval.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ReadDiskStats retrieves the disk IO stats belonging to the current process.
|
||||
func ReadDiskStats(stats *DiskStats) error {
|
||||
// Open the process disk IO counter file
|
||||
inf, err := os.Open(fmt.Sprintf("/proc/%d/io", os.Getpid()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer inf.Close()
|
||||
in := bufio.NewReader(inf)
|
||||
|
||||
// Iterate over the IO counter, and extract what we need
|
||||
for {
|
||||
// Read the next line and split to key and value
|
||||
line, err := in.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
parts := strings.Split(line, ":")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the counter based on the key
|
||||
switch key {
|
||||
case "syscr":
|
||||
stats.ReadCount = value
|
||||
case "syscw":
|
||||
stats.WriteCount = value
|
||||
case "rchar":
|
||||
stats.ReadBytes = value
|
||||
case "wchar":
|
||||
stats.WriteBytes = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package metrics
|
||||
|
||||
import "errors"
|
||||
|
||||
// ReadDiskStats retrieves the disk IO stats belonging to the current process.
|
||||
func ReadDiskStats(stats *DiskStats) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
111
metrics/ewma.go
111
metrics/ewma.go
|
|
@ -1,111 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EWMASnapshot interface {
|
||||
Rate() float64
|
||||
}
|
||||
|
||||
// EWMAs continuously calculate an exponentially-weighted moving average
|
||||
// based on an outside source of clock ticks.
|
||||
type EWMA interface {
|
||||
Snapshot() EWMASnapshot
|
||||
Tick()
|
||||
Update(int64)
|
||||
}
|
||||
|
||||
// NewEWMA constructs a new EWMA with the given alpha.
|
||||
func NewEWMA(alpha float64) EWMA {
|
||||
return &StandardEWMA{alpha: alpha}
|
||||
}
|
||||
|
||||
// NewEWMA1 constructs a new EWMA for a one-minute moving average.
|
||||
func NewEWMA1() EWMA {
|
||||
return NewEWMA(1 - math.Exp(-5.0/60.0/1))
|
||||
}
|
||||
|
||||
// NewEWMA5 constructs a new EWMA for a five-minute moving average.
|
||||
func NewEWMA5() EWMA {
|
||||
return NewEWMA(1 - math.Exp(-5.0/60.0/5))
|
||||
}
|
||||
|
||||
// NewEWMA15 constructs a new EWMA for a fifteen-minute moving average.
|
||||
func NewEWMA15() EWMA {
|
||||
return NewEWMA(1 - math.Exp(-5.0/60.0/15))
|
||||
}
|
||||
|
||||
// ewmaSnapshot is a read-only copy of another EWMA.
|
||||
type ewmaSnapshot float64
|
||||
|
||||
// Rate returns the rate of events per second at the time the snapshot was
|
||||
// taken.
|
||||
func (a ewmaSnapshot) Rate() float64 { return float64(a) }
|
||||
|
||||
// NilEWMA is a no-op EWMA.
|
||||
type NilEWMA struct{}
|
||||
|
||||
func (NilEWMA) Snapshot() EWMASnapshot { return (*emptySnapshot)(nil) }
|
||||
func (NilEWMA) Tick() {}
|
||||
func (NilEWMA) Update(n int64) {}
|
||||
|
||||
// StandardEWMA is the standard implementation of an EWMA and tracks the number
|
||||
// of uncounted events and processes them on each tick. It uses the
|
||||
// sync/atomic package to manage uncounted events.
|
||||
type StandardEWMA struct {
|
||||
uncounted atomic.Int64
|
||||
alpha float64
|
||||
rate atomic.Uint64
|
||||
init atomic.Bool
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the EWMA.
|
||||
func (a *StandardEWMA) Snapshot() EWMASnapshot {
|
||||
r := math.Float64frombits(a.rate.Load()) * float64(time.Second)
|
||||
return ewmaSnapshot(r)
|
||||
}
|
||||
|
||||
// Tick ticks the clock to update the moving average. It assumes it is called
|
||||
// every five seconds.
|
||||
func (a *StandardEWMA) Tick() {
|
||||
// Optimization to avoid mutex locking in the hot-path.
|
||||
if a.init.Load() {
|
||||
a.updateRate(a.fetchInstantRate())
|
||||
return
|
||||
}
|
||||
// Slow-path: this is only needed on the first Tick() and preserves transactional updating
|
||||
// of init and rate in the else block. The first conditional is needed below because
|
||||
// a different thread could have set a.init = 1 between the time of the first atomic load and when
|
||||
// the lock was acquired.
|
||||
a.mutex.Lock()
|
||||
if a.init.Load() {
|
||||
// The fetchInstantRate() uses atomic loading, which is unnecessary in this critical section
|
||||
// but again, this section is only invoked on the first successful Tick() operation.
|
||||
a.updateRate(a.fetchInstantRate())
|
||||
} else {
|
||||
a.init.Store(true)
|
||||
a.rate.Store(math.Float64bits(a.fetchInstantRate()))
|
||||
}
|
||||
a.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (a *StandardEWMA) fetchInstantRate() float64 {
|
||||
count := a.uncounted.Swap(0)
|
||||
return float64(count) / float64(5*time.Second)
|
||||
}
|
||||
|
||||
func (a *StandardEWMA) updateRate(instantRate float64) {
|
||||
currentRate := math.Float64frombits(a.rate.Load())
|
||||
currentRate += a.alpha * (instantRate - currentRate)
|
||||
a.rate.Store(math.Float64bits(currentRate))
|
||||
}
|
||||
|
||||
// Update adds n uncounted events.
|
||||
func (a *StandardEWMA) Update(n int64) {
|
||||
a.uncounted.Add(n)
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const epsilon = 0.0000000000000001
|
||||
|
||||
func BenchmarkEWMA(b *testing.B) {
|
||||
a := NewEWMA1()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
a.Update(1)
|
||||
a.Tick()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEWMAParallel(b *testing.B) {
|
||||
a := NewEWMA1()
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
a.Update(1)
|
||||
a.Tick()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEWMA1(t *testing.T) {
|
||||
a := NewEWMA1()
|
||||
a.Update(3)
|
||||
a.Tick()
|
||||
for i, want := range []float64{0.6,
|
||||
0.22072766470286553, 0.08120116994196772, 0.029872241020718428,
|
||||
0.01098938333324054, 0.004042768199451294, 0.0014872513059998212,
|
||||
0.0005471291793327122, 0.00020127757674150815, 7.404588245200814e-05,
|
||||
2.7239957857491083e-05, 1.0021020474147462e-05, 3.6865274119969525e-06,
|
||||
1.3561976441886433e-06, 4.989172314621449e-07, 1.8354139230109722e-07,
|
||||
} {
|
||||
if rate := a.Snapshot().Rate(); math.Abs(want-rate) > epsilon {
|
||||
t.Errorf("%d minute a.Snapshot().Rate(): %f != %v\n", i, want, rate)
|
||||
}
|
||||
elapseMinute(a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEWMA5(t *testing.T) {
|
||||
a := NewEWMA5()
|
||||
a.Update(3)
|
||||
a.Tick()
|
||||
for i, want := range []float64{
|
||||
0.6, 0.49123845184678905, 0.4021920276213837, 0.32928698165641596,
|
||||
0.269597378470333, 0.2207276647028654, 0.18071652714732128,
|
||||
0.14795817836496392, 0.12113791079679326, 0.09917933293295193,
|
||||
0.08120116994196763, 0.06648189501740036, 0.05443077197364752,
|
||||
0.04456414692860035, 0.03648603757513079, 0.0298722410207183831020718428,
|
||||
} {
|
||||
if rate := a.Snapshot().Rate(); math.Abs(want-rate) > epsilon {
|
||||
t.Errorf("%d minute a.Snapshot().Rate(): %f != %v\n", i, want, rate)
|
||||
}
|
||||
elapseMinute(a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEWMA15(t *testing.T) {
|
||||
a := NewEWMA15()
|
||||
a.Update(3)
|
||||
a.Tick()
|
||||
for i, want := range []float64{
|
||||
0.6, 0.5613041910189706, 0.5251039914257684, 0.4912384518467888184678905,
|
||||
0.459557003018789, 0.4299187863442732, 0.4021920276213831,
|
||||
0.37625345116383313, 0.3519877317060185, 0.3292869816564153165641596,
|
||||
0.3080502714195546, 0.2881831806538789, 0.26959737847033216,
|
||||
0.2522102307052083, 0.23594443252115815, 0.2207276647028646247028654470286553,
|
||||
} {
|
||||
if rate := a.Snapshot().Rate(); math.Abs(want-rate) > epsilon {
|
||||
t.Errorf("%d minute a.Snapshot().Rate(): %f != %v\n", i, want, rate)
|
||||
}
|
||||
elapseMinute(a)
|
||||
}
|
||||
}
|
||||
|
||||
func elapseMinute(a EWMA) {
|
||||
for i := 0; i < 12; i++ {
|
||||
a.Tick()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
// Hook go-metrics into expvar
|
||||
// on any /debug/metrics request, load all vars from the registry into expvar, and execute regular expvar handler
|
||||
package exp
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/metrics/prometheus"
|
||||
)
|
||||
|
||||
type exp struct {
|
||||
expvarLock sync.Mutex // expvar panics if you try to register the same var twice, so we must probe it safely
|
||||
registry metrics.Registry
|
||||
}
|
||||
|
||||
func (exp *exp) expHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// load our variables into expvar
|
||||
exp.syncToExpvar()
|
||||
|
||||
// now just run the official expvar handler code (which is not publicly callable, so pasted inline)
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
fmt.Fprintf(w, "{\n")
|
||||
first := true
|
||||
expvar.Do(func(kv expvar.KeyValue) {
|
||||
if !first {
|
||||
fmt.Fprintf(w, ",\n")
|
||||
}
|
||||
first = false
|
||||
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
||||
})
|
||||
fmt.Fprintf(w, "\n}\n")
|
||||
}
|
||||
|
||||
// Exp will register an expvar powered metrics handler with http.DefaultServeMux on "/debug/vars"
|
||||
func Exp(r metrics.Registry) {
|
||||
h := ExpHandler(r)
|
||||
// this would cause a panic:
|
||||
// panic: http: multiple registrations for /debug/vars
|
||||
// http.HandleFunc("/debug/vars", e.expHandler)
|
||||
// haven't found an elegant way, so just use a different endpoint
|
||||
http.Handle("/debug/metrics", h)
|
||||
http.Handle("/debug/metrics/prometheus", prometheus.Handler(r))
|
||||
}
|
||||
|
||||
// ExpHandler will return an expvar powered metrics handler.
|
||||
func ExpHandler(r metrics.Registry) http.Handler {
|
||||
e := exp{sync.Mutex{}, r}
|
||||
return http.HandlerFunc(e.expHandler)
|
||||
}
|
||||
|
||||
// Setup starts a dedicated metrics server at the given address.
|
||||
// This function enables metrics reporting separate from pprof.
|
||||
func Setup(address string) {
|
||||
m := http.NewServeMux()
|
||||
m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry))
|
||||
m.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry))
|
||||
log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address))
|
||||
go func() {
|
||||
if err := http.ListenAndServe(address, m); err != nil {
|
||||
log.Error("Failure in running metrics server", "err", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (exp *exp) getInt(name string) *expvar.Int {
|
||||
var v *expvar.Int
|
||||
exp.expvarLock.Lock()
|
||||
p := expvar.Get(name)
|
||||
if p != nil {
|
||||
v = p.(*expvar.Int)
|
||||
} else {
|
||||
v = new(expvar.Int)
|
||||
expvar.Publish(name, v)
|
||||
}
|
||||
exp.expvarLock.Unlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func (exp *exp) getFloat(name string) *expvar.Float {
|
||||
var v *expvar.Float
|
||||
exp.expvarLock.Lock()
|
||||
p := expvar.Get(name)
|
||||
if p != nil {
|
||||
v = p.(*expvar.Float)
|
||||
} else {
|
||||
v = new(expvar.Float)
|
||||
expvar.Publish(name, v)
|
||||
}
|
||||
exp.expvarLock.Unlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func (exp *exp) getInfo(name string) *expvar.String {
|
||||
var v *expvar.String
|
||||
exp.expvarLock.Lock()
|
||||
p := expvar.Get(name)
|
||||
if p != nil {
|
||||
v = p.(*expvar.String)
|
||||
} else {
|
||||
v = new(expvar.String)
|
||||
expvar.Publish(name, v)
|
||||
}
|
||||
exp.expvarLock.Unlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func (exp *exp) publishCounter(name string, metric metrics.CounterSnapshot) {
|
||||
v := exp.getInt(name)
|
||||
v.Set(metric.Count())
|
||||
}
|
||||
|
||||
func (exp *exp) publishCounterFloat64(name string, metric metrics.CounterFloat64Snapshot) {
|
||||
v := exp.getFloat(name)
|
||||
v.Set(metric.Count())
|
||||
}
|
||||
|
||||
func (exp *exp) publishGauge(name string, metric metrics.GaugeSnapshot) {
|
||||
v := exp.getInt(name)
|
||||
v.Set(metric.Value())
|
||||
}
|
||||
func (exp *exp) publishGaugeFloat64(name string, metric metrics.GaugeFloat64Snapshot) {
|
||||
exp.getFloat(name).Set(metric.Value())
|
||||
}
|
||||
|
||||
func (exp *exp) publishGaugeInfo(name string, metric metrics.GaugeInfoSnapshot) {
|
||||
exp.getInfo(name).Set(metric.Value().String())
|
||||
}
|
||||
|
||||
func (exp *exp) publishHistogram(name string, metric metrics.Histogram) {
|
||||
h := metric.Snapshot()
|
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
exp.getInt(name + ".count").Set(h.Count())
|
||||
exp.getFloat(name + ".min").Set(float64(h.Min()))
|
||||
exp.getFloat(name + ".max").Set(float64(h.Max()))
|
||||
exp.getFloat(name + ".mean").Set(h.Mean())
|
||||
exp.getFloat(name + ".std-dev").Set(h.StdDev())
|
||||
exp.getFloat(name + ".50-percentile").Set(ps[0])
|
||||
exp.getFloat(name + ".75-percentile").Set(ps[1])
|
||||
exp.getFloat(name + ".95-percentile").Set(ps[2])
|
||||
exp.getFloat(name + ".99-percentile").Set(ps[3])
|
||||
exp.getFloat(name + ".999-percentile").Set(ps[4])
|
||||
}
|
||||
|
||||
func (exp *exp) publishMeter(name string, metric metrics.Meter) {
|
||||
m := metric.Snapshot()
|
||||
exp.getInt(name + ".count").Set(m.Count())
|
||||
exp.getFloat(name + ".one-minute").Set(m.Rate1())
|
||||
exp.getFloat(name + ".five-minute").Set(m.Rate5())
|
||||
exp.getFloat(name + ".fifteen-minute").Set(m.Rate15())
|
||||
exp.getFloat(name + ".mean").Set(m.RateMean())
|
||||
}
|
||||
|
||||
func (exp *exp) publishTimer(name string, metric metrics.Timer) {
|
||||
t := metric.Snapshot()
|
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
exp.getInt(name + ".count").Set(t.Count())
|
||||
exp.getFloat(name + ".min").Set(float64(t.Min()))
|
||||
exp.getFloat(name + ".max").Set(float64(t.Max()))
|
||||
exp.getFloat(name + ".mean").Set(t.Mean())
|
||||
exp.getFloat(name + ".std-dev").Set(t.StdDev())
|
||||
exp.getFloat(name + ".50-percentile").Set(ps[0])
|
||||
exp.getFloat(name + ".75-percentile").Set(ps[1])
|
||||
exp.getFloat(name + ".95-percentile").Set(ps[2])
|
||||
exp.getFloat(name + ".99-percentile").Set(ps[3])
|
||||
exp.getFloat(name + ".999-percentile").Set(ps[4])
|
||||
exp.getFloat(name + ".one-minute").Set(t.Rate1())
|
||||
exp.getFloat(name + ".five-minute").Set(t.Rate5())
|
||||
exp.getFloat(name + ".fifteen-minute").Set(t.Rate15())
|
||||
exp.getFloat(name + ".mean-rate").Set(t.RateMean())
|
||||
}
|
||||
|
||||
func (exp *exp) publishResettingTimer(name string, metric metrics.ResettingTimer) {
|
||||
t := metric.Snapshot()
|
||||
ps := t.Percentiles([]float64{0.50, 0.75, 0.95, 0.99})
|
||||
exp.getInt(name + ".count").Set(int64(t.Count()))
|
||||
exp.getFloat(name + ".mean").Set(t.Mean())
|
||||
exp.getFloat(name + ".50-percentile").Set(ps[0])
|
||||
exp.getFloat(name + ".75-percentile").Set(ps[1])
|
||||
exp.getFloat(name + ".95-percentile").Set(ps[2])
|
||||
exp.getFloat(name + ".99-percentile").Set(ps[3])
|
||||
}
|
||||
|
||||
func (exp *exp) syncToExpvar() {
|
||||
exp.registry.Each(func(name string, i interface{}) {
|
||||
switch i := i.(type) {
|
||||
case metrics.Counter:
|
||||
exp.publishCounter(name, i.Snapshot())
|
||||
case metrics.CounterFloat64:
|
||||
exp.publishCounterFloat64(name, i.Snapshot())
|
||||
case metrics.Gauge:
|
||||
exp.publishGauge(name, i.Snapshot())
|
||||
case metrics.GaugeFloat64:
|
||||
exp.publishGaugeFloat64(name, i.Snapshot())
|
||||
case metrics.GaugeInfo:
|
||||
exp.publishGaugeInfo(name, i.Snapshot())
|
||||
case metrics.Histogram:
|
||||
exp.publishHistogram(name, i)
|
||||
case metrics.Meter:
|
||||
exp.publishMeter(name, i)
|
||||
case metrics.Timer:
|
||||
exp.publishTimer(name, i)
|
||||
case metrics.ResettingTimer:
|
||||
exp.publishResettingTimer(name, i)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// gaugeSnapshot contains a readonly int64.
|
||||
type GaugeSnapshot interface {
|
||||
Value() int64
|
||||
}
|
||||
|
||||
// Gauges hold an int64 value that can be set arbitrarily.
|
||||
type Gauge interface {
|
||||
Snapshot() GaugeSnapshot
|
||||
Update(int64)
|
||||
UpdateIfGt(int64)
|
||||
Dec(int64)
|
||||
Inc(int64)
|
||||
}
|
||||
|
||||
// GetOrRegisterGauge returns an existing Gauge or constructs and registers a
|
||||
// new StandardGauge.
|
||||
func GetOrRegisterGauge(name string, r Registry) Gauge {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewGauge).(Gauge)
|
||||
}
|
||||
|
||||
// NewGauge constructs a new StandardGauge.
|
||||
func NewGauge() Gauge {
|
||||
if !Enabled {
|
||||
return NilGauge{}
|
||||
}
|
||||
return &StandardGauge{}
|
||||
}
|
||||
|
||||
// NewRegisteredGauge constructs and registers a new StandardGauge.
|
||||
func NewRegisteredGauge(name string, r Registry) Gauge {
|
||||
c := NewGauge()
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// gaugeSnapshot is a read-only copy of another Gauge.
|
||||
type gaugeSnapshot int64
|
||||
|
||||
// Value returns the value at the time the snapshot was taken.
|
||||
func (g gaugeSnapshot) Value() int64 { return int64(g) }
|
||||
|
||||
// NilGauge is a no-op Gauge.
|
||||
type NilGauge struct{}
|
||||
|
||||
func (NilGauge) Snapshot() GaugeSnapshot { return (*emptySnapshot)(nil) }
|
||||
func (NilGauge) Update(v int64) {}
|
||||
func (NilGauge) UpdateIfGt(v int64) {}
|
||||
func (NilGauge) Dec(i int64) {}
|
||||
func (NilGauge) Inc(i int64) {}
|
||||
|
||||
// StandardGauge is the standard implementation of a Gauge and uses the
|
||||
// sync/atomic package to manage a single int64 value.
|
||||
type StandardGauge struct {
|
||||
value atomic.Int64
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the gauge.
|
||||
func (g *StandardGauge) Snapshot() GaugeSnapshot {
|
||||
return gaugeSnapshot(g.value.Load())
|
||||
}
|
||||
|
||||
// Update updates the gauge's value.
|
||||
func (g *StandardGauge) Update(v int64) {
|
||||
g.value.Store(v)
|
||||
}
|
||||
|
||||
// Update updates the gauge's value if v is larger then the current valie.
|
||||
func (g *StandardGauge) UpdateIfGt(v int64) {
|
||||
for {
|
||||
exist := g.value.Load()
|
||||
if exist >= v {
|
||||
break
|
||||
}
|
||||
if g.value.CompareAndSwap(exist, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dec decrements the gauge's current value by the given amount.
|
||||
func (g *StandardGauge) Dec(i int64) {
|
||||
g.value.Add(-i)
|
||||
}
|
||||
|
||||
// Inc increments the gauge's current value by the given amount.
|
||||
func (g *StandardGauge) Inc(i int64) {
|
||||
g.value.Add(i)
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type GaugeFloat64Snapshot interface {
|
||||
Value() float64
|
||||
}
|
||||
|
||||
// GaugeFloat64 hold a float64 value that can be set arbitrarily.
|
||||
type GaugeFloat64 interface {
|
||||
Snapshot() GaugeFloat64Snapshot
|
||||
Update(float64)
|
||||
}
|
||||
|
||||
// GetOrRegisterGaugeFloat64 returns an existing GaugeFloat64 or constructs and registers a
|
||||
// new StandardGaugeFloat64.
|
||||
func GetOrRegisterGaugeFloat64(name string, r Registry) GaugeFloat64 {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewGaugeFloat64()).(GaugeFloat64)
|
||||
}
|
||||
|
||||
// NewGaugeFloat64 constructs a new StandardGaugeFloat64.
|
||||
func NewGaugeFloat64() GaugeFloat64 {
|
||||
if !Enabled {
|
||||
return NilGaugeFloat64{}
|
||||
}
|
||||
return &StandardGaugeFloat64{}
|
||||
}
|
||||
|
||||
// NewRegisteredGaugeFloat64 constructs and registers a new StandardGaugeFloat64.
|
||||
func NewRegisteredGaugeFloat64(name string, r Registry) GaugeFloat64 {
|
||||
c := NewGaugeFloat64()
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// gaugeFloat64Snapshot is a read-only copy of another GaugeFloat64.
|
||||
type gaugeFloat64Snapshot float64
|
||||
|
||||
// Value returns the value at the time the snapshot was taken.
|
||||
func (g gaugeFloat64Snapshot) Value() float64 { return float64(g) }
|
||||
|
||||
// NilGauge is a no-op Gauge.
|
||||
type NilGaugeFloat64 struct{}
|
||||
|
||||
func (NilGaugeFloat64) Snapshot() GaugeFloat64Snapshot { return NilGaugeFloat64{} }
|
||||
func (NilGaugeFloat64) Update(v float64) {}
|
||||
func (NilGaugeFloat64) Value() float64 { return 0.0 }
|
||||
|
||||
// StandardGaugeFloat64 is the standard implementation of a GaugeFloat64 and uses
|
||||
// atomic to manage a single float64 value.
|
||||
type StandardGaugeFloat64 struct {
|
||||
floatBits atomic.Uint64
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the gauge.
|
||||
func (g *StandardGaugeFloat64) Snapshot() GaugeFloat64Snapshot {
|
||||
v := math.Float64frombits(g.floatBits.Load())
|
||||
return gaugeFloat64Snapshot(v)
|
||||
}
|
||||
|
||||
// Update updates the gauge's value.
|
||||
func (g *StandardGaugeFloat64) Update(v float64) {
|
||||
g.floatBits.Store(math.Float64bits(v))
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkGaugeFloat64(b *testing.B) {
|
||||
g := NewGaugeFloat64()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
g.Update(float64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGaugeFloat64Parallel(b *testing.B) {
|
||||
c := NewGaugeFloat64()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.Update(float64(i))
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if have, want := c.Snapshot().Value(), float64(b.N-1); have != want {
|
||||
b.Fatalf("have %f want %f", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGaugeFloat64Snapshot(t *testing.T) {
|
||||
g := NewGaugeFloat64()
|
||||
g.Update(47.0)
|
||||
snapshot := g.Snapshot()
|
||||
g.Update(float64(0))
|
||||
if v := snapshot.Value(); v != 47.0 {
|
||||
t.Errorf("g.Value(): 47.0 != %v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrRegisterGaugeFloat64(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
NewRegisteredGaugeFloat64("foo", r).Update(47.0)
|
||||
t.Logf("registry: %v", r)
|
||||
if g := GetOrRegisterGaugeFloat64("foo", r).Snapshot(); g.Value() != 47.0 {
|
||||
t.Fatal(g)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type GaugeInfoSnapshot interface {
|
||||
Value() GaugeInfoValue
|
||||
}
|
||||
|
||||
// GaugeInfos hold a GaugeInfoValue value that can be set arbitrarily.
|
||||
type GaugeInfo interface {
|
||||
Update(GaugeInfoValue)
|
||||
Snapshot() GaugeInfoSnapshot
|
||||
}
|
||||
|
||||
// GaugeInfoValue is a mapping of keys to values
|
||||
type GaugeInfoValue map[string]string
|
||||
|
||||
func (val GaugeInfoValue) String() string {
|
||||
data, _ := json.Marshal(val)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// GetOrRegisterGaugeInfo returns an existing GaugeInfo or constructs and registers a
|
||||
// new StandardGaugeInfo.
|
||||
func GetOrRegisterGaugeInfo(name string, r Registry) GaugeInfo {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewGaugeInfo()).(GaugeInfo)
|
||||
}
|
||||
|
||||
// NewGaugeInfo constructs a new StandardGaugeInfo.
|
||||
func NewGaugeInfo() GaugeInfo {
|
||||
if !Enabled {
|
||||
return NilGaugeInfo{}
|
||||
}
|
||||
return &StandardGaugeInfo{
|
||||
value: GaugeInfoValue{},
|
||||
}
|
||||
}
|
||||
|
||||
// NewRegisteredGaugeInfo constructs and registers a new StandardGaugeInfo.
|
||||
func NewRegisteredGaugeInfo(name string, r Registry) GaugeInfo {
|
||||
c := NewGaugeInfo()
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// gaugeInfoSnapshot is a read-only copy of another GaugeInfo.
|
||||
type gaugeInfoSnapshot GaugeInfoValue
|
||||
|
||||
// Value returns the value at the time the snapshot was taken.
|
||||
func (g gaugeInfoSnapshot) Value() GaugeInfoValue { return GaugeInfoValue(g) }
|
||||
|
||||
type NilGaugeInfo struct{}
|
||||
|
||||
func (NilGaugeInfo) Snapshot() GaugeInfoSnapshot { return NilGaugeInfo{} }
|
||||
func (NilGaugeInfo) Update(v GaugeInfoValue) {}
|
||||
func (NilGaugeInfo) Value() GaugeInfoValue { return GaugeInfoValue{} }
|
||||
|
||||
// StandardGaugeInfo is the standard implementation of a GaugeInfo and uses
|
||||
// sync.Mutex to manage a single string value.
|
||||
type StandardGaugeInfo struct {
|
||||
mutex sync.Mutex
|
||||
value GaugeInfoValue
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the gauge.
|
||||
func (g *StandardGaugeInfo) Snapshot() GaugeInfoSnapshot {
|
||||
return gaugeInfoSnapshot(g.value)
|
||||
}
|
||||
|
||||
// Update updates the gauge's value.
|
||||
func (g *StandardGaugeInfo) Update(v GaugeInfoValue) {
|
||||
g.mutex.Lock()
|
||||
defer g.mutex.Unlock()
|
||||
g.value = v
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGaugeInfoJsonString(t *testing.T) {
|
||||
g := NewGaugeInfo()
|
||||
g.Update(GaugeInfoValue{
|
||||
"chain_id": "5",
|
||||
"anotherKey": "any_string_value",
|
||||
"third_key": "anything",
|
||||
},
|
||||
)
|
||||
want := `{"anotherKey":"any_string_value","chain_id":"5","third_key":"anything"}`
|
||||
|
||||
original := g.Snapshot()
|
||||
g.Update(GaugeInfoValue{"value": "updated"})
|
||||
|
||||
if have := original.Value().String(); have != want {
|
||||
t.Errorf("\nhave: %v\nwant: %v\n", have, want)
|
||||
}
|
||||
if have, want := g.Snapshot().Value().String(), `{"value":"updated"}`; have != want {
|
||||
t.Errorf("\nhave: %v\nwant: %v\n", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrRegisterGaugeInfo(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
NewRegisteredGaugeInfo("foo", r).Update(
|
||||
GaugeInfoValue{"chain_id": "5"})
|
||||
g := GetOrRegisterGaugeInfo("foo", r).Snapshot()
|
||||
if have, want := g.Value().String(), `{"chain_id":"5"}`; have != want {
|
||||
t.Errorf("have\n%v\nwant\n%v\n", have, want)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkGauge(b *testing.B) {
|
||||
g := NewGauge()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
g.Update(int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGaugeSnapshot(t *testing.T) {
|
||||
g := NewGauge()
|
||||
g.Update(int64(47))
|
||||
snapshot := g.Snapshot()
|
||||
g.Update(int64(0))
|
||||
if v := snapshot.Value(); v != 47 {
|
||||
t.Errorf("g.Value(): 47 != %v\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrRegisterGauge(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
NewRegisteredGauge("foo", r).Update(47)
|
||||
if g := GetOrRegisterGauge("foo", r); g.Snapshot().Value() != 47 {
|
||||
t.Fatal(g)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GraphiteConfig provides a container with configuration parameters for
|
||||
// the Graphite exporter
|
||||
type GraphiteConfig struct {
|
||||
Addr *net.TCPAddr // Network address to connect to
|
||||
Registry Registry // Registry to be exported
|
||||
FlushInterval time.Duration // Flush interval
|
||||
DurationUnit time.Duration // Time conversion unit for durations
|
||||
Prefix string // Prefix to be prepended to metric names
|
||||
Percentiles []float64 // Percentiles to export from timers and histograms
|
||||
}
|
||||
|
||||
// Graphite is a blocking exporter function which reports metrics in r
|
||||
// to a graphite server located at addr, flushing them every d duration
|
||||
// and prepending metric names with prefix.
|
||||
func Graphite(r Registry, d time.Duration, prefix string, addr *net.TCPAddr) {
|
||||
GraphiteWithConfig(GraphiteConfig{
|
||||
Addr: addr,
|
||||
Registry: r,
|
||||
FlushInterval: d,
|
||||
DurationUnit: time.Nanosecond,
|
||||
Prefix: prefix,
|
||||
Percentiles: []float64{0.5, 0.75, 0.95, 0.99, 0.999},
|
||||
})
|
||||
}
|
||||
|
||||
// GraphiteWithConfig is a blocking exporter function just like Graphite,
|
||||
// but it takes a GraphiteConfig instead.
|
||||
func GraphiteWithConfig(c GraphiteConfig) {
|
||||
log.Printf("WARNING: This go-metrics client has been DEPRECATED! It has been moved to https://github.com/cyberdelia/go-metrics-graphite and will be removed from rcrowley/go-metrics on August 12th 2015")
|
||||
for range time.Tick(c.FlushInterval) {
|
||||
if err := graphite(&c); nil != err {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GraphiteOnce performs a single submission to Graphite, returning a
|
||||
// non-nil error on failed connections. This can be used in a loop
|
||||
// similar to GraphiteWithConfig for custom error handling.
|
||||
func GraphiteOnce(c GraphiteConfig) error {
|
||||
log.Printf("WARNING: This go-metrics client has been DEPRECATED! It has been moved to https://github.com/cyberdelia/go-metrics-graphite and will be removed from rcrowley/go-metrics on August 12th 2015")
|
||||
return graphite(&c)
|
||||
}
|
||||
|
||||
func graphite(c *GraphiteConfig) error {
|
||||
now := time.Now().Unix()
|
||||
du := float64(c.DurationUnit)
|
||||
conn, err := net.DialTCP("tcp", nil, c.Addr)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
w := bufio.NewWriter(conn)
|
||||
c.Registry.Each(func(name string, i interface{}) {
|
||||
switch metric := i.(type) {
|
||||
case Counter:
|
||||
fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, metric.Snapshot().Count(), now)
|
||||
case CounterFloat64:
|
||||
fmt.Fprintf(w, "%s.%s.count %f %d\n", c.Prefix, name, metric.Snapshot().Count(), now)
|
||||
case Gauge:
|
||||
fmt.Fprintf(w, "%s.%s.value %d %d\n", c.Prefix, name, metric.Snapshot().Value(), now)
|
||||
case GaugeFloat64:
|
||||
fmt.Fprintf(w, "%s.%s.value %f %d\n", c.Prefix, name, metric.Snapshot().Value(), now)
|
||||
case GaugeInfo:
|
||||
fmt.Fprintf(w, "%s.%s.value %s %d\n", c.Prefix, name, metric.Snapshot().Value().String(), now)
|
||||
case Histogram:
|
||||
h := metric.Snapshot()
|
||||
ps := h.Percentiles(c.Percentiles)
|
||||
fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, h.Count(), now)
|
||||
fmt.Fprintf(w, "%s.%s.min %d %d\n", c.Prefix, name, h.Min(), now)
|
||||
fmt.Fprintf(w, "%s.%s.max %d %d\n", c.Prefix, name, h.Max(), now)
|
||||
fmt.Fprintf(w, "%s.%s.mean %.2f %d\n", c.Prefix, name, h.Mean(), now)
|
||||
fmt.Fprintf(w, "%s.%s.std-dev %.2f %d\n", c.Prefix, name, h.StdDev(), now)
|
||||
for psIdx, psKey := range c.Percentiles {
|
||||
key := strings.Replace(strconv.FormatFloat(psKey*100.0, 'f', -1, 64), ".", "", 1)
|
||||
fmt.Fprintf(w, "%s.%s.%s-percentile %.2f %d\n", c.Prefix, name, key, ps[psIdx], now)
|
||||
}
|
||||
case Meter:
|
||||
m := metric.Snapshot()
|
||||
fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, m.Count(), now)
|
||||
fmt.Fprintf(w, "%s.%s.one-minute %.2f %d\n", c.Prefix, name, m.Rate1(), now)
|
||||
fmt.Fprintf(w, "%s.%s.five-minute %.2f %d\n", c.Prefix, name, m.Rate5(), now)
|
||||
fmt.Fprintf(w, "%s.%s.fifteen-minute %.2f %d\n", c.Prefix, name, m.Rate15(), now)
|
||||
fmt.Fprintf(w, "%s.%s.mean %.2f %d\n", c.Prefix, name, m.RateMean(), now)
|
||||
case Timer:
|
||||
t := metric.Snapshot()
|
||||
ps := t.Percentiles(c.Percentiles)
|
||||
fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, t.Count(), now)
|
||||
fmt.Fprintf(w, "%s.%s.min %d %d\n", c.Prefix, name, t.Min()/int64(du), now)
|
||||
fmt.Fprintf(w, "%s.%s.max %d %d\n", c.Prefix, name, t.Max()/int64(du), now)
|
||||
fmt.Fprintf(w, "%s.%s.mean %.2f %d\n", c.Prefix, name, t.Mean()/du, now)
|
||||
fmt.Fprintf(w, "%s.%s.std-dev %.2f %d\n", c.Prefix, name, t.StdDev()/du, now)
|
||||
for psIdx, psKey := range c.Percentiles {
|
||||
key := strings.Replace(strconv.FormatFloat(psKey*100.0, 'f', -1, 64), ".", "", 1)
|
||||
fmt.Fprintf(w, "%s.%s.%s-percentile %.2f %d\n", c.Prefix, name, key, ps[psIdx], now)
|
||||
}
|
||||
fmt.Fprintf(w, "%s.%s.one-minute %.2f %d\n", c.Prefix, name, t.Rate1(), now)
|
||||
fmt.Fprintf(w, "%s.%s.five-minute %.2f %d\n", c.Prefix, name, t.Rate5(), now)
|
||||
fmt.Fprintf(w, "%s.%s.fifteen-minute %.2f %d\n", c.Prefix, name, t.Rate15(), now)
|
||||
fmt.Fprintf(w, "%s.%s.mean-rate %.2f %d\n", c.Prefix, name, t.RateMean(), now)
|
||||
}
|
||||
w.Flush()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleGraphite() {
|
||||
addr, _ := net.ResolveTCPAddr("net", ":2003")
|
||||
go Graphite(DefaultRegistry, 1*time.Second, "some.prefix", addr)
|
||||
}
|
||||
|
||||
func ExampleGraphiteWithConfig() {
|
||||
addr, _ := net.ResolveTCPAddr("net", ":2003")
|
||||
go GraphiteWithConfig(GraphiteConfig{
|
||||
Addr: addr,
|
||||
Registry: DefaultRegistry,
|
||||
FlushInterval: 1 * time.Second,
|
||||
DurationUnit: time.Millisecond,
|
||||
Percentiles: []float64{0.5, 0.75, 0.99, 0.999},
|
||||
})
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
package metrics
|
||||
|
||||
// Healthchecks hold an error value describing an arbitrary up/down status.
|
||||
type Healthcheck interface {
|
||||
Check()
|
||||
Error() error
|
||||
Healthy()
|
||||
Unhealthy(error)
|
||||
}
|
||||
|
||||
// NewHealthcheck constructs a new Healthcheck which will use the given
|
||||
// function to update its status.
|
||||
func NewHealthcheck(f func(Healthcheck)) Healthcheck {
|
||||
if !Enabled {
|
||||
return NilHealthcheck{}
|
||||
}
|
||||
return &StandardHealthcheck{nil, f}
|
||||
}
|
||||
|
||||
// NilHealthcheck is a no-op.
|
||||
type NilHealthcheck struct{}
|
||||
|
||||
// Check is a no-op.
|
||||
func (NilHealthcheck) Check() {}
|
||||
|
||||
// Error is a no-op.
|
||||
func (NilHealthcheck) Error() error { return nil }
|
||||
|
||||
// Healthy is a no-op.
|
||||
func (NilHealthcheck) Healthy() {}
|
||||
|
||||
// Unhealthy is a no-op.
|
||||
func (NilHealthcheck) Unhealthy(error) {}
|
||||
|
||||
// StandardHealthcheck is the standard implementation of a Healthcheck and
|
||||
// stores the status and a function to call to update the status.
|
||||
type StandardHealthcheck struct {
|
||||
err error
|
||||
f func(Healthcheck)
|
||||
}
|
||||
|
||||
// Check runs the healthcheck function to update the healthcheck's status.
|
||||
func (h *StandardHealthcheck) Check() {
|
||||
h.f(h)
|
||||
}
|
||||
|
||||
// Error returns the healthcheck's status, which will be nil if it is healthy.
|
||||
func (h *StandardHealthcheck) Error() error {
|
||||
return h.err
|
||||
}
|
||||
|
||||
// Healthy marks the healthcheck as healthy.
|
||||
func (h *StandardHealthcheck) Healthy() {
|
||||
h.err = nil
|
||||
}
|
||||
|
||||
// Unhealthy marks the healthcheck as unhealthy. The error is stored and
|
||||
// may be retrieved by the Error method.
|
||||
func (h *StandardHealthcheck) Unhealthy(err error) {
|
||||
h.err = err
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package metrics
|
||||
|
||||
type HistogramSnapshot interface {
|
||||
SampleSnapshot
|
||||
}
|
||||
|
||||
// Histograms calculate distribution statistics from a series of int64 values.
|
||||
type Histogram interface {
|
||||
Clear()
|
||||
Update(int64)
|
||||
Snapshot() HistogramSnapshot
|
||||
}
|
||||
|
||||
// GetOrRegisterHistogram returns an existing Histogram or constructs and
|
||||
// registers a new StandardHistogram.
|
||||
func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, func() Histogram { return NewHistogram(s) }).(Histogram)
|
||||
}
|
||||
|
||||
// GetOrRegisterHistogramLazy returns an existing Histogram or constructs and
|
||||
// registers a new StandardHistogram.
|
||||
func GetOrRegisterHistogramLazy(name string, r Registry, s func() Sample) Histogram {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, func() Histogram { return NewHistogram(s()) }).(Histogram)
|
||||
}
|
||||
|
||||
// NewHistogram constructs a new StandardHistogram from a Sample.
|
||||
func NewHistogram(s Sample) Histogram {
|
||||
if !Enabled {
|
||||
return NilHistogram{}
|
||||
}
|
||||
return &StandardHistogram{sample: s}
|
||||
}
|
||||
|
||||
// NewRegisteredHistogram constructs and registers a new StandardHistogram from
|
||||
// a Sample.
|
||||
func NewRegisteredHistogram(name string, r Registry, s Sample) Histogram {
|
||||
c := NewHistogram(s)
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// NilHistogram is a no-op Histogram.
|
||||
type NilHistogram struct{}
|
||||
|
||||
func (NilHistogram) Clear() {}
|
||||
func (NilHistogram) Snapshot() HistogramSnapshot { return (*emptySnapshot)(nil) }
|
||||
func (NilHistogram) Update(v int64) {}
|
||||
|
||||
// StandardHistogram is the standard implementation of a Histogram and uses a
|
||||
// Sample to bound its memory use.
|
||||
type StandardHistogram struct {
|
||||
sample Sample
|
||||
}
|
||||
|
||||
// Clear clears the histogram and its sample.
|
||||
func (h *StandardHistogram) Clear() { h.sample.Clear() }
|
||||
|
||||
// Snapshot returns a read-only copy of the histogram.
|
||||
func (h *StandardHistogram) Snapshot() HistogramSnapshot {
|
||||
return h.sample.Snapshot()
|
||||
}
|
||||
|
||||
// Update samples a new value.
|
||||
func (h *StandardHistogram) Update(v int64) { h.sample.Update(v) }
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkHistogram(b *testing.B) {
|
||||
h := NewHistogram(NewUniformSample(100))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
h.Update(int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrRegisterHistogram(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
s := NewUniformSample(100)
|
||||
NewRegisteredHistogram("foo", r, s).Update(47)
|
||||
if h := GetOrRegisterHistogram("foo", r, s).Snapshot(); h.Count() != 1 {
|
||||
t.Fatal(h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogram10000(t *testing.T) {
|
||||
h := NewHistogram(NewUniformSample(100000))
|
||||
for i := 1; i <= 10000; i++ {
|
||||
h.Update(int64(i))
|
||||
}
|
||||
testHistogram10000(t, h.Snapshot())
|
||||
}
|
||||
|
||||
func TestHistogramEmpty(t *testing.T) {
|
||||
h := NewHistogram(NewUniformSample(100)).Snapshot()
|
||||
if count := h.Count(); count != 0 {
|
||||
t.Errorf("h.Count(): 0 != %v\n", count)
|
||||
}
|
||||
if min := h.Min(); min != 0 {
|
||||
t.Errorf("h.Min(): 0 != %v\n", min)
|
||||
}
|
||||
if max := h.Max(); max != 0 {
|
||||
t.Errorf("h.Max(): 0 != %v\n", max)
|
||||
}
|
||||
if mean := h.Mean(); mean != 0.0 {
|
||||
t.Errorf("h.Mean(): 0.0 != %v\n", mean)
|
||||
}
|
||||
if stdDev := h.StdDev(); stdDev != 0.0 {
|
||||
t.Errorf("h.StdDev(): 0.0 != %v\n", stdDev)
|
||||
}
|
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.99})
|
||||
if ps[0] != 0.0 {
|
||||
t.Errorf("median: 0.0 != %v\n", ps[0])
|
||||
}
|
||||
if ps[1] != 0.0 {
|
||||
t.Errorf("75th percentile: 0.0 != %v\n", ps[1])
|
||||
}
|
||||
if ps[2] != 0.0 {
|
||||
t.Errorf("99th percentile: 0.0 != %v\n", ps[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogramSnapshot(t *testing.T) {
|
||||
h := NewHistogram(NewUniformSample(100000))
|
||||
for i := 1; i <= 10000; i++ {
|
||||
h.Update(int64(i))
|
||||
}
|
||||
snapshot := h.Snapshot()
|
||||
h.Update(0)
|
||||
testHistogram10000(t, snapshot)
|
||||
}
|
||||
|
||||
func testHistogram10000(t *testing.T, h HistogramSnapshot) {
|
||||
if count := h.Count(); count != 10000 {
|
||||
t.Errorf("h.Count(): 10000 != %v\n", count)
|
||||
}
|
||||
if min := h.Min(); min != 1 {
|
||||
t.Errorf("h.Min(): 1 != %v\n", min)
|
||||
}
|
||||
if max := h.Max(); max != 10000 {
|
||||
t.Errorf("h.Max(): 10000 != %v\n", max)
|
||||
}
|
||||
if mean := h.Mean(); mean != 5000.5 {
|
||||
t.Errorf("h.Mean(): 5000.5 != %v\n", mean)
|
||||
}
|
||||
if stdDev := h.StdDev(); stdDev != 2886.751331514372 {
|
||||
t.Errorf("h.StdDev(): 2886.751331514372 != %v\n", stdDev)
|
||||
}
|
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.99})
|
||||
if ps[0] != 5000.5 {
|
||||
t.Errorf("median: 5000.5 != %v\n", ps[0])
|
||||
}
|
||||
if ps[1] != 7500.75 {
|
||||
t.Errorf("75th percentile: 7500.75 != %v\n", ps[1])
|
||||
}
|
||||
if ps[2] != 9900.99 {
|
||||
t.Errorf("99th percentile: 9900.99 != %v\n", ps[2])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package metrics
|
||||
|
||||
// compile-time checks that interfaces are implemented.
|
||||
var (
|
||||
_ SampleSnapshot = (*emptySnapshot)(nil)
|
||||
_ HistogramSnapshot = (*emptySnapshot)(nil)
|
||||
_ CounterSnapshot = (*emptySnapshot)(nil)
|
||||
_ GaugeSnapshot = (*emptySnapshot)(nil)
|
||||
_ MeterSnapshot = (*emptySnapshot)(nil)
|
||||
_ EWMASnapshot = (*emptySnapshot)(nil)
|
||||
_ TimerSnapshot = (*emptySnapshot)(nil)
|
||||
)
|
||||
|
||||
type emptySnapshot struct{}
|
||||
|
||||
func (*emptySnapshot) Count() int64 { return 0 }
|
||||
func (*emptySnapshot) Max() int64 { return 0 }
|
||||
func (*emptySnapshot) Mean() float64 { return 0.0 }
|
||||
func (*emptySnapshot) Min() int64 { return 0 }
|
||||
func (*emptySnapshot) Percentile(p float64) float64 { return 0.0 }
|
||||
func (*emptySnapshot) Percentiles(ps []float64) []float64 { return make([]float64, len(ps)) }
|
||||
func (*emptySnapshot) Size() int { return 0 }
|
||||
func (*emptySnapshot) StdDev() float64 { return 0.0 }
|
||||
func (*emptySnapshot) Sum() int64 { return 0 }
|
||||
func (*emptySnapshot) Values() []int64 { return []int64{} }
|
||||
func (*emptySnapshot) Variance() float64 { return 0.0 }
|
||||
func (*emptySnapshot) Value() int64 { return 0 }
|
||||
func (*emptySnapshot) Rate() float64 { return 0.0 }
|
||||
func (*emptySnapshot) Rate1() float64 { return 0.0 }
|
||||
func (*emptySnapshot) Rate5() float64 { return 0.0 }
|
||||
func (*emptySnapshot) Rate15() float64 { return 0.0 }
|
||||
func (*emptySnapshot) RateMean() float64 { return 0.0 }
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2015 Vincent Rischmann
|
||||
|
||||
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.
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
go-metrics-influxdb
|
||||
===================
|
||||
|
||||
This is a reporter for the [go-metrics](https://github.com/rcrowley/go-metrics) library which will post the metrics to [InfluxDB](https://influxdb.com/).
|
||||
|
||||
Note
|
||||
----
|
||||
|
||||
This is only compatible with InfluxDB 0.9+.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```go
|
||||
import "github.com/vrischmann/go-metrics-influxdb"
|
||||
|
||||
go influxdb.InfluxDB(
|
||||
metrics.DefaultRegistry, // metrics registry
|
||||
time.Second * 10, // interval
|
||||
"http://localhost:8086", // the InfluxDB url
|
||||
"mydb", // your InfluxDB database
|
||||
"myuser", // your InfluxDB user
|
||||
"mypassword", // your InfluxDB password
|
||||
)
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
go-metrics-influxdb is licensed under the MIT license. See the LICENSE file for details.
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
package influxdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
func readMeter(namespace, name string, i interface{}) (string, map[string]interface{}) {
|
||||
switch metric := i.(type) {
|
||||
case metrics.Counter:
|
||||
measurement := fmt.Sprintf("%s%s.count", namespace, name)
|
||||
fields := map[string]interface{}{
|
||||
"value": metric.Snapshot().Count(),
|
||||
}
|
||||
return measurement, fields
|
||||
case metrics.CounterFloat64:
|
||||
measurement := fmt.Sprintf("%s%s.count", namespace, name)
|
||||
fields := map[string]interface{}{
|
||||
"value": metric.Snapshot().Count(),
|
||||
}
|
||||
return measurement, fields
|
||||
case metrics.Gauge:
|
||||
measurement := fmt.Sprintf("%s%s.gauge", namespace, name)
|
||||
fields := map[string]interface{}{
|
||||
"value": metric.Snapshot().Value(),
|
||||
}
|
||||
return measurement, fields
|
||||
case metrics.GaugeFloat64:
|
||||
measurement := fmt.Sprintf("%s%s.gauge", namespace, name)
|
||||
fields := map[string]interface{}{
|
||||
"value": metric.Snapshot().Value(),
|
||||
}
|
||||
return measurement, fields
|
||||
case metrics.GaugeInfo:
|
||||
ms := metric.Snapshot()
|
||||
measurement := fmt.Sprintf("%s%s.gauge", namespace, name)
|
||||
fields := map[string]interface{}{
|
||||
"value": ms.Value().String(),
|
||||
}
|
||||
return measurement, fields
|
||||
case metrics.Histogram:
|
||||
ms := metric.Snapshot()
|
||||
if ms.Count() <= 0 {
|
||||
break
|
||||
}
|
||||
ps := ms.Percentiles([]float64{0.25, 0.5, 0.75, 0.95, 0.99, 0.999, 0.9999})
|
||||
measurement := fmt.Sprintf("%s%s.histogram", namespace, name)
|
||||
fields := map[string]interface{}{
|
||||
"count": ms.Count(),
|
||||
"max": ms.Max(),
|
||||
"mean": ms.Mean(),
|
||||
"min": ms.Min(),
|
||||
"stddev": ms.StdDev(),
|
||||
"variance": ms.Variance(),
|
||||
"p25": ps[0],
|
||||
"p50": ps[1],
|
||||
"p75": ps[2],
|
||||
"p95": ps[3],
|
||||
"p99": ps[4],
|
||||
"p999": ps[5],
|
||||
"p9999": ps[6],
|
||||
}
|
||||
return measurement, fields
|
||||
case metrics.Meter:
|
||||
ms := metric.Snapshot()
|
||||
measurement := fmt.Sprintf("%s%s.meter", namespace, name)
|
||||
fields := map[string]interface{}{
|
||||
"count": ms.Count(),
|
||||
"m1": ms.Rate1(),
|
||||
"m5": ms.Rate5(),
|
||||
"m15": ms.Rate15(),
|
||||
"mean": ms.RateMean(),
|
||||
}
|
||||
return measurement, fields
|
||||
case metrics.Timer:
|
||||
ms := metric.Snapshot()
|
||||
ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999})
|
||||
|
||||
measurement := fmt.Sprintf("%s%s.timer", namespace, name)
|
||||
fields := map[string]interface{}{
|
||||
"count": ms.Count(),
|
||||
"max": ms.Max(),
|
||||
"mean": ms.Mean(),
|
||||
"min": ms.Min(),
|
||||
"stddev": ms.StdDev(),
|
||||
"variance": ms.Variance(),
|
||||
"p50": ps[0],
|
||||
"p75": ps[1],
|
||||
"p95": ps[2],
|
||||
"p99": ps[3],
|
||||
"p999": ps[4],
|
||||
"p9999": ps[5],
|
||||
"m1": ms.Rate1(),
|
||||
"m5": ms.Rate5(),
|
||||
"m15": ms.Rate15(),
|
||||
"meanrate": ms.RateMean(),
|
||||
}
|
||||
return measurement, fields
|
||||
case metrics.ResettingTimer:
|
||||
t := metric.Snapshot()
|
||||
if t.Count() == 0 {
|
||||
break
|
||||
}
|
||||
ps := t.Percentiles([]float64{0.50, 0.95, 0.99})
|
||||
measurement := fmt.Sprintf("%s%s.span", namespace, name)
|
||||
fields := map[string]interface{}{
|
||||
"count": t.Count(),
|
||||
"max": t.Max(),
|
||||
"mean": t.Mean(),
|
||||
"min": t.Min(),
|
||||
"p50": int(ps[0]),
|
||||
"p95": int(ps[1]),
|
||||
"p99": int(ps[2]),
|
||||
}
|
||||
return measurement, fields
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package influxdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/metrics/internal"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
metrics.Enabled = true
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestExampleV1(t *testing.T) {
|
||||
r := internal.ExampleMetrics()
|
||||
var have, want string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
haveB, _ := io.ReadAll(r.Body)
|
||||
have = string(haveB)
|
||||
r.Body.Close()
|
||||
}))
|
||||
defer ts.Close()
|
||||
u, _ := url.Parse(ts.URL)
|
||||
rep := &reporter{
|
||||
reg: r,
|
||||
url: *u,
|
||||
namespace: "goth.",
|
||||
}
|
||||
if err := rep.makeClient(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := rep.send(978307200); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if wantB, err := os.ReadFile("./testdata/influxdbv1.want"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
want = string(wantB)
|
||||
}
|
||||
if have != want {
|
||||
t.Errorf("\nhave:\n%v\nwant:\n%v\n", have, want)
|
||||
t.Logf("have vs want:\n%v", findFirstDiffPos(have, want))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExampleV2(t *testing.T) {
|
||||
r := internal.ExampleMetrics()
|
||||
var have, want string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
haveB, _ := io.ReadAll(r.Body)
|
||||
have = string(haveB)
|
||||
r.Body.Close()
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
rep := &v2Reporter{
|
||||
reg: r,
|
||||
endpoint: ts.URL,
|
||||
namespace: "goth.",
|
||||
}
|
||||
rep.client = influxdb2.NewClient(rep.endpoint, rep.token)
|
||||
defer rep.client.Close()
|
||||
rep.write = rep.client.WriteAPI(rep.organization, rep.bucket)
|
||||
|
||||
rep.send(978307200)
|
||||
|
||||
if wantB, err := os.ReadFile("./testdata/influxdbv2.want"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
want = string(wantB)
|
||||
}
|
||||
if have != want {
|
||||
t.Errorf("\nhave:\n%v\nwant:\n%v\n", have, want)
|
||||
t.Logf("have vs want:\n%v", findFirstDiffPos(have, want))
|
||||
}
|
||||
}
|
||||
|
||||
func findFirstDiffPos(a, b string) string {
|
||||
yy := strings.Split(b, "\n")
|
||||
for i, x := range strings.Split(a, "\n") {
|
||||
if i >= len(yy) {
|
||||
return fmt.Sprintf("have:%d: %s\nwant:%d: <EOF>", i, x, i)
|
||||
}
|
||||
if y := yy[i]; x != y {
|
||||
return fmt.Sprintf("have:%d: %s\nwant:%d: %s", i, x, i, y)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
package influxdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
uurl "net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
client "github.com/influxdata/influxdb1-client/v2"
|
||||
)
|
||||
|
||||
type reporter struct {
|
||||
reg metrics.Registry
|
||||
interval time.Duration
|
||||
|
||||
url uurl.URL
|
||||
database string
|
||||
username string
|
||||
password string
|
||||
namespace string
|
||||
tags map[string]string
|
||||
|
||||
client client.Client
|
||||
|
||||
cache map[string]int64
|
||||
}
|
||||
|
||||
// InfluxDB starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval.
|
||||
func InfluxDB(r metrics.Registry, d time.Duration, url, database, username, password, namespace string) {
|
||||
InfluxDBWithTags(r, d, url, database, username, password, namespace, nil)
|
||||
}
|
||||
|
||||
// InfluxDBWithTags starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval with the specified tags
|
||||
func InfluxDBWithTags(r metrics.Registry, d time.Duration, url, database, username, password, namespace string, tags map[string]string) {
|
||||
u, err := uurl.Parse(url)
|
||||
if err != nil {
|
||||
log.Warn("Unable to parse InfluxDB", "url", url, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
rep := &reporter{
|
||||
reg: r,
|
||||
interval: d,
|
||||
url: *u,
|
||||
database: database,
|
||||
username: username,
|
||||
password: password,
|
||||
namespace: namespace,
|
||||
tags: tags,
|
||||
cache: make(map[string]int64),
|
||||
}
|
||||
if err := rep.makeClient(); err != nil {
|
||||
log.Warn("Unable to make InfluxDB client", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
rep.run()
|
||||
}
|
||||
|
||||
// InfluxDBWithTagsOnce runs once an InfluxDB reporter and post the given metrics.Registry with the specified tags
|
||||
func InfluxDBWithTagsOnce(r metrics.Registry, url, database, username, password, namespace string, tags map[string]string) error {
|
||||
u, err := uurl.Parse(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse InfluxDB. url: %s, err: %v", url, err)
|
||||
}
|
||||
|
||||
rep := &reporter{
|
||||
reg: r,
|
||||
url: *u,
|
||||
database: database,
|
||||
username: username,
|
||||
password: password,
|
||||
namespace: namespace,
|
||||
tags: tags,
|
||||
cache: make(map[string]int64),
|
||||
}
|
||||
if err := rep.makeClient(); err != nil {
|
||||
return fmt.Errorf("unable to make InfluxDB client. err: %v", err)
|
||||
}
|
||||
|
||||
if err := rep.send(0); err != nil {
|
||||
return fmt.Errorf("unable to send to InfluxDB. err: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reporter) makeClient() (err error) {
|
||||
r.client, err = client.NewHTTPClient(client.HTTPConfig{
|
||||
Addr: r.url.String(),
|
||||
Username: r.username,
|
||||
Password: r.password,
|
||||
Timeout: 10 * time.Second,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *reporter) run() {
|
||||
intervalTicker := time.NewTicker(r.interval)
|
||||
pingTicker := time.NewTicker(time.Second * 5)
|
||||
|
||||
defer intervalTicker.Stop()
|
||||
defer pingTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-intervalTicker.C:
|
||||
if err := r.send(0); err != nil {
|
||||
log.Warn("Unable to send to InfluxDB", "err", err)
|
||||
}
|
||||
case <-pingTicker.C:
|
||||
_, _, err := r.client.Ping(0)
|
||||
if err != nil {
|
||||
log.Warn("Got error while sending a ping to InfluxDB, trying to recreate client", "err", err)
|
||||
|
||||
if err = r.makeClient(); err != nil {
|
||||
log.Warn("Unable to make InfluxDB client", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send sends the measurements. If provided tstamp is >0, it is used. Otherwise,
|
||||
// a 'fresh' timestamp is used.
|
||||
func (r *reporter) send(tstamp int64) error {
|
||||
bps, err := client.NewBatchPoints(
|
||||
client.BatchPointsConfig{
|
||||
Database: r.database,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.reg.Each(func(name string, i interface{}) {
|
||||
var now time.Time
|
||||
if tstamp <= 0 {
|
||||
now = time.Now()
|
||||
} else {
|
||||
now = time.Unix(tstamp, 0)
|
||||
}
|
||||
measurement, fields := readMeter(r.namespace, name, i)
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
if p, err := client.NewPoint(measurement, r.tags, fields, now); err == nil {
|
||||
bps.AddPoint(p)
|
||||
}
|
||||
})
|
||||
return r.client.Write(bps)
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
package influxdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
"github.com/influxdata/influxdb-client-go/v2/api"
|
||||
)
|
||||
|
||||
type v2Reporter struct {
|
||||
reg metrics.Registry
|
||||
interval time.Duration
|
||||
|
||||
endpoint string
|
||||
token string
|
||||
bucket string
|
||||
organization string
|
||||
namespace string
|
||||
tags map[string]string
|
||||
|
||||
client influxdb2.Client
|
||||
write api.WriteAPI
|
||||
}
|
||||
|
||||
// InfluxDBWithTags starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval with the specified tags
|
||||
func InfluxDBV2WithTags(r metrics.Registry, d time.Duration, endpoint string, token string, bucket string, organization string, namespace string, tags map[string]string) {
|
||||
rep := &v2Reporter{
|
||||
reg: r,
|
||||
interval: d,
|
||||
endpoint: endpoint,
|
||||
token: token,
|
||||
bucket: bucket,
|
||||
organization: organization,
|
||||
namespace: namespace,
|
||||
tags: tags,
|
||||
}
|
||||
|
||||
rep.client = influxdb2.NewClient(rep.endpoint, rep.token)
|
||||
defer rep.client.Close()
|
||||
|
||||
// async write client
|
||||
rep.write = rep.client.WriteAPI(rep.organization, rep.bucket)
|
||||
errorsCh := rep.write.Errors()
|
||||
|
||||
// have to handle write errors in a separate goroutine like this b/c the channel is unbuffered and will block writes if not read
|
||||
go func() {
|
||||
for err := range errorsCh {
|
||||
log.Warn("write error", "err", err.Error())
|
||||
}
|
||||
}()
|
||||
rep.run()
|
||||
}
|
||||
|
||||
func (r *v2Reporter) run() {
|
||||
intervalTicker := time.NewTicker(r.interval)
|
||||
pingTicker := time.NewTicker(time.Second * 5)
|
||||
|
||||
defer intervalTicker.Stop()
|
||||
defer pingTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-intervalTicker.C:
|
||||
r.send(0)
|
||||
case <-pingTicker.C:
|
||||
_, err := r.client.Health(context.Background())
|
||||
if err != nil {
|
||||
log.Warn("Got error from influxdb client health check", "err", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send sends the measurements. If provided tstamp is >0, it is used. Otherwise,
|
||||
// a 'fresh' timestamp is used.
|
||||
func (r *v2Reporter) send(tstamp int64) {
|
||||
r.reg.Each(func(name string, i interface{}) {
|
||||
var now time.Time
|
||||
if tstamp <= 0 {
|
||||
now = time.Now()
|
||||
} else {
|
||||
now = time.Unix(tstamp, 0)
|
||||
}
|
||||
measurement, fields := readMeter(r.namespace, name, i)
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
pt := influxdb2.NewPoint(measurement, r.tags, fields, now)
|
||||
r.write.WritePoint(pt)
|
||||
})
|
||||
// Force all unwritten data to be sent
|
||||
r.write.Flush()
|
||||
}
|
||||
11
metrics/influxdb/testdata/influxdbv1.want
vendored
11
metrics/influxdb/testdata/influxdbv1.want
vendored
|
|
@ -1,11 +0,0 @@
|
|||
goth.system/cpu/schedlatency.histogram count=5645i,max=41943040i,mean=1819544.0410983171,min=0i,p25=0,p50=0,p75=7168,p95=16777216,p99=29360128,p999=33554432,p9999=33554432,stddev=6393570.217198883,variance=40877740122252.57 978307200000000000
|
||||
goth.system/memory/pauses.histogram count=14i,max=229376i,mean=50066.28571428572,min=5120i,p25=10240,p50=32768,p75=57344,p95=196608,p99=196608,p999=196608,p9999=196608,stddev=54726.062410783874,variance=2994941906.9890113 978307200000000000
|
||||
goth.test/counter.count value=12345 978307200000000000
|
||||
goth.test/counter_float64.count value=54321.98 978307200000000000
|
||||
goth.test/gauge.gauge value=23456i 978307200000000000
|
||||
goth.test/gauge_float64.gauge value=34567.89 978307200000000000
|
||||
goth.test/gauge_info.gauge value="{\"arch\":\"amd64\",\"commit\":\"7caa2d8163ae3132c1c2d6978c76610caee2d949\",\"os\":\"linux\",\"protocol_versions\":\"64 65 66\",\"version\":\"1.10.18-unstable\"}" 978307200000000000
|
||||
goth.test/histogram.histogram count=3i,max=3i,mean=2,min=1i,p25=1,p50=2,p75=3,p95=3,p99=3,p999=3,p9999=3,stddev=0.816496580927726,variance=0.6666666666666666 978307200000000000
|
||||
goth.test/meter.meter count=0i,m1=0,m15=0,m5=0,mean=0 978307200000000000
|
||||
goth.test/resetting_timer.span count=6i,max=120000000i,mean=30000000,min=10000000i,p50=12500000i,p95=120000000i,p99=120000000i 978307200000000000
|
||||
goth.test/timer.timer count=6i,m1=0,m15=0,m5=0,max=120000000i,mean=38333333.333333336,meanrate=0,min=20000000i,p50=22500000,p75=48000000,p95=120000000,p99=120000000,p999=120000000,p9999=120000000,stddev=36545253.529775314,variance=1335555555555555.2 978307200000000000
|
||||
11
metrics/influxdb/testdata/influxdbv2.want
vendored
11
metrics/influxdb/testdata/influxdbv2.want
vendored
|
|
@ -1,11 +0,0 @@
|
|||
goth.system/cpu/schedlatency.histogram count=5645i,max=41943040i,mean=1819544.0410983171,min=0i,p25=0,p50=0,p75=7168,p95=16777216,p99=29360128,p999=33554432,p9999=33554432,stddev=6393570.217198883,variance=40877740122252.57 978307200000000000
|
||||
goth.system/memory/pauses.histogram count=14i,max=229376i,mean=50066.28571428572,min=5120i,p25=10240,p50=32768,p75=57344,p95=196608,p99=196608,p999=196608,p9999=196608,stddev=54726.062410783874,variance=2994941906.9890113 978307200000000000
|
||||
goth.test/counter.count value=12345 978307200000000000
|
||||
goth.test/counter_float64.count value=54321.98 978307200000000000
|
||||
goth.test/gauge.gauge value=23456i 978307200000000000
|
||||
goth.test/gauge_float64.gauge value=34567.89 978307200000000000
|
||||
goth.test/gauge_info.gauge value="{\"arch\":\"amd64\",\"commit\":\"7caa2d8163ae3132c1c2d6978c76610caee2d949\",\"os\":\"linux\",\"protocol_versions\":\"64 65 66\",\"version\":\"1.10.18-unstable\"}" 978307200000000000
|
||||
goth.test/histogram.histogram count=3i,max=3i,mean=2,min=1i,p25=1,p50=2,p75=3,p95=3,p99=3,p999=3,p9999=3,stddev=0.816496580927726,variance=0.6666666666666666 978307200000000000
|
||||
goth.test/meter.meter count=0i,m1=0,m15=0,m5=0,mean=0 978307200000000000
|
||||
goth.test/resetting_timer.span count=6i,max=120000000i,mean=30000000,min=10000000i,p50=12500000i,p95=120000000i,p99=120000000i 978307200000000000
|
||||
goth.test/timer.timer count=6i,m1=0,m15=0,m5=0,max=120000000i,mean=38333333.333333336,meanrate=0,min=20000000i,p50=22500000,p75=48000000,p95=120000000,p99=120000000,p999=120000000,p9999=120000000,stddev=36545253.529775314,variance=1335555555555555.2 978307200000000000
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package metrics
|
||||
|
||||
func init() {
|
||||
Enabled = true
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
metrics2 "runtime/metrics"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
// ExampleMetrics returns an ordered registry populated with a sample of metrics.
|
||||
func ExampleMetrics() metrics.Registry {
|
||||
var registry = metrics.NewOrderedRegistry()
|
||||
|
||||
metrics.NewRegisteredCounterFloat64("test/counter", registry).Inc(12345)
|
||||
metrics.NewRegisteredCounterFloat64("test/counter_float64", registry).Inc(54321.98)
|
||||
metrics.NewRegisteredGauge("test/gauge", registry).Update(23456)
|
||||
metrics.NewRegisteredGaugeFloat64("test/gauge_float64", registry).Update(34567.89)
|
||||
metrics.NewRegisteredGaugeInfo("test/gauge_info", registry).Update(
|
||||
metrics.GaugeInfoValue{
|
||||
"version": "1.10.18-unstable",
|
||||
"arch": "amd64",
|
||||
"os": "linux",
|
||||
"commit": "7caa2d8163ae3132c1c2d6978c76610caee2d949",
|
||||
"protocol_versions": "64 65 66",
|
||||
})
|
||||
|
||||
{
|
||||
s := metrics.NewUniformSample(3)
|
||||
s.Update(1)
|
||||
s.Update(2)
|
||||
s.Update(3)
|
||||
//metrics.NewRegisteredHistogram("test/histogram", registry, metrics.NewSampleSnapshot(3, []int64{1, 2, 3}))
|
||||
metrics.NewRegisteredHistogram("test/histogram", registry, s)
|
||||
}
|
||||
registry.Register("test/meter", metrics.NewInactiveMeter())
|
||||
{
|
||||
timer := metrics.NewRegisteredResettingTimer("test/resetting_timer", registry)
|
||||
timer.Update(10 * time.Millisecond)
|
||||
timer.Update(11 * time.Millisecond)
|
||||
timer.Update(12 * time.Millisecond)
|
||||
timer.Update(120 * time.Millisecond)
|
||||
timer.Update(13 * time.Millisecond)
|
||||
timer.Update(14 * time.Millisecond)
|
||||
}
|
||||
{
|
||||
timer := metrics.NewRegisteredTimer("test/timer", registry)
|
||||
timer.Update(20 * time.Millisecond)
|
||||
timer.Update(21 * time.Millisecond)
|
||||
timer.Update(22 * time.Millisecond)
|
||||
timer.Update(120 * time.Millisecond)
|
||||
timer.Update(23 * time.Millisecond)
|
||||
timer.Update(24 * time.Millisecond)
|
||||
timer.Stop()
|
||||
}
|
||||
registry.Register("test/empty_resetting_timer", metrics.NewResettingTimer().Snapshot())
|
||||
|
||||
{ // go runtime metrics
|
||||
var sLatency = "7\xff\x81\x03\x01\x01\x10Float64Histogram\x01\xff\x82\x00\x01\x02\x01\x06Counts\x01\xff\x84\x00\x01\aBuckets\x01\xff\x86\x00\x00\x00\x16\xff\x83\x02\x01\x01\b[]uint64\x01\xff\x84\x00\x01\x06\x00\x00\x17\xff\x85\x02\x01\x01\t[]float64\x01\xff\x86\x00\x01\b\x00\x00\xfe\x06T\xff\x82\x01\xff\xa2\x00\xfe\r\xef\x00\x01\x02\x02\x04\x05\x04\b\x15\x17 B?6.L;$!2) \x1a? \x190aH7FY6#\x190\x1d\x14\x10\x1b\r\t\x04\x03\x01\x01\x00\x03\x02\x00\x03\x05\x05\x02\x02\x06\x04\v\x06\n\x15\x18\x13'&.\x12=H/L&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xa3\xfe\xf0\xff\x00\xf8\x95\xd6&\xe8\v.q>\xf8\x95\xd6&\xe8\v.\x81>\xf8\xdfA:\xdc\x11ʼn>\xf8\x95\xd6&\xe8\v.\x91>\xf8:\x8c0\xe2\x8ey\x95>\xf8\xdfA:\xdc\x11ř>\xf8\x84\xf7C֔\x10\x9e>\xf8\x95\xd6&\xe8\v.\xa1>\xf8:\x8c0\xe2\x8ey\xa5>\xf8\xdfA:\xdc\x11ũ>\xf8\x84\xf7C֔\x10\xae>\xf8\x95\xd6&\xe8\v.\xb1>\xf8:\x8c0\xe2\x8ey\xb5>\xf8\xdfA:\xdc\x11Ź>\xf8\x84\xf7C֔\x10\xbe>\xf8\x95\xd6&\xe8\v.\xc1>\xf8:\x8c0\xe2\x8ey\xc5>\xf8\xdfA:\xdc\x11\xc5\xc9>\xf8\x84\xf7C֔\x10\xce>\xf8\x95\xd6&\xe8\v.\xd1>\xf8:\x8c0\xe2\x8ey\xd5>\xf8\xdfA:\xdc\x11\xc5\xd9>\xf8\x84\xf7C֔\x10\xde>\xf8\x95\xd6&\xe8\v.\xe1>\xf8:\x8c0\xe2\x8ey\xe5>\xf8\xdfA:\xdc\x11\xc5\xe9>\xf8\x84\xf7C֔\x10\xee>\xf8\x95\xd6&\xe8\v.\xf1>\xf8:\x8c0\xe2\x8ey\xf5>\xf8\xdfA:\xdc\x11\xc5\xf9>\xf8\x84\xf7C֔\x10\xfe>\xf8\x95\xd6&\xe8\v.\x01?\xf8:\x8c0\xe2\x8ey\x05?\xf8\xdfA:\xdc\x11\xc5\t?\xf8\x84\xf7C֔\x10\x0e?\xf8\x95\xd6&\xe8\v.\x11?\xf8:\x8c0\xe2\x8ey\x15?\xf8\xdfA:\xdc\x11\xc5\x19?\xf8\x84\xf7C֔\x10\x1e?\xf8\x95\xd6&\xe8\v.!?\xf8:\x8c0\xe2\x8ey%?\xf8\xdfA:\xdc\x11\xc5)?\xf8\x84\xf7C֔\x10.?\xf8\x95\xd6&\xe8\v.1?\xf8:\x8c0\xe2\x8ey5?\xf8\xdfA:\xdc\x11\xc59?\xf8\x84\xf7C֔\x10>?\xf8\x95\xd6&\xe8\v.A?\xf8:\x8c0\xe2\x8eyE?\xf8\xdfA:\xdc\x11\xc5I?\xf8\x84\xf7C֔\x10N?\xf8\x95\xd6&\xe8\v.Q?\xf8:\x8c0\xe2\x8eyU?\xf8\xdfA:\xdc\x11\xc5Y?\xf8\x84\xf7C֔\x10^?\xf8\x95\xd6&\xe8\v.a?\xf8:\x8c0\xe2\x8eye?\xf8\xdfA:\xdc\x11\xc5i?\xf8\x84\xf7C֔\x10n?\xf8\x95\xd6&\xe8\v.q?\xf8:\x8c0\xe2\x8eyu?\xf8\xdfA:\xdc\x11\xc5y?\xf8\x84\xf7C֔\x10~?\xf8\x95\xd6&\xe8\v.\x81?\xf8:\x8c0\xe2\x8ey\x85?\xf8\xdfA:\xdc\x11ʼn?\xf8\x84\xf7C֔\x10\x8e?\xf8\x95\xd6&\xe8\v.\x91?\xf8:\x8c0\xe2\x8ey\x95?\xf8\xdfA:\xdc\x11ř?\xf8\x84\xf7C֔\x10\x9e?\xf8\x95\xd6&\xe8\v.\xa1?\xf8:\x8c0\xe2\x8ey\xa5?\xf8\xdfA:\xdc\x11ũ?\xf8\x84\xf7C֔\x10\xae?\xf8\x95\xd6&\xe8\v.\xb1?\xf8:\x8c0\xe2\x8ey\xb5?\xf8\xdfA:\xdc\x11Ź?\xf8\x84\xf7C֔\x10\xbe?\xf8\x95\xd6&\xe8\v.\xc1?\xf8:\x8c0\xe2\x8ey\xc5?\xf8\xdfA:\xdc\x11\xc5\xc9?\xf8\x84\xf7C֔\x10\xce?\xf8\x95\xd6&\xe8\v.\xd1?\xf8:\x8c0\xe2\x8ey\xd5?\xf8\xdfA:\xdc\x11\xc5\xd9?\xf8\x84\xf7C֔\x10\xde?\xf8\x95\xd6&\xe8\v.\xe1?\xf8:\x8c0\xe2\x8ey\xe5?\xf8\xdfA:\xdc\x11\xc5\xe9?\xf8\x84\xf7C֔\x10\xee?\xf8\x95\xd6&\xe8\v.\xf1?\xf8:\x8c0\xe2\x8ey\xf5?\xf8\xdfA:\xdc\x11\xc5\xf9?\xf8\x84\xf7C֔\x10\xfe?\xf8\x95\xd6&\xe8\v.\x01@\xf8:\x8c0\xe2\x8ey\x05@\xf8\xdfA:\xdc\x11\xc5\t@\xf8\x84\xf7C֔\x10\x0e@\xf8\x95\xd6&\xe8\v.\x11@\xf8:\x8c0\xe2\x8ey\x15@\xf8\xdfA:\xdc\x11\xc5\x19@\xf8\x84\xf7C֔\x10\x1e@\xf8\x95\xd6&\xe8\v.!@\xf8:\x8c0\xe2\x8ey%@\xf8\xdfA:\xdc\x11\xc5)@\xf8\x84\xf7C֔\x10.@\xf8\x95\xd6&\xe8\v.1@\xf8:\x8c0\xe2\x8ey5@\xf8\xdfA:\xdc\x11\xc59@\xf8\x84\xf7C֔\x10>@\xf8\x95\xd6&\xe8\v.A@\xf8:\x8c0\xe2\x8eyE@\xf8\xdfA:\xdc\x11\xc5I@\xf8\x84\xf7C֔\x10N@\xf8\x95\xd6&\xe8\v.Q@\xf8:\x8c0\xe2\x8eyU@\xf8\xdfA:\xdc\x11\xc5Y@\xf8\x84\xf7C֔\x10^@\xf8\x95\xd6&\xe8\v.a@\xf8:\x8c0\xe2\x8eye@\xf8\xdfA:\xdc\x11\xc5i@\xf8\x84\xf7C֔\x10n@\xf8\x95\xd6&\xe8\v.q@\xf8:\x8c0\xe2\x8eyu@\xf8\xdfA:\xdc\x11\xc5y@\xf8\x84\xf7C֔\x10~@\xf8\x95\xd6&\xe8\v.\x81@\xf8:\x8c0\xe2\x8ey\x85@\xf8\xdfA:\xdc\x11ʼn@\xf8\x84\xf7C֔\x10\x8e@\xf8\x95\xd6&\xe8\v.\x91@\xf8:\x8c0\xe2\x8ey\x95@\xf8\xdfA:\xdc\x11ř@\xf8\x84\xf7C֔\x10\x9e@\xf8\x95\xd6&\xe8\v.\xa1@\xf8:\x8c0\xe2\x8ey\xa5@\xf8\xdfA:\xdc\x11ũ@\xf8\x84\xf7C֔\x10\xae@\xf8\x95\xd6&\xe8\v.\xb1@\xf8:\x8c0\xe2\x8ey\xb5@\xf8\xdfA:\xdc\x11Ź@\xf8\x84\xf7C֔\x10\xbe@\xf8\x95\xd6&\xe8\v.\xc1@\xf8:\x8c0\xe2\x8ey\xc5@\xf8\xdfA:\xdc\x11\xc5\xc9@\xf8\x84\xf7C֔\x10\xce@\xf8\x95\xd6&\xe8\v.\xd1@\xf8:\x8c0\xe2\x8ey\xd5@\xf8\xdfA:\xdc\x11\xc5\xd9@\xf8\x84\xf7C֔\x10\xde@\xf8\x95\xd6&\xe8\v.\xe1@\xf8:\x8c0\xe2\x8ey\xe5@\xf8\xdfA:\xdc\x11\xc5\xe9@\xf8\x84\xf7C֔\x10\xee@\xf8\x95\xd6&\xe8\v.\xf1@\xf8:\x8c0\xe2\x8ey\xf5@\xf8\xdfA:\xdc\x11\xc5\xf9@\xf8\x84\xf7C֔\x10\xfe@\xf8\x95\xd6&\xe8\v.\x01A\xfe\xf0\x7f\x00"
|
||||
var gcPauses = "7\xff\x81\x03\x01\x01\x10Float64Histogram\x01\xff\x82\x00\x01\x02\x01\x06Counts\x01\xff\x84\x00\x01\aBuckets\x01\xff\x86\x00\x00\x00\x16\xff\x83\x02\x01\x01\b[]uint64\x01\xff\x84\x00\x01\x06\x00\x00\x17\xff\x85\x02\x01\x01\t[]float64\x01\xff\x86\x00\x01\b\x00\x00\xfe\x06R\xff\x82\x01\xff\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x00\x02\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xa3\xfe\xf0\xff\x00\xf8\x95\xd6&\xe8\v.q>\xf8\x95\xd6&\xe8\v.\x81>\xf8\xdfA:\xdc\x11ʼn>\xf8\x95\xd6&\xe8\v.\x91>\xf8:\x8c0\xe2\x8ey\x95>\xf8\xdfA:\xdc\x11ř>\xf8\x84\xf7C֔\x10\x9e>\xf8\x95\xd6&\xe8\v.\xa1>\xf8:\x8c0\xe2\x8ey\xa5>\xf8\xdfA:\xdc\x11ũ>\xf8\x84\xf7C֔\x10\xae>\xf8\x95\xd6&\xe8\v.\xb1>\xf8:\x8c0\xe2\x8ey\xb5>\xf8\xdfA:\xdc\x11Ź>\xf8\x84\xf7C֔\x10\xbe>\xf8\x95\xd6&\xe8\v.\xc1>\xf8:\x8c0\xe2\x8ey\xc5>\xf8\xdfA:\xdc\x11\xc5\xc9>\xf8\x84\xf7C֔\x10\xce>\xf8\x95\xd6&\xe8\v.\xd1>\xf8:\x8c0\xe2\x8ey\xd5>\xf8\xdfA:\xdc\x11\xc5\xd9>\xf8\x84\xf7C֔\x10\xde>\xf8\x95\xd6&\xe8\v.\xe1>\xf8:\x8c0\xe2\x8ey\xe5>\xf8\xdfA:\xdc\x11\xc5\xe9>\xf8\x84\xf7C֔\x10\xee>\xf8\x95\xd6&\xe8\v.\xf1>\xf8:\x8c0\xe2\x8ey\xf5>\xf8\xdfA:\xdc\x11\xc5\xf9>\xf8\x84\xf7C֔\x10\xfe>\xf8\x95\xd6&\xe8\v.\x01?\xf8:\x8c0\xe2\x8ey\x05?\xf8\xdfA:\xdc\x11\xc5\t?\xf8\x84\xf7C֔\x10\x0e?\xf8\x95\xd6&\xe8\v.\x11?\xf8:\x8c0\xe2\x8ey\x15?\xf8\xdfA:\xdc\x11\xc5\x19?\xf8\x84\xf7C֔\x10\x1e?\xf8\x95\xd6&\xe8\v.!?\xf8:\x8c0\xe2\x8ey%?\xf8\xdfA:\xdc\x11\xc5)?\xf8\x84\xf7C֔\x10.?\xf8\x95\xd6&\xe8\v.1?\xf8:\x8c0\xe2\x8ey5?\xf8\xdfA:\xdc\x11\xc59?\xf8\x84\xf7C֔\x10>?\xf8\x95\xd6&\xe8\v.A?\xf8:\x8c0\xe2\x8eyE?\xf8\xdfA:\xdc\x11\xc5I?\xf8\x84\xf7C֔\x10N?\xf8\x95\xd6&\xe8\v.Q?\xf8:\x8c0\xe2\x8eyU?\xf8\xdfA:\xdc\x11\xc5Y?\xf8\x84\xf7C֔\x10^?\xf8\x95\xd6&\xe8\v.a?\xf8:\x8c0\xe2\x8eye?\xf8\xdfA:\xdc\x11\xc5i?\xf8\x84\xf7C֔\x10n?\xf8\x95\xd6&\xe8\v.q?\xf8:\x8c0\xe2\x8eyu?\xf8\xdfA:\xdc\x11\xc5y?\xf8\x84\xf7C֔\x10~?\xf8\x95\xd6&\xe8\v.\x81?\xf8:\x8c0\xe2\x8ey\x85?\xf8\xdfA:\xdc\x11ʼn?\xf8\x84\xf7C֔\x10\x8e?\xf8\x95\xd6&\xe8\v.\x91?\xf8:\x8c0\xe2\x8ey\x95?\xf8\xdfA:\xdc\x11ř?\xf8\x84\xf7C֔\x10\x9e?\xf8\x95\xd6&\xe8\v.\xa1?\xf8:\x8c0\xe2\x8ey\xa5?\xf8\xdfA:\xdc\x11ũ?\xf8\x84\xf7C֔\x10\xae?\xf8\x95\xd6&\xe8\v.\xb1?\xf8:\x8c0\xe2\x8ey\xb5?\xf8\xdfA:\xdc\x11Ź?\xf8\x84\xf7C֔\x10\xbe?\xf8\x95\xd6&\xe8\v.\xc1?\xf8:\x8c0\xe2\x8ey\xc5?\xf8\xdfA:\xdc\x11\xc5\xc9?\xf8\x84\xf7C֔\x10\xce?\xf8\x95\xd6&\xe8\v.\xd1?\xf8:\x8c0\xe2\x8ey\xd5?\xf8\xdfA:\xdc\x11\xc5\xd9?\xf8\x84\xf7C֔\x10\xde?\xf8\x95\xd6&\xe8\v.\xe1?\xf8:\x8c0\xe2\x8ey\xe5?\xf8\xdfA:\xdc\x11\xc5\xe9?\xf8\x84\xf7C֔\x10\xee?\xf8\x95\xd6&\xe8\v.\xf1?\xf8:\x8c0\xe2\x8ey\xf5?\xf8\xdfA:\xdc\x11\xc5\xf9?\xf8\x84\xf7C֔\x10\xfe?\xf8\x95\xd6&\xe8\v.\x01@\xf8:\x8c0\xe2\x8ey\x05@\xf8\xdfA:\xdc\x11\xc5\t@\xf8\x84\xf7C֔\x10\x0e@\xf8\x95\xd6&\xe8\v.\x11@\xf8:\x8c0\xe2\x8ey\x15@\xf8\xdfA:\xdc\x11\xc5\x19@\xf8\x84\xf7C֔\x10\x1e@\xf8\x95\xd6&\xe8\v.!@\xf8:\x8c0\xe2\x8ey%@\xf8\xdfA:\xdc\x11\xc5)@\xf8\x84\xf7C֔\x10.@\xf8\x95\xd6&\xe8\v.1@\xf8:\x8c0\xe2\x8ey5@\xf8\xdfA:\xdc\x11\xc59@\xf8\x84\xf7C֔\x10>@\xf8\x95\xd6&\xe8\v.A@\xf8:\x8c0\xe2\x8eyE@\xf8\xdfA:\xdc\x11\xc5I@\xf8\x84\xf7C֔\x10N@\xf8\x95\xd6&\xe8\v.Q@\xf8:\x8c0\xe2\x8eyU@\xf8\xdfA:\xdc\x11\xc5Y@\xf8\x84\xf7C֔\x10^@\xf8\x95\xd6&\xe8\v.a@\xf8:\x8c0\xe2\x8eye@\xf8\xdfA:\xdc\x11\xc5i@\xf8\x84\xf7C֔\x10n@\xf8\x95\xd6&\xe8\v.q@\xf8:\x8c0\xe2\x8eyu@\xf8\xdfA:\xdc\x11\xc5y@\xf8\x84\xf7C֔\x10~@\xf8\x95\xd6&\xe8\v.\x81@\xf8:\x8c0\xe2\x8ey\x85@\xf8\xdfA:\xdc\x11ʼn@\xf8\x84\xf7C֔\x10\x8e@\xf8\x95\xd6&\xe8\v.\x91@\xf8:\x8c0\xe2\x8ey\x95@\xf8\xdfA:\xdc\x11ř@\xf8\x84\xf7C֔\x10\x9e@\xf8\x95\xd6&\xe8\v.\xa1@\xf8:\x8c0\xe2\x8ey\xa5@\xf8\xdfA:\xdc\x11ũ@\xf8\x84\xf7C֔\x10\xae@\xf8\x95\xd6&\xe8\v.\xb1@\xf8:\x8c0\xe2\x8ey\xb5@\xf8\xdfA:\xdc\x11Ź@\xf8\x84\xf7C֔\x10\xbe@\xf8\x95\xd6&\xe8\v.\xc1@\xf8:\x8c0\xe2\x8ey\xc5@\xf8\xdfA:\xdc\x11\xc5\xc9@\xf8\x84\xf7C֔\x10\xce@\xf8\x95\xd6&\xe8\v.\xd1@\xf8:\x8c0\xe2\x8ey\xd5@\xf8\xdfA:\xdc\x11\xc5\xd9@\xf8\x84\xf7C֔\x10\xde@\xf8\x95\xd6&\xe8\v.\xe1@\xf8:\x8c0\xe2\x8ey\xe5@\xf8\xdfA:\xdc\x11\xc5\xe9@\xf8\x84\xf7C֔\x10\xee@\xf8\x95\xd6&\xe8\v.\xf1@\xf8:\x8c0\xe2\x8ey\xf5@\xf8\xdfA:\xdc\x11\xc5\xf9@\xf8\x84\xf7C֔\x10\xfe@\xf8\x95\xd6&\xe8\v.\x01A\xfe\xf0\x7f\x00"
|
||||
|
||||
var secondsToNs = float64(time.Second)
|
||||
|
||||
dserialize := func(data string) *metrics2.Float64Histogram {
|
||||
var res metrics2.Float64Histogram
|
||||
if err := gob.NewDecoder(bytes.NewReader([]byte(data))).Decode(&res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &res
|
||||
}
|
||||
cpuSchedLatency := metrics.RuntimeHistogramFromData(secondsToNs, dserialize(sLatency))
|
||||
registry.Register("system/cpu/schedlatency", cpuSchedLatency)
|
||||
|
||||
memPauses := metrics.RuntimeHistogramFromData(secondsToNs, dserialize(gcPauses))
|
||||
registry.Register("system/memory/pauses", memPauses)
|
||||
}
|
||||
return registry
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
metrics2 "runtime/metrics"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
func TestCollectRuntimeMetrics(t *testing.T) {
|
||||
t.Skip("Only used for generating testdata")
|
||||
serialize := func(path string, histogram *metrics2.Float64Histogram) {
|
||||
var f = new(bytes.Buffer)
|
||||
if err := gob.NewEncoder(f).Encode(histogram); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("var %v = %q\n", path, f.Bytes())
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
stats := metrics.ReadRuntimeStats()
|
||||
serialize("schedlatency", stats.SchedLatency)
|
||||
serialize("gcpauses", stats.GCPauses)
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MarshalJSON returns a byte slice containing a JSON representation of all
|
||||
// the metrics in the Registry.
|
||||
func (r *StandardRegistry) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(r.GetAll())
|
||||
}
|
||||
|
||||
// WriteJSON writes metrics from the given registry periodically to the
|
||||
// specified io.Writer as JSON.
|
||||
func WriteJSON(r Registry, d time.Duration, w io.Writer) {
|
||||
for range time.Tick(d) {
|
||||
WriteJSONOnce(r, w)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteJSONOnce writes metrics from the given registry to the specified
|
||||
// io.Writer as JSON.
|
||||
func WriteJSONOnce(r Registry, w io.Writer) {
|
||||
json.NewEncoder(w).Encode(r)
|
||||
}
|
||||
|
||||
func (p *PrefixedRegistry) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(p.GetAll())
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegistryMarshallJSON(t *testing.T) {
|
||||
b := &bytes.Buffer{}
|
||||
enc := json.NewEncoder(b)
|
||||
r := NewRegistry()
|
||||
r.Register("counter", NewCounter())
|
||||
enc.Encode(r)
|
||||
if s := b.String(); s != "{\"counter\":{\"count\":0}}\n" {
|
||||
t.Fatalf(s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryWriteJSONOnce(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
r.Register("counter", NewCounter())
|
||||
b := &bytes.Buffer{}
|
||||
WriteJSONOnce(r, b)
|
||||
if s := b.String(); s != "{\"counter\":{\"count\":0}}\n" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
package librato
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const Operations = "operations"
|
||||
const OperationsShort = "ops"
|
||||
|
||||
type LibratoClient struct {
|
||||
Email, Token string
|
||||
}
|
||||
|
||||
// property strings
|
||||
const (
|
||||
// display attributes
|
||||
Color = "color"
|
||||
DisplayMax = "display_max"
|
||||
DisplayMin = "display_min"
|
||||
DisplayUnitsLong = "display_units_long"
|
||||
DisplayUnitsShort = "display_units_short"
|
||||
DisplayStacked = "display_stacked"
|
||||
DisplayTransform = "display_transform"
|
||||
// special gauge display attributes
|
||||
SummarizeFunction = "summarize_function"
|
||||
Aggregate = "aggregate"
|
||||
|
||||
// metric keys
|
||||
Name = "name"
|
||||
Period = "period"
|
||||
Description = "description"
|
||||
DisplayName = "display_name"
|
||||
Attributes = "attributes"
|
||||
|
||||
// measurement keys
|
||||
MeasureTime = "measure_time"
|
||||
Source = "source"
|
||||
Value = "value"
|
||||
|
||||
// special gauge keys
|
||||
Count = "count"
|
||||
Sum = "sum"
|
||||
Max = "max"
|
||||
Min = "min"
|
||||
SumSquares = "sum_squares"
|
||||
|
||||
// batch keys
|
||||
Counters = "counters"
|
||||
Gauges = "gauges"
|
||||
|
||||
MetricsPostUrl = "https://metrics-api.librato.com/v1/metrics"
|
||||
)
|
||||
|
||||
type Measurement map[string]interface{}
|
||||
type Metric map[string]interface{}
|
||||
|
||||
type Batch struct {
|
||||
Gauges []Measurement `json:"gauges,omitempty"`
|
||||
Counters []Measurement `json:"counters,omitempty"`
|
||||
MeasureTime int64 `json:"measure_time"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
func (c *LibratoClient) PostMetrics(batch Batch) (err error) {
|
||||
var (
|
||||
js []byte
|
||||
req *http.Request
|
||||
resp *http.Response
|
||||
)
|
||||
|
||||
if len(batch.Counters) == 0 && len(batch.Gauges) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if js, err = json.Marshal(batch); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if req, err = http.NewRequest(http.MethodPost, MetricsPostUrl, bytes.NewBuffer(js)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.SetBasicAuth(c.Email, c.Token)
|
||||
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
var body []byte
|
||||
if body, err = io.ReadAll(resp.Body); err != nil {
|
||||
body = []byte(fmt.Sprintf("(could not fetch response body for error: %s)", err))
|
||||
}
|
||||
err = fmt.Errorf("unable to post to Librato: %d %s %s", resp.StatusCode, resp.Status, string(body))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
package librato
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
// a regexp for extracting the unit from time.Duration.String
|
||||
var unitRegexp = regexp.MustCompile(`[^\\d]+$`)
|
||||
|
||||
// a helper that turns a time.Duration into librato display attributes for timer metrics
|
||||
func translateTimerAttributes(d time.Duration) (attrs map[string]interface{}) {
|
||||
attrs = make(map[string]interface{})
|
||||
attrs[DisplayTransform] = fmt.Sprintf("x/%d", int64(d))
|
||||
attrs[DisplayUnitsShort] = string(unitRegexp.Find([]byte(d.String())))
|
||||
return
|
||||
}
|
||||
|
||||
type Reporter struct {
|
||||
Email, Token string
|
||||
Namespace string
|
||||
Source string
|
||||
Interval time.Duration
|
||||
Registry metrics.Registry
|
||||
Percentiles []float64 // percentiles to report on histogram metrics
|
||||
TimerAttributes map[string]interface{} // units in which timers will be displayed
|
||||
intervalSec int64
|
||||
}
|
||||
|
||||
func NewReporter(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) *Reporter {
|
||||
return &Reporter{e, t, "", s, d, r, p, translateTimerAttributes(u), int64(d / time.Second)}
|
||||
}
|
||||
|
||||
func Librato(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) {
|
||||
NewReporter(r, d, e, t, s, p, u).Run()
|
||||
}
|
||||
|
||||
func (rep *Reporter) Run() {
|
||||
log.Printf("WARNING: This client has been DEPRECATED! It has been moved to https://github.com/mihasya/go-metrics-librato and will be removed from rcrowley/go-metrics on August 5th 2015")
|
||||
ticker := time.NewTicker(rep.Interval)
|
||||
defer ticker.Stop()
|
||||
metricsApi := &LibratoClient{rep.Email, rep.Token}
|
||||
for now := range ticker.C {
|
||||
var metrics Batch
|
||||
var err error
|
||||
if metrics, err = rep.BuildRequest(now, rep.Registry); err != nil {
|
||||
log.Printf("ERROR constructing librato request body %s", err)
|
||||
continue
|
||||
}
|
||||
if err := metricsApi.PostMetrics(metrics); err != nil {
|
||||
log.Printf("ERROR sending metrics to librato %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate sum of squares from data provided by metrics.Histogram
|
||||
// see http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
|
||||
func sumSquares(icount int64, mean, stDev float64) float64 {
|
||||
count := float64(icount)
|
||||
sumSquared := math.Pow(count*mean, 2)
|
||||
sumSquares := math.Pow(count*stDev, 2) + sumSquared/count
|
||||
if math.IsNaN(sumSquares) {
|
||||
return 0.0
|
||||
}
|
||||
return sumSquares
|
||||
}
|
||||
func sumSquaresTimer(t metrics.TimerSnapshot) float64 {
|
||||
count := float64(t.Count())
|
||||
sumSquared := math.Pow(count*t.Mean(), 2)
|
||||
sumSquares := math.Pow(count*t.StdDev(), 2) + sumSquared/count
|
||||
if math.IsNaN(sumSquares) {
|
||||
return 0.0
|
||||
}
|
||||
return sumSquares
|
||||
}
|
||||
|
||||
func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Batch, err error) {
|
||||
snapshot = Batch{
|
||||
// coerce timestamps to a stepping fn so that they line up in Librato graphs
|
||||
MeasureTime: (now.Unix() / rep.intervalSec) * rep.intervalSec,
|
||||
Source: rep.Source,
|
||||
}
|
||||
snapshot.Gauges = make([]Measurement, 0)
|
||||
snapshot.Counters = make([]Measurement, 0)
|
||||
histogramGaugeCount := 1 + len(rep.Percentiles)
|
||||
r.Each(func(name string, metric interface{}) {
|
||||
if rep.Namespace != "" {
|
||||
name = fmt.Sprintf("%s.%s", rep.Namespace, name)
|
||||
}
|
||||
measurement := Measurement{}
|
||||
measurement[Period] = rep.Interval.Seconds()
|
||||
switch m := metric.(type) {
|
||||
case metrics.Counter:
|
||||
ms := m.Snapshot()
|
||||
if ms.Count() > 0 {
|
||||
measurement[Name] = fmt.Sprintf("%s.%s", name, "count")
|
||||
measurement[Value] = float64(ms.Count())
|
||||
measurement[Attributes] = map[string]interface{}{
|
||||
DisplayUnitsLong: Operations,
|
||||
DisplayUnitsShort: OperationsShort,
|
||||
DisplayMin: "0",
|
||||
}
|
||||
snapshot.Counters = append(snapshot.Counters, measurement)
|
||||
}
|
||||
case metrics.CounterFloat64:
|
||||
if count := m.Snapshot().Count(); count > 0 {
|
||||
measurement[Name] = fmt.Sprintf("%s.%s", name, "count")
|
||||
measurement[Value] = count
|
||||
measurement[Attributes] = map[string]interface{}{
|
||||
DisplayUnitsLong: Operations,
|
||||
DisplayUnitsShort: OperationsShort,
|
||||
DisplayMin: "0",
|
||||
}
|
||||
snapshot.Counters = append(snapshot.Counters, measurement)
|
||||
}
|
||||
case metrics.Gauge:
|
||||
measurement[Name] = name
|
||||
measurement[Value] = float64(m.Snapshot().Value())
|
||||
snapshot.Gauges = append(snapshot.Gauges, measurement)
|
||||
case metrics.GaugeFloat64:
|
||||
measurement[Name] = name
|
||||
measurement[Value] = m.Snapshot().Value()
|
||||
snapshot.Gauges = append(snapshot.Gauges, measurement)
|
||||
case metrics.GaugeInfo:
|
||||
measurement[Name] = name
|
||||
measurement[Value] = m.Snapshot().Value()
|
||||
snapshot.Gauges = append(snapshot.Gauges, measurement)
|
||||
case metrics.Histogram:
|
||||
ms := m.Snapshot()
|
||||
if ms.Count() > 0 {
|
||||
gauges := make([]Measurement, histogramGaugeCount)
|
||||
measurement[Name] = fmt.Sprintf("%s.%s", name, "hist")
|
||||
measurement[Count] = uint64(ms.Count())
|
||||
measurement[Max] = float64(ms.Max())
|
||||
measurement[Min] = float64(ms.Min())
|
||||
measurement[Sum] = float64(ms.Sum())
|
||||
measurement[SumSquares] = sumSquares(ms.Count(), ms.Mean(), ms.StdDev())
|
||||
gauges[0] = measurement
|
||||
for i, p := range rep.Percentiles {
|
||||
gauges[i+1] = Measurement{
|
||||
Name: fmt.Sprintf("%s.%.2f", measurement[Name], p),
|
||||
Value: ms.Percentile(p),
|
||||
Period: measurement[Period],
|
||||
}
|
||||
}
|
||||
snapshot.Gauges = append(snapshot.Gauges, gauges...)
|
||||
}
|
||||
case metrics.Meter:
|
||||
ms := m.Snapshot()
|
||||
measurement[Name] = name
|
||||
measurement[Value] = float64(ms.Count())
|
||||
snapshot.Counters = append(snapshot.Counters, measurement)
|
||||
snapshot.Gauges = append(snapshot.Gauges,
|
||||
Measurement{
|
||||
Name: fmt.Sprintf("%s.%s", name, "1min"),
|
||||
Value: ms.Rate1(),
|
||||
Period: int64(rep.Interval.Seconds()),
|
||||
Attributes: map[string]interface{}{
|
||||
DisplayUnitsLong: Operations,
|
||||
DisplayUnitsShort: OperationsShort,
|
||||
DisplayMin: "0",
|
||||
},
|
||||
},
|
||||
Measurement{
|
||||
Name: fmt.Sprintf("%s.%s", name, "5min"),
|
||||
Value: ms.Rate5(),
|
||||
Period: int64(rep.Interval.Seconds()),
|
||||
Attributes: map[string]interface{}{
|
||||
DisplayUnitsLong: Operations,
|
||||
DisplayUnitsShort: OperationsShort,
|
||||
DisplayMin: "0",
|
||||
},
|
||||
},
|
||||
Measurement{
|
||||
Name: fmt.Sprintf("%s.%s", name, "15min"),
|
||||
Value: ms.Rate15(),
|
||||
Period: int64(rep.Interval.Seconds()),
|
||||
Attributes: map[string]interface{}{
|
||||
DisplayUnitsLong: Operations,
|
||||
DisplayUnitsShort: OperationsShort,
|
||||
DisplayMin: "0",
|
||||
},
|
||||
},
|
||||
)
|
||||
case metrics.Timer:
|
||||
ms := m.Snapshot()
|
||||
measurement[Name] = name
|
||||
measurement[Value] = float64(ms.Count())
|
||||
snapshot.Counters = append(snapshot.Counters, measurement)
|
||||
if ms.Count() > 0 {
|
||||
libratoName := fmt.Sprintf("%s.%s", name, "timer.mean")
|
||||
gauges := make([]Measurement, histogramGaugeCount)
|
||||
gauges[0] = Measurement{
|
||||
Name: libratoName,
|
||||
Count: uint64(ms.Count()),
|
||||
Sum: ms.Mean() * float64(ms.Count()),
|
||||
Max: float64(ms.Max()),
|
||||
Min: float64(ms.Min()),
|
||||
SumSquares: sumSquaresTimer(ms),
|
||||
Period: int64(rep.Interval.Seconds()),
|
||||
Attributes: rep.TimerAttributes,
|
||||
}
|
||||
for i, p := range rep.Percentiles {
|
||||
gauges[i+1] = Measurement{
|
||||
Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100),
|
||||
Value: ms.Percentile(p),
|
||||
Period: int64(rep.Interval.Seconds()),
|
||||
Attributes: rep.TimerAttributes,
|
||||
}
|
||||
}
|
||||
snapshot.Gauges = append(snapshot.Gauges, gauges...)
|
||||
snapshot.Gauges = append(snapshot.Gauges,
|
||||
Measurement{
|
||||
Name: fmt.Sprintf("%s.%s", name, "rate.1min"),
|
||||
Value: ms.Rate1(),
|
||||
Period: int64(rep.Interval.Seconds()),
|
||||
Attributes: map[string]interface{}{
|
||||
DisplayUnitsLong: Operations,
|
||||
DisplayUnitsShort: OperationsShort,
|
||||
DisplayMin: "0",
|
||||
},
|
||||
},
|
||||
Measurement{
|
||||
Name: fmt.Sprintf("%s.%s", name, "rate.5min"),
|
||||
Value: ms.Rate5(),
|
||||
Period: int64(rep.Interval.Seconds()),
|
||||
Attributes: map[string]interface{}{
|
||||
DisplayUnitsLong: Operations,
|
||||
DisplayUnitsShort: OperationsShort,
|
||||
DisplayMin: "0",
|
||||
},
|
||||
},
|
||||
Measurement{
|
||||
Name: fmt.Sprintf("%s.%s", name, "rate.15min"),
|
||||
Value: ms.Rate15(),
|
||||
Period: int64(rep.Interval.Seconds()),
|
||||
Attributes: map[string]interface{}{
|
||||
DisplayUnitsLong: Operations,
|
||||
DisplayUnitsShort: OperationsShort,
|
||||
DisplayMin: "0",
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Printf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
func Log(r Registry, freq time.Duration, l Logger) {
|
||||
LogScaled(r, freq, time.Nanosecond, l)
|
||||
}
|
||||
|
||||
// Output each metric in the given registry periodically using the given
|
||||
// logger. Print timings in `scale` units (eg time.Millisecond) rather than nanos.
|
||||
func LogScaled(r Registry, freq time.Duration, scale time.Duration, l Logger) {
|
||||
du := float64(scale)
|
||||
duSuffix := scale.String()[1:]
|
||||
|
||||
for range time.Tick(freq) {
|
||||
r.Each(func(name string, i interface{}) {
|
||||
switch metric := i.(type) {
|
||||
case Counter:
|
||||
l.Printf("counter %s\n", name)
|
||||
l.Printf(" count: %9d\n", metric.Snapshot().Count())
|
||||
case CounterFloat64:
|
||||
l.Printf("counter %s\n", name)
|
||||
l.Printf(" count: %f\n", metric.Snapshot().Count())
|
||||
case Gauge:
|
||||
l.Printf("gauge %s\n", name)
|
||||
l.Printf(" value: %9d\n", metric.Snapshot().Value())
|
||||
case GaugeFloat64:
|
||||
l.Printf("gauge %s\n", name)
|
||||
l.Printf(" value: %f\n", metric.Snapshot().Value())
|
||||
case GaugeInfo:
|
||||
l.Printf("gauge %s\n", name)
|
||||
l.Printf(" value: %s\n", metric.Snapshot().Value())
|
||||
case Healthcheck:
|
||||
metric.Check()
|
||||
l.Printf("healthcheck %s\n", name)
|
||||
l.Printf(" error: %v\n", metric.Error())
|
||||
case Histogram:
|
||||
h := metric.Snapshot()
|
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
l.Printf("histogram %s\n", name)
|
||||
l.Printf(" count: %9d\n", h.Count())
|
||||
l.Printf(" min: %9d\n", h.Min())
|
||||
l.Printf(" max: %9d\n", h.Max())
|
||||
l.Printf(" mean: %12.2f\n", h.Mean())
|
||||
l.Printf(" stddev: %12.2f\n", h.StdDev())
|
||||
l.Printf(" median: %12.2f\n", ps[0])
|
||||
l.Printf(" 75%%: %12.2f\n", ps[1])
|
||||
l.Printf(" 95%%: %12.2f\n", ps[2])
|
||||
l.Printf(" 99%%: %12.2f\n", ps[3])
|
||||
l.Printf(" 99.9%%: %12.2f\n", ps[4])
|
||||
case Meter:
|
||||
m := metric.Snapshot()
|
||||
l.Printf("meter %s\n", name)
|
||||
l.Printf(" count: %9d\n", m.Count())
|
||||
l.Printf(" 1-min rate: %12.2f\n", m.Rate1())
|
||||
l.Printf(" 5-min rate: %12.2f\n", m.Rate5())
|
||||
l.Printf(" 15-min rate: %12.2f\n", m.Rate15())
|
||||
l.Printf(" mean rate: %12.2f\n", m.RateMean())
|
||||
case Timer:
|
||||
t := metric.Snapshot()
|
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
l.Printf("timer %s\n", name)
|
||||
l.Printf(" count: %9d\n", t.Count())
|
||||
l.Printf(" min: %12.2f%s\n", float64(t.Min())/du, duSuffix)
|
||||
l.Printf(" max: %12.2f%s\n", float64(t.Max())/du, duSuffix)
|
||||
l.Printf(" mean: %12.2f%s\n", t.Mean()/du, duSuffix)
|
||||
l.Printf(" stddev: %12.2f%s\n", t.StdDev()/du, duSuffix)
|
||||
l.Printf(" median: %12.2f%s\n", ps[0]/du, duSuffix)
|
||||
l.Printf(" 75%%: %12.2f%s\n", ps[1]/du, duSuffix)
|
||||
l.Printf(" 95%%: %12.2f%s\n", ps[2]/du, duSuffix)
|
||||
l.Printf(" 99%%: %12.2f%s\n", ps[3]/du, duSuffix)
|
||||
l.Printf(" 99.9%%: %12.2f%s\n", ps[4]/du, duSuffix)
|
||||
l.Printf(" 1-min rate: %12.2f\n", t.Rate1())
|
||||
l.Printf(" 5-min rate: %12.2f\n", t.Rate5())
|
||||
l.Printf(" 15-min rate: %12.2f\n", t.Rate15())
|
||||
l.Printf(" mean rate: %12.2f\n", t.RateMean())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,285 +0,0 @@
|
|||
Memory usage
|
||||
============
|
||||
|
||||
(Highly unscientific.)
|
||||
|
||||
Command used to gather static memory usage:
|
||||
|
||||
```sh
|
||||
grep ^Vm "/proc/$(ps fax | grep [m]etrics-bench | awk '{print $1}')/status"
|
||||
```
|
||||
|
||||
Program used to gather baseline memory usage:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
func main() {
|
||||
time.Sleep(600e9)
|
||||
}
|
||||
```
|
||||
|
||||
Baseline
|
||||
--------
|
||||
|
||||
```
|
||||
VmPeak: 42604 kB
|
||||
VmSize: 42604 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 1120 kB
|
||||
VmRSS: 1120 kB
|
||||
VmData: 35460 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1020 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 36 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
Program used to gather metric memory usage (with other metrics being similar):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"metrics"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Sprintf("foo")
|
||||
metrics.NewRegistry()
|
||||
time.Sleep(600e9)
|
||||
}
|
||||
```
|
||||
|
||||
1000 counters registered
|
||||
------------------------
|
||||
|
||||
```
|
||||
VmPeak: 44016 kB
|
||||
VmSize: 44016 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 1928 kB
|
||||
VmRSS: 1928 kB
|
||||
VmData: 36868 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1024 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 40 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**1.412 kB virtual, TODO 0.808 kB resident per counter.**
|
||||
|
||||
100000 counters registered
|
||||
--------------------------
|
||||
|
||||
```
|
||||
VmPeak: 55024 kB
|
||||
VmSize: 55024 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 12440 kB
|
||||
VmRSS: 12440 kB
|
||||
VmData: 47876 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1024 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 64 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**0.1242 kB virtual, 0.1132 kB resident per counter.**
|
||||
|
||||
1000 gauges registered
|
||||
----------------------
|
||||
|
||||
```
|
||||
VmPeak: 44012 kB
|
||||
VmSize: 44012 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 1928 kB
|
||||
VmRSS: 1928 kB
|
||||
VmData: 36868 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1020 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 40 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**1.408 kB virtual, 0.808 kB resident per counter.**
|
||||
|
||||
100000 gauges registered
|
||||
------------------------
|
||||
|
||||
```
|
||||
VmPeak: 55020 kB
|
||||
VmSize: 55020 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 12432 kB
|
||||
VmRSS: 12432 kB
|
||||
VmData: 47876 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1020 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 60 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**0.12416 kB virtual, 0.11312 resident per gauge.**
|
||||
|
||||
1000 histograms with a uniform sample size of 1028
|
||||
--------------------------------------------------
|
||||
|
||||
```
|
||||
VmPeak: 72272 kB
|
||||
VmSize: 72272 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 16204 kB
|
||||
VmRSS: 16204 kB
|
||||
VmData: 65100 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1048 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 80 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**29.668 kB virtual, TODO 15.084 resident per histogram.**
|
||||
|
||||
10000 histograms with a uniform sample size of 1028
|
||||
---------------------------------------------------
|
||||
|
||||
```
|
||||
VmPeak: 256912 kB
|
||||
VmSize: 256912 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 146204 kB
|
||||
VmRSS: 146204 kB
|
||||
VmData: 249740 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1048 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 448 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**21.4308 kB virtual, 14.5084 kB resident per histogram.**
|
||||
|
||||
50000 histograms with a uniform sample size of 1028
|
||||
---------------------------------------------------
|
||||
|
||||
```
|
||||
VmPeak: 908112 kB
|
||||
VmSize: 908112 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 645832 kB
|
||||
VmRSS: 645588 kB
|
||||
VmData: 900940 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1048 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 1716 kB
|
||||
VmSwap: 1544 kB
|
||||
```
|
||||
|
||||
**17.31016 kB virtual, 12.88936 kB resident per histogram.**
|
||||
|
||||
1000 histograms with an exponentially-decaying sample size of 1028 and alpha of 0.015
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
```
|
||||
VmPeak: 62480 kB
|
||||
VmSize: 62480 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 11572 kB
|
||||
VmRSS: 11572 kB
|
||||
VmData: 55308 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1048 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 64 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**19.876 kB virtual, 10.452 kB resident per histogram.**
|
||||
|
||||
10000 histograms with an exponentially-decaying sample size of 1028 and alpha of 0.015
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
```
|
||||
VmPeak: 153296 kB
|
||||
VmSize: 153296 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 101176 kB
|
||||
VmRSS: 101176 kB
|
||||
VmData: 146124 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1048 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 240 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**11.0692 kB virtual, 10.0056 kB resident per histogram.**
|
||||
|
||||
50000 histograms with an exponentially-decaying sample size of 1028 and alpha of 0.015
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
```
|
||||
VmPeak: 557264 kB
|
||||
VmSize: 557264 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 501056 kB
|
||||
VmRSS: 501056 kB
|
||||
VmData: 550092 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1048 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 1032 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**10.2932 kB virtual, 9.99872 kB resident per histogram.**
|
||||
|
||||
1000 meters
|
||||
-----------
|
||||
|
||||
```
|
||||
VmPeak: 74504 kB
|
||||
VmSize: 74504 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 24124 kB
|
||||
VmRSS: 24124 kB
|
||||
VmData: 67340 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1040 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 92 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**31.9 kB virtual, 23.004 kB resident per meter.**
|
||||
|
||||
10000 meters
|
||||
------------
|
||||
|
||||
```
|
||||
VmPeak: 278920 kB
|
||||
VmSize: 278920 kB
|
||||
VmLck: 0 kB
|
||||
VmHWM: 227300 kB
|
||||
VmRSS: 227300 kB
|
||||
VmData: 271756 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 1040 kB
|
||||
VmLib: 1848 kB
|
||||
VmPTE: 488 kB
|
||||
VmSwap: 0 kB
|
||||
```
|
||||
|
||||
**23.6316 kB virtual, 22.618 kB resident per meter.**
|
||||
189
metrics/meter.go
189
metrics/meter.go
|
|
@ -1,189 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MeterSnapshot interface {
|
||||
Count() int64
|
||||
Rate1() float64
|
||||
Rate5() float64
|
||||
Rate15() float64
|
||||
RateMean() float64
|
||||
}
|
||||
|
||||
// Meters count events to produce exponentially-weighted moving average rates
|
||||
// at one-, five-, and fifteen-minutes and a mean rate.
|
||||
type Meter interface {
|
||||
Mark(int64)
|
||||
Snapshot() MeterSnapshot
|
||||
Stop()
|
||||
}
|
||||
|
||||
// GetOrRegisterMeter returns an existing Meter or constructs and registers a
|
||||
// new StandardMeter.
|
||||
// Be sure to unregister the meter from the registry once it is of no use to
|
||||
// allow for garbage collection.
|
||||
func GetOrRegisterMeter(name string, r Registry) Meter {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewMeter).(Meter)
|
||||
}
|
||||
|
||||
// NewMeter constructs a new StandardMeter and launches a goroutine.
|
||||
// Be sure to call Stop() once the meter is of no use to allow for garbage collection.
|
||||
func NewMeter() Meter {
|
||||
if !Enabled {
|
||||
return NilMeter{}
|
||||
}
|
||||
m := newStandardMeter()
|
||||
arbiter.Lock()
|
||||
defer arbiter.Unlock()
|
||||
arbiter.meters[m] = struct{}{}
|
||||
if !arbiter.started {
|
||||
arbiter.started = true
|
||||
go arbiter.tick()
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewInactiveMeter returns a meter but does not start any goroutines. This
|
||||
// method is mainly intended for testing.
|
||||
func NewInactiveMeter() Meter {
|
||||
if !Enabled {
|
||||
return NilMeter{}
|
||||
}
|
||||
m := newStandardMeter()
|
||||
return m
|
||||
}
|
||||
|
||||
// NewRegisteredMeter constructs and registers a new StandardMeter
|
||||
// and launches a goroutine.
|
||||
// Be sure to unregister the meter from the registry once it is of no use to
|
||||
// allow for garbage collection.
|
||||
func NewRegisteredMeter(name string, r Registry) Meter {
|
||||
return GetOrRegisterMeter(name, r)
|
||||
}
|
||||
|
||||
// meterSnapshot is a read-only copy of the meter's internal values.
|
||||
type meterSnapshot struct {
|
||||
count int64
|
||||
rate1, rate5, rate15, rateMean float64
|
||||
}
|
||||
|
||||
// Count returns the count of events at the time the snapshot was taken.
|
||||
func (m *meterSnapshot) Count() int64 { return m.count }
|
||||
|
||||
// Rate1 returns the one-minute moving average rate of events per second at the
|
||||
// time the snapshot was taken.
|
||||
func (m *meterSnapshot) Rate1() float64 { return m.rate1 }
|
||||
|
||||
// Rate5 returns the five-minute moving average rate of events per second at
|
||||
// the time the snapshot was taken.
|
||||
func (m *meterSnapshot) Rate5() float64 { return m.rate5 }
|
||||
|
||||
// Rate15 returns the fifteen-minute moving average rate of events per second
|
||||
// at the time the snapshot was taken.
|
||||
func (m *meterSnapshot) Rate15() float64 { return m.rate15 }
|
||||
|
||||
// RateMean returns the meter's mean rate of events per second at the time the
|
||||
// snapshot was taken.
|
||||
func (m *meterSnapshot) RateMean() float64 { return m.rateMean }
|
||||
|
||||
// NilMeter is a no-op Meter.
|
||||
type NilMeter struct{}
|
||||
|
||||
func (NilMeter) Count() int64 { return 0 }
|
||||
func (NilMeter) Mark(n int64) {}
|
||||
func (NilMeter) Snapshot() MeterSnapshot { return (*emptySnapshot)(nil) }
|
||||
func (NilMeter) Stop() {}
|
||||
|
||||
// StandardMeter is the standard implementation of a Meter.
|
||||
type StandardMeter struct {
|
||||
count atomic.Int64
|
||||
uncounted atomic.Int64 // not yet added to the EWMAs
|
||||
rateMean atomic.Uint64
|
||||
|
||||
a1, a5, a15 EWMA
|
||||
startTime time.Time
|
||||
stopped atomic.Bool
|
||||
}
|
||||
|
||||
func newStandardMeter() *StandardMeter {
|
||||
return &StandardMeter{
|
||||
a1: NewEWMA1(),
|
||||
a5: NewEWMA5(),
|
||||
a15: NewEWMA15(),
|
||||
startTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the meter, Mark() will be a no-op if you use it after being stopped.
|
||||
func (m *StandardMeter) Stop() {
|
||||
if stopped := m.stopped.Swap(true); !stopped {
|
||||
arbiter.Lock()
|
||||
delete(arbiter.meters, m)
|
||||
arbiter.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Mark records the occurrence of n events.
|
||||
func (m *StandardMeter) Mark(n int64) {
|
||||
m.uncounted.Add(n)
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the meter.
|
||||
func (m *StandardMeter) Snapshot() MeterSnapshot {
|
||||
return &meterSnapshot{
|
||||
count: m.count.Load() + m.uncounted.Load(),
|
||||
rate1: m.a1.Snapshot().Rate(),
|
||||
rate5: m.a5.Snapshot().Rate(),
|
||||
rate15: m.a15.Snapshot().Rate(),
|
||||
rateMean: math.Float64frombits(m.rateMean.Load()),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *StandardMeter) tick() {
|
||||
// Take the uncounted values, add to count
|
||||
n := m.uncounted.Swap(0)
|
||||
count := m.count.Add(n)
|
||||
m.rateMean.Store(math.Float64bits(float64(count) / time.Since(m.startTime).Seconds()))
|
||||
// Update the EWMA's internal state
|
||||
m.a1.Update(n)
|
||||
m.a5.Update(n)
|
||||
m.a15.Update(n)
|
||||
// And trigger them to calculate the rates
|
||||
m.a1.Tick()
|
||||
m.a5.Tick()
|
||||
m.a15.Tick()
|
||||
}
|
||||
|
||||
// meterArbiter ticks meters every 5s from a single goroutine.
|
||||
// meters are references in a set for future stopping.
|
||||
type meterArbiter struct {
|
||||
sync.RWMutex
|
||||
started bool
|
||||
meters map[*StandardMeter]struct{}
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
var arbiter = meterArbiter{ticker: time.NewTicker(5 * time.Second), meters: make(map[*StandardMeter]struct{})}
|
||||
|
||||
// Ticks meters on the scheduled interval
|
||||
func (ma *meterArbiter) tick() {
|
||||
for range ma.ticker.C {
|
||||
ma.tickMeters()
|
||||
}
|
||||
}
|
||||
|
||||
func (ma *meterArbiter) tickMeters() {
|
||||
ma.RLock()
|
||||
defer ma.RUnlock()
|
||||
for meter := range ma.meters {
|
||||
meter.tick()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkMeter(b *testing.B) {
|
||||
m := NewMeter()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Mark(1)
|
||||
}
|
||||
}
|
||||
func TestMeter(t *testing.T) {
|
||||
m := NewMeter()
|
||||
m.Mark(47)
|
||||
if v := m.Snapshot().Count(); v != 47 {
|
||||
t.Fatalf("have %d want %d", v, 47)
|
||||
}
|
||||
}
|
||||
func TestGetOrRegisterMeter(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
NewRegisteredMeter("foo", r).Mark(47)
|
||||
if m := GetOrRegisterMeter("foo", r).Snapshot(); m.Count() != 47 {
|
||||
t.Fatal(m.Count())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeterDecay(t *testing.T) {
|
||||
ma := meterArbiter{
|
||||
ticker: time.NewTicker(time.Millisecond),
|
||||
meters: make(map[*StandardMeter]struct{}),
|
||||
}
|
||||
defer ma.ticker.Stop()
|
||||
m := newStandardMeter()
|
||||
ma.meters[m] = struct{}{}
|
||||
m.Mark(1)
|
||||
ma.tickMeters()
|
||||
rateMean := m.Snapshot().RateMean()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
ma.tickMeters()
|
||||
if m.Snapshot().RateMean() >= rateMean {
|
||||
t.Error("m.RateMean() didn't decrease")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeterNonzero(t *testing.T) {
|
||||
m := NewMeter()
|
||||
m.Mark(3)
|
||||
if count := m.Snapshot().Count(); count != 3 {
|
||||
t.Errorf("m.Count(): 3 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeterStop(t *testing.T) {
|
||||
l := len(arbiter.meters)
|
||||
m := NewMeter()
|
||||
if l+1 != len(arbiter.meters) {
|
||||
t.Errorf("arbiter.meters: %d != %d\n", l+1, len(arbiter.meters))
|
||||
}
|
||||
m.Stop()
|
||||
if l != len(arbiter.meters) {
|
||||
t.Errorf("arbiter.meters: %d != %d\n", l, len(arbiter.meters))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeterZero(t *testing.T) {
|
||||
m := NewMeter().Snapshot()
|
||||
if count := m.Count(); count != 0 {
|
||||
t.Errorf("m.Count(): 0 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeterRepeat(t *testing.T) {
|
||||
m := NewMeter()
|
||||
for i := 0; i < 101; i++ {
|
||||
m.Mark(int64(i))
|
||||
}
|
||||
if count := m.Snapshot().Count(); count != 5050 {
|
||||
t.Errorf("m.Count(): 5050 != %v\n", count)
|
||||
}
|
||||
for i := 0; i < 101; i++ {
|
||||
m.Mark(int64(i))
|
||||
}
|
||||
if count := m.Snapshot().Count(); count != 10100 {
|
||||
t.Errorf("m.Count(): 10100 != %v\n", count)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
// Go port of Coda Hale's Metrics library
|
||||
//
|
||||
// <https://github.com/rcrowley/go-metrics>
|
||||
//
|
||||
// Coda Hale's original work: <https://github.com/codahale/metrics>
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/metrics"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Enabled is checked by the constructor functions for all of the
|
||||
// standard metrics. If it is true, the metric returned is a stub.
|
||||
//
|
||||
// This global kill-switch helps quantify the observer effect and makes
|
||||
// for less cluttered pprof profiles.
|
||||
var Enabled = false
|
||||
|
||||
// EnabledExpensive is a soft-flag meant for external packages to check if costly
|
||||
// metrics gathering is allowed or not. The goal is to separate standard metrics
|
||||
// for health monitoring and debug metrics that might impact runtime performance.
|
||||
var EnabledExpensive = false
|
||||
|
||||
// enablerFlags is the CLI flag names to use to enable metrics collections.
|
||||
var enablerFlags = []string{"metrics"}
|
||||
|
||||
// enablerEnvVars is the env var names to use to enable metrics collections.
|
||||
var enablerEnvVars = []string{"GETH_METRICS"}
|
||||
|
||||
// expensiveEnablerFlags is the CLI flag names to use to enable metrics collections.
|
||||
var expensiveEnablerFlags = []string{"metrics.expensive"}
|
||||
|
||||
// expensiveEnablerEnvVars is the env var names to use to enable metrics collections.
|
||||
var expensiveEnablerEnvVars = []string{"GETH_METRICS_EXPENSIVE"}
|
||||
|
||||
// Init enables or disables the metrics system. Since we need this to run before
|
||||
// any other code gets to create meters and timers, we'll actually do an ugly hack
|
||||
// and peek into the command line args for the metrics flag.
|
||||
func init() {
|
||||
for _, enabler := range enablerEnvVars {
|
||||
if val, found := syscall.Getenv(enabler); found && !Enabled {
|
||||
if enable, _ := strconv.ParseBool(val); enable { // ignore error, flag parser will choke on it later
|
||||
log.Info("Enabling metrics collection")
|
||||
Enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, enabler := range expensiveEnablerEnvVars {
|
||||
if val, found := syscall.Getenv(enabler); found && !EnabledExpensive {
|
||||
if enable, _ := strconv.ParseBool(val); enable { // ignore error, flag parser will choke on it later
|
||||
log.Info("Enabling expensive metrics collection")
|
||||
EnabledExpensive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, arg := range os.Args {
|
||||
flag := strings.TrimLeft(arg, "-")
|
||||
|
||||
for _, enabler := range enablerFlags {
|
||||
if !Enabled && flag == enabler {
|
||||
log.Info("Enabling metrics collection")
|
||||
Enabled = true
|
||||
}
|
||||
}
|
||||
for _, enabler := range expensiveEnablerFlags {
|
||||
if !EnabledExpensive && flag == enabler {
|
||||
log.Info("Enabling expensive metrics collection")
|
||||
EnabledExpensive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var threadCreateProfile = pprof.Lookup("threadcreate")
|
||||
|
||||
type runtimeStats struct {
|
||||
GCPauses *metrics.Float64Histogram
|
||||
GCAllocBytes uint64
|
||||
GCFreedBytes uint64
|
||||
|
||||
MemTotal uint64
|
||||
HeapObjects uint64
|
||||
HeapFree uint64
|
||||
HeapReleased uint64
|
||||
HeapUnused uint64
|
||||
|
||||
Goroutines uint64
|
||||
SchedLatency *metrics.Float64Histogram
|
||||
}
|
||||
|
||||
var runtimeSamples = []metrics.Sample{
|
||||
{Name: "/gc/pauses:seconds"}, // histogram
|
||||
{Name: "/gc/heap/allocs:bytes"},
|
||||
{Name: "/gc/heap/frees:bytes"},
|
||||
{Name: "/memory/classes/total:bytes"},
|
||||
{Name: "/memory/classes/heap/objects:bytes"},
|
||||
{Name: "/memory/classes/heap/free:bytes"},
|
||||
{Name: "/memory/classes/heap/released:bytes"},
|
||||
{Name: "/memory/classes/heap/unused:bytes"},
|
||||
{Name: "/sched/goroutines:goroutines"},
|
||||
{Name: "/sched/latencies:seconds"}, // histogram
|
||||
}
|
||||
|
||||
func ReadRuntimeStats() *runtimeStats {
|
||||
r := new(runtimeStats)
|
||||
readRuntimeStats(r)
|
||||
return r
|
||||
}
|
||||
|
||||
func readRuntimeStats(v *runtimeStats) {
|
||||
metrics.Read(runtimeSamples)
|
||||
for _, s := range runtimeSamples {
|
||||
// Skip invalid/unknown metrics. This is needed because some metrics
|
||||
// are unavailable in older Go versions, and attempting to read a 'bad'
|
||||
// metric panics.
|
||||
if s.Value.Kind() == metrics.KindBad {
|
||||
continue
|
||||
}
|
||||
|
||||
switch s.Name {
|
||||
case "/gc/pauses:seconds":
|
||||
v.GCPauses = s.Value.Float64Histogram()
|
||||
case "/gc/heap/allocs:bytes":
|
||||
v.GCAllocBytes = s.Value.Uint64()
|
||||
case "/gc/heap/frees:bytes":
|
||||
v.GCFreedBytes = s.Value.Uint64()
|
||||
case "/memory/classes/total:bytes":
|
||||
v.MemTotal = s.Value.Uint64()
|
||||
case "/memory/classes/heap/objects:bytes":
|
||||
v.HeapObjects = s.Value.Uint64()
|
||||
case "/memory/classes/heap/free:bytes":
|
||||
v.HeapFree = s.Value.Uint64()
|
||||
case "/memory/classes/heap/released:bytes":
|
||||
v.HeapReleased = s.Value.Uint64()
|
||||
case "/memory/classes/heap/unused:bytes":
|
||||
v.HeapUnused = s.Value.Uint64()
|
||||
case "/sched/goroutines:goroutines":
|
||||
v.Goroutines = s.Value.Uint64()
|
||||
case "/sched/latencies:seconds":
|
||||
v.SchedLatency = s.Value.Float64Histogram()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CollectProcessMetrics periodically collects various metrics about the running process.
|
||||
func CollectProcessMetrics(refresh time.Duration) {
|
||||
// Short circuit if the metrics system is disabled
|
||||
if !Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
// Create the various data collectors
|
||||
var (
|
||||
cpustats = make([]CPUStats, 2)
|
||||
diskstats = make([]DiskStats, 2)
|
||||
rstats = make([]runtimeStats, 2)
|
||||
)
|
||||
|
||||
// This scale factor is used for the runtime's time metrics. It's useful to convert to
|
||||
// ns here because the runtime gives times in float seconds, but runtimeHistogram can
|
||||
// only provide integers for the minimum and maximum values.
|
||||
const secondsToNs = float64(time.Second)
|
||||
|
||||
// Define the various metrics to collect
|
||||
var (
|
||||
cpuSysLoad = GetOrRegisterGauge("system/cpu/sysload", DefaultRegistry)
|
||||
cpuSysWait = GetOrRegisterGauge("system/cpu/syswait", DefaultRegistry)
|
||||
cpuProcLoad = GetOrRegisterGauge("system/cpu/procload", DefaultRegistry)
|
||||
cpuSysLoadTotal = GetOrRegisterCounterFloat64("system/cpu/sysload/total", DefaultRegistry)
|
||||
cpuSysWaitTotal = GetOrRegisterCounterFloat64("system/cpu/syswait/total", DefaultRegistry)
|
||||
cpuProcLoadTotal = GetOrRegisterCounterFloat64("system/cpu/procload/total", DefaultRegistry)
|
||||
cpuThreads = GetOrRegisterGauge("system/cpu/threads", DefaultRegistry)
|
||||
cpuGoroutines = GetOrRegisterGauge("system/cpu/goroutines", DefaultRegistry)
|
||||
cpuSchedLatency = getOrRegisterRuntimeHistogram("system/cpu/schedlatency", secondsToNs, nil)
|
||||
memPauses = getOrRegisterRuntimeHistogram("system/memory/pauses", secondsToNs, nil)
|
||||
memAllocs = GetOrRegisterMeter("system/memory/allocs", DefaultRegistry)
|
||||
memFrees = GetOrRegisterMeter("system/memory/frees", DefaultRegistry)
|
||||
memTotal = GetOrRegisterGauge("system/memory/held", DefaultRegistry)
|
||||
heapUsed = GetOrRegisterGauge("system/memory/used", DefaultRegistry)
|
||||
heapObjects = GetOrRegisterGauge("system/memory/objects", DefaultRegistry)
|
||||
diskReads = GetOrRegisterMeter("system/disk/readcount", DefaultRegistry)
|
||||
diskReadBytes = GetOrRegisterMeter("system/disk/readdata", DefaultRegistry)
|
||||
diskReadBytesCounter = GetOrRegisterCounter("system/disk/readbytes", DefaultRegistry)
|
||||
diskWrites = GetOrRegisterMeter("system/disk/writecount", DefaultRegistry)
|
||||
diskWriteBytes = GetOrRegisterMeter("system/disk/writedata", DefaultRegistry)
|
||||
diskWriteBytesCounter = GetOrRegisterCounter("system/disk/writebytes", DefaultRegistry)
|
||||
)
|
||||
|
||||
var lastCollectTime time.Time
|
||||
|
||||
// Iterate loading the different stats and updating the meters.
|
||||
now, prev := 0, 1
|
||||
for ; ; now, prev = prev, now {
|
||||
// Gather CPU times.
|
||||
ReadCPUStats(&cpustats[now])
|
||||
collectTime := time.Now()
|
||||
secondsSinceLastCollect := collectTime.Sub(lastCollectTime).Seconds()
|
||||
lastCollectTime = collectTime
|
||||
if secondsSinceLastCollect > 0 {
|
||||
sysLoad := cpustats[now].GlobalTime - cpustats[prev].GlobalTime
|
||||
sysWait := cpustats[now].GlobalWait - cpustats[prev].GlobalWait
|
||||
procLoad := cpustats[now].LocalTime - cpustats[prev].LocalTime
|
||||
// Convert to integer percentage.
|
||||
cpuSysLoad.Update(int64(sysLoad / secondsSinceLastCollect * 100))
|
||||
cpuSysWait.Update(int64(sysWait / secondsSinceLastCollect * 100))
|
||||
cpuProcLoad.Update(int64(procLoad / secondsSinceLastCollect * 100))
|
||||
// increment counters (ms)
|
||||
cpuSysLoadTotal.Inc(sysLoad)
|
||||
cpuSysWaitTotal.Inc(sysWait)
|
||||
cpuProcLoadTotal.Inc(procLoad)
|
||||
}
|
||||
|
||||
// Threads
|
||||
cpuThreads.Update(int64(threadCreateProfile.Count()))
|
||||
|
||||
// Go runtime metrics
|
||||
readRuntimeStats(&rstats[now])
|
||||
|
||||
cpuGoroutines.Update(int64(rstats[now].Goroutines))
|
||||
cpuSchedLatency.update(rstats[now].SchedLatency)
|
||||
memPauses.update(rstats[now].GCPauses)
|
||||
|
||||
memAllocs.Mark(int64(rstats[now].GCAllocBytes - rstats[prev].GCAllocBytes))
|
||||
memFrees.Mark(int64(rstats[now].GCFreedBytes - rstats[prev].GCFreedBytes))
|
||||
|
||||
memTotal.Update(int64(rstats[now].MemTotal))
|
||||
heapUsed.Update(int64(rstats[now].MemTotal - rstats[now].HeapUnused - rstats[now].HeapFree - rstats[now].HeapReleased))
|
||||
heapObjects.Update(int64(rstats[now].HeapObjects))
|
||||
|
||||
// Disk
|
||||
if ReadDiskStats(&diskstats[now]) == nil {
|
||||
diskReads.Mark(diskstats[now].ReadCount - diskstats[prev].ReadCount)
|
||||
diskReadBytes.Mark(diskstats[now].ReadBytes - diskstats[prev].ReadBytes)
|
||||
diskWrites.Mark(diskstats[now].WriteCount - diskstats[prev].WriteCount)
|
||||
diskWriteBytes.Mark(diskstats[now].WriteBytes - diskstats[prev].WriteBytes)
|
||||
diskReadBytesCounter.Inc(diskstats[now].ReadBytes - diskstats[prev].ReadBytes)
|
||||
diskWriteBytesCounter.Inc(diskstats[now].WriteBytes - diskstats[prev].WriteBytes)
|
||||
}
|
||||
|
||||
time.Sleep(refresh)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const FANOUT = 128
|
||||
|
||||
func TestReadRuntimeValues(t *testing.T) {
|
||||
var v runtimeStats
|
||||
readRuntimeStats(&v)
|
||||
t.Logf("%+v", v)
|
||||
}
|
||||
|
||||
func BenchmarkMetrics(b *testing.B) {
|
||||
r := NewRegistry()
|
||||
c := NewRegisteredCounter("counter", r)
|
||||
cf := NewRegisteredCounterFloat64("counterfloat64", r)
|
||||
g := NewRegisteredGauge("gauge", r)
|
||||
gf := NewRegisteredGaugeFloat64("gaugefloat64", r)
|
||||
h := NewRegisteredHistogram("histogram", r, NewUniformSample(100))
|
||||
m := NewRegisteredMeter("meter", r)
|
||||
t := NewRegisteredTimer("timer", r)
|
||||
RegisterDebugGCStats(r)
|
||||
b.ResetTimer()
|
||||
ch := make(chan bool)
|
||||
|
||||
wgD := &sync.WaitGroup{}
|
||||
/*
|
||||
wgD.Add(1)
|
||||
go func() {
|
||||
defer wgD.Done()
|
||||
//log.Println("go CaptureDebugGCStats")
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
//log.Println("done CaptureDebugGCStats")
|
||||
return
|
||||
default:
|
||||
CaptureDebugGCStatsOnce(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
//*/
|
||||
|
||||
wgW := &sync.WaitGroup{}
|
||||
/*
|
||||
wgW.Add(1)
|
||||
go func() {
|
||||
defer wgW.Done()
|
||||
//log.Println("go Write")
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
//log.Println("done Write")
|
||||
return
|
||||
default:
|
||||
WriteOnce(r, io.Discard)
|
||||
}
|
||||
}
|
||||
}()
|
||||
//*/
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(FANOUT)
|
||||
for i := 0; i < FANOUT; i++ {
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
//log.Println("go", i)
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.Inc(1)
|
||||
cf.Inc(1.0)
|
||||
g.Update(int64(i))
|
||||
gf.Update(float64(i))
|
||||
h.Update(int64(i))
|
||||
m.Mark(1)
|
||||
t.Update(1)
|
||||
}
|
||||
//log.Println("done", i)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
wgD.Wait()
|
||||
wgW.Wait()
|
||||
}
|
||||
|
||||
func Example() {
|
||||
c := NewCounter()
|
||||
Register("money", c)
|
||||
c.Inc(17)
|
||||
|
||||
// Threadsafe registration
|
||||
t := GetOrRegisterTimer("db.get.latency", nil)
|
||||
t.Time(func() { time.Sleep(10 * time.Millisecond) })
|
||||
t.Update(1)
|
||||
|
||||
fmt.Println(c.Snapshot().Count())
|
||||
fmt.Println(t.Snapshot().Min())
|
||||
// Output: 17
|
||||
// 1
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var shortHostName = ""
|
||||
|
||||
// OpenTSDBConfig provides a container with configuration parameters for
|
||||
// the OpenTSDB exporter
|
||||
type OpenTSDBConfig struct {
|
||||
Addr *net.TCPAddr // Network address to connect to
|
||||
Registry Registry // Registry to be exported
|
||||
FlushInterval time.Duration // Flush interval
|
||||
DurationUnit time.Duration // Time conversion unit for durations
|
||||
Prefix string // Prefix to be prepended to metric names
|
||||
}
|
||||
|
||||
// OpenTSDB is a blocking exporter function which reports metrics in r
|
||||
// to a TSDB server located at addr, flushing them every d duration
|
||||
// and prepending metric names with prefix.
|
||||
func OpenTSDB(r Registry, d time.Duration, prefix string, addr *net.TCPAddr) {
|
||||
OpenTSDBWithConfig(OpenTSDBConfig{
|
||||
Addr: addr,
|
||||
Registry: r,
|
||||
FlushInterval: d,
|
||||
DurationUnit: time.Nanosecond,
|
||||
Prefix: prefix,
|
||||
})
|
||||
}
|
||||
|
||||
// OpenTSDBWithConfig is a blocking exporter function just like OpenTSDB,
|
||||
// but it takes a OpenTSDBConfig instead.
|
||||
func OpenTSDBWithConfig(c OpenTSDBConfig) {
|
||||
for range time.Tick(c.FlushInterval) {
|
||||
if err := openTSDB(&c); nil != err {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getShortHostname() string {
|
||||
if shortHostName == "" {
|
||||
host, _ := os.Hostname()
|
||||
if index := strings.Index(host, "."); index > 0 {
|
||||
shortHostName = host[:index]
|
||||
} else {
|
||||
shortHostName = host
|
||||
}
|
||||
}
|
||||
return shortHostName
|
||||
}
|
||||
|
||||
// writeRegistry writes the registry-metrics on the opentsb format.
|
||||
func (c *OpenTSDBConfig) writeRegistry(w io.Writer, now int64, shortHostname string) {
|
||||
du := float64(c.DurationUnit)
|
||||
|
||||
c.Registry.Each(func(name string, i interface{}) {
|
||||
switch metric := i.(type) {
|
||||
case Counter:
|
||||
fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, metric.Snapshot().Count(), shortHostname)
|
||||
case CounterFloat64:
|
||||
fmt.Fprintf(w, "put %s.%s.count %d %f host=%s\n", c.Prefix, name, now, metric.Snapshot().Count(), shortHostname)
|
||||
case Gauge:
|
||||
fmt.Fprintf(w, "put %s.%s.value %d %d host=%s\n", c.Prefix, name, now, metric.Snapshot().Value(), shortHostname)
|
||||
case GaugeFloat64:
|
||||
fmt.Fprintf(w, "put %s.%s.value %d %f host=%s\n", c.Prefix, name, now, metric.Snapshot().Value(), shortHostname)
|
||||
case GaugeInfo:
|
||||
fmt.Fprintf(w, "put %s.%s.value %d %s host=%s\n", c.Prefix, name, now, metric.Snapshot().Value().String(), shortHostname)
|
||||
case Histogram:
|
||||
h := metric.Snapshot()
|
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, h.Count(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.min %d %d host=%s\n", c.Prefix, name, now, h.Min(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.max %d %d host=%s\n", c.Prefix, name, now, h.Max(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.mean %d %.2f host=%s\n", c.Prefix, name, now, h.Mean(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.std-dev %d %.2f host=%s\n", c.Prefix, name, now, h.StdDev(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.50-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[0], shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.75-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[1], shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.95-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[2], shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.99-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[3], shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.999-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[4], shortHostname)
|
||||
case Meter:
|
||||
m := metric.Snapshot()
|
||||
fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, m.Count(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.one-minute %d %.2f host=%s\n", c.Prefix, name, now, m.Rate1(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.five-minute %d %.2f host=%s\n", c.Prefix, name, now, m.Rate5(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.fifteen-minute %d %.2f host=%s\n", c.Prefix, name, now, m.Rate15(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.mean %d %.2f host=%s\n", c.Prefix, name, now, m.RateMean(), shortHostname)
|
||||
case Timer:
|
||||
t := metric.Snapshot()
|
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, t.Count(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.min %d %d host=%s\n", c.Prefix, name, now, t.Min()/int64(du), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.max %d %d host=%s\n", c.Prefix, name, now, t.Max()/int64(du), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.mean %d %.2f host=%s\n", c.Prefix, name, now, t.Mean()/du, shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.std-dev %d %.2f host=%s\n", c.Prefix, name, now, t.StdDev()/du, shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.50-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[0]/du, shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.75-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[1]/du, shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.95-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[2]/du, shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.99-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[3]/du, shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.999-percentile %d %.2f host=%s\n", c.Prefix, name, now, ps[4]/du, shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.one-minute %d %.2f host=%s\n", c.Prefix, name, now, t.Rate1(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.five-minute %d %.2f host=%s\n", c.Prefix, name, now, t.Rate5(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.fifteen-minute %d %.2f host=%s\n", c.Prefix, name, now, t.Rate15(), shortHostname)
|
||||
fmt.Fprintf(w, "put %s.%s.mean-rate %d %.2f host=%s\n", c.Prefix, name, now, t.RateMean(), shortHostname)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func openTSDB(c *OpenTSDBConfig) error {
|
||||
conn, err := net.DialTCP("tcp", nil, c.Addr)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
w := bufio.NewWriter(conn)
|
||||
c.writeRegistry(w, time.Now().Unix(), getShortHostname())
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleOpenTSDB() {
|
||||
addr, _ := net.ResolveTCPAddr("net", ":2003")
|
||||
go OpenTSDB(DefaultRegistry, 1*time.Second, "some.prefix", addr)
|
||||
}
|
||||
|
||||
func ExampleOpenTSDBWithConfig() {
|
||||
addr, _ := net.ResolveTCPAddr("net", ":2003")
|
||||
go OpenTSDBWithConfig(OpenTSDBConfig{
|
||||
Addr: addr,
|
||||
Registry: DefaultRegistry,
|
||||
FlushInterval: 1 * time.Second,
|
||||
DurationUnit: time.Millisecond,
|
||||
})
|
||||
}
|
||||
|
||||
func TestExampleOpenTSB(t *testing.T) {
|
||||
r := NewOrderedRegistry()
|
||||
NewRegisteredGaugeInfo("foo", r).Update(GaugeInfoValue{"chain_id": "5"})
|
||||
NewRegisteredGaugeFloat64("pi", r).Update(3.14)
|
||||
NewRegisteredCounter("months", r).Inc(12)
|
||||
NewRegisteredCounterFloat64("tau", r).Inc(1.57)
|
||||
NewRegisteredMeter("elite", r).Mark(1337)
|
||||
NewRegisteredTimer("second", r).Update(time.Second)
|
||||
NewRegisteredCounterFloat64("tau", r).Inc(1.57)
|
||||
NewRegisteredCounterFloat64("tau", r).Inc(1.57)
|
||||
|
||||
w := new(strings.Builder)
|
||||
(&OpenTSDBConfig{
|
||||
Registry: r,
|
||||
DurationUnit: time.Millisecond,
|
||||
Prefix: "pre",
|
||||
}).writeRegistry(w, 978307200, "hal9000")
|
||||
|
||||
wantB, err := os.ReadFile("./testdata/opentsb.want")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if have, want := w.String(), string(wantB); have != want {
|
||||
t.Errorf("\nhave:\n%v\nwant:\n%v\n", have, want)
|
||||
t.Logf("have vs want:\n%v", findFirstDiffPos(have, want))
|
||||
}
|
||||
}
|
||||
|
||||
func findFirstDiffPos(a, b string) string {
|
||||
yy := strings.Split(b, "\n")
|
||||
for i, x := range strings.Split(a, "\n") {
|
||||
if i >= len(yy) {
|
||||
return fmt.Sprintf("have:%d: %s\nwant:%d: <EOF>", i, x, i)
|
||||
}
|
||||
if y := yy[i]; x != y {
|
||||
return fmt.Sprintf("have:%d: %s\nwant:%d: %s", i, x, i, y)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
typeGaugeTpl = "# TYPE %s gauge\n"
|
||||
typeCounterTpl = "# TYPE %s counter\n"
|
||||
typeSummaryTpl = "# TYPE %s summary\n"
|
||||
keyValueTpl = "%s %v\n\n"
|
||||
keyQuantileTagValueTpl = "%s {quantile=\"%s\"} %v\n"
|
||||
)
|
||||
|
||||
// collector is a collection of byte buffers that aggregate Prometheus reports
|
||||
// for different metric types.
|
||||
type collector struct {
|
||||
buff *bytes.Buffer
|
||||
}
|
||||
|
||||
// newCollector creates a new Prometheus metric aggregator.
|
||||
func newCollector() *collector {
|
||||
return &collector{
|
||||
buff: &bytes.Buffer{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds the metric i to the collector. This method returns an error if the
|
||||
// metric type is not supported/known.
|
||||
func (c *collector) Add(name string, i any) error {
|
||||
switch m := i.(type) {
|
||||
case metrics.Counter:
|
||||
c.addCounter(name, m.Snapshot())
|
||||
case metrics.CounterFloat64:
|
||||
c.addCounterFloat64(name, m.Snapshot())
|
||||
case metrics.Gauge:
|
||||
c.addGauge(name, m.Snapshot())
|
||||
case metrics.GaugeFloat64:
|
||||
c.addGaugeFloat64(name, m.Snapshot())
|
||||
case metrics.GaugeInfo:
|
||||
c.addGaugeInfo(name, m.Snapshot())
|
||||
case metrics.Histogram:
|
||||
c.addHistogram(name, m.Snapshot())
|
||||
case metrics.Meter:
|
||||
c.addMeter(name, m.Snapshot())
|
||||
case metrics.Timer:
|
||||
c.addTimer(name, m.Snapshot())
|
||||
case metrics.ResettingTimer:
|
||||
c.addResettingTimer(name, m.Snapshot())
|
||||
default:
|
||||
return fmt.Errorf("unknown prometheus metric type %T", i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *collector) addCounter(name string, m metrics.CounterSnapshot) {
|
||||
c.writeGaugeCounter(name, m.Count())
|
||||
}
|
||||
|
||||
func (c *collector) addCounterFloat64(name string, m metrics.CounterFloat64Snapshot) {
|
||||
c.writeGaugeCounter(name, m.Count())
|
||||
}
|
||||
|
||||
func (c *collector) addGauge(name string, m metrics.GaugeSnapshot) {
|
||||
c.writeGaugeCounter(name, m.Value())
|
||||
}
|
||||
|
||||
func (c *collector) addGaugeFloat64(name string, m metrics.GaugeFloat64Snapshot) {
|
||||
c.writeGaugeCounter(name, m.Value())
|
||||
}
|
||||
|
||||
func (c *collector) addGaugeInfo(name string, m metrics.GaugeInfoSnapshot) {
|
||||
c.writeGaugeInfo(name, m.Value())
|
||||
}
|
||||
|
||||
func (c *collector) addHistogram(name string, m metrics.HistogramSnapshot) {
|
||||
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
|
||||
ps := m.Percentiles(pv)
|
||||
c.writeSummaryCounter(name, m.Count())
|
||||
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
|
||||
for i := range pv {
|
||||
c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
|
||||
}
|
||||
c.buff.WriteRune('\n')
|
||||
}
|
||||
|
||||
func (c *collector) addMeter(name string, m metrics.MeterSnapshot) {
|
||||
c.writeGaugeCounter(name, m.Count())
|
||||
}
|
||||
|
||||
func (c *collector) addTimer(name string, m metrics.TimerSnapshot) {
|
||||
pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
|
||||
ps := m.Percentiles(pv)
|
||||
c.writeSummaryCounter(name, m.Count())
|
||||
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
|
||||
for i := range pv {
|
||||
c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
|
||||
}
|
||||
c.buff.WriteRune('\n')
|
||||
}
|
||||
|
||||
func (c *collector) addResettingTimer(name string, m metrics.ResettingTimerSnapshot) {
|
||||
if m.Count() <= 0 {
|
||||
return
|
||||
}
|
||||
ps := m.Percentiles([]float64{0.50, 0.95, 0.99})
|
||||
c.writeSummaryCounter(name, m.Count())
|
||||
c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
|
||||
c.writeSummaryPercentile(name, "0.50", ps[0])
|
||||
c.writeSummaryPercentile(name, "0.95", ps[1])
|
||||
c.writeSummaryPercentile(name, "0.99", ps[2])
|
||||
c.buff.WriteRune('\n')
|
||||
}
|
||||
|
||||
func (c *collector) writeGaugeInfo(name string, value metrics.GaugeInfoValue) {
|
||||
name = mutateKey(name)
|
||||
c.buff.WriteString(fmt.Sprintf(typeGaugeTpl, name))
|
||||
c.buff.WriteString(name)
|
||||
c.buff.WriteString(" ")
|
||||
var kvs []string
|
||||
for k, v := range value {
|
||||
kvs = append(kvs, fmt.Sprintf("%v=%q", k, v))
|
||||
}
|
||||
sort.Strings(kvs)
|
||||
c.buff.WriteString(fmt.Sprintf("{%v} 1\n\n", strings.Join(kvs, ", ")))
|
||||
}
|
||||
|
||||
func (c *collector) writeGaugeCounter(name string, value interface{}) {
|
||||
name = mutateKey(name)
|
||||
c.buff.WriteString(fmt.Sprintf(typeGaugeTpl, name))
|
||||
c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value))
|
||||
}
|
||||
|
||||
func (c *collector) writeSummaryCounter(name string, value interface{}) {
|
||||
name = mutateKey(name + "_count")
|
||||
c.buff.WriteString(fmt.Sprintf(typeCounterTpl, name))
|
||||
c.buff.WriteString(fmt.Sprintf(keyValueTpl, name, value))
|
||||
}
|
||||
|
||||
func (c *collector) writeSummaryPercentile(name, p string, value interface{}) {
|
||||
name = mutateKey(name)
|
||||
c.buff.WriteString(fmt.Sprintf(keyQuantileTagValueTpl, name, p, value))
|
||||
}
|
||||
|
||||
func mutateKey(key string) string {
|
||||
return strings.ReplaceAll(key, "/", "_")
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/metrics/internal"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
metrics.Enabled = true
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
var (
|
||||
c = newCollector()
|
||||
want string
|
||||
)
|
||||
internal.ExampleMetrics().Each(func(name string, i interface{}) {
|
||||
c.Add(name, i)
|
||||
})
|
||||
if wantB, err := os.ReadFile("./testdata/prometheus.want"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
want = string(wantB)
|
||||
}
|
||||
if have := c.buff.String(); have != want {
|
||||
t.Logf("have\n%v", have)
|
||||
t.Logf("have vs want:\n%v", findFirstDiffPos(have, want))
|
||||
t.Fatalf("unexpected collector output")
|
||||
}
|
||||
}
|
||||
|
||||
func findFirstDiffPos(a, b string) string {
|
||||
yy := strings.Split(b, "\n")
|
||||
for i, x := range strings.Split(a, "\n") {
|
||||
if i >= len(yy) {
|
||||
return fmt.Sprintf("have:%d: %s\nwant:%d: <EOF>", i, x, i)
|
||||
}
|
||||
if y := yy[i]; x != y {
|
||||
return fmt.Sprintf("have:%d: %s\nwant:%d: %s", i, x, i, y)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package prometheus exposes go-metrics into a Prometheus format.
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
// Handler returns an HTTP handler which dump metrics in Prometheus format.
|
||||
func Handler(reg metrics.Registry) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Gather and pre-sort the metrics to avoid random listings
|
||||
var names []string
|
||||
reg.Each(func(name string, i interface{}) {
|
||||
names = append(names, name)
|
||||
})
|
||||
sort.Strings(names)
|
||||
|
||||
// Aggregate all the metrics into a Prometheus collector
|
||||
c := newCollector()
|
||||
|
||||
for _, name := range names {
|
||||
i := reg.Get(name)
|
||||
if err := c.Add(name, i); err != nil {
|
||||
log.Warn("Unknown Prometheus metric type", "type", fmt.Sprintf("%T", i))
|
||||
}
|
||||
}
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
w.Header().Add("Content-Length", fmt.Sprint(c.buff.Len()))
|
||||
w.Write(c.buff.Bytes())
|
||||
})
|
||||
}
|
||||
70
metrics/prometheus/testdata/prometheus.want
vendored
70
metrics/prometheus/testdata/prometheus.want
vendored
|
|
@ -1,70 +0,0 @@
|
|||
# TYPE system_cpu_schedlatency_count counter
|
||||
system_cpu_schedlatency_count 5645
|
||||
|
||||
# TYPE system_cpu_schedlatency summary
|
||||
system_cpu_schedlatency {quantile="0.5"} 0
|
||||
system_cpu_schedlatency {quantile="0.75"} 7168
|
||||
system_cpu_schedlatency {quantile="0.95"} 1.6777216e+07
|
||||
system_cpu_schedlatency {quantile="0.99"} 2.9360128e+07
|
||||
system_cpu_schedlatency {quantile="0.999"} 3.3554432e+07
|
||||
system_cpu_schedlatency {quantile="0.9999"} 3.3554432e+07
|
||||
|
||||
# TYPE system_memory_pauses_count counter
|
||||
system_memory_pauses_count 14
|
||||
|
||||
# TYPE system_memory_pauses summary
|
||||
system_memory_pauses {quantile="0.5"} 32768
|
||||
system_memory_pauses {quantile="0.75"} 57344
|
||||
system_memory_pauses {quantile="0.95"} 196608
|
||||
system_memory_pauses {quantile="0.99"} 196608
|
||||
system_memory_pauses {quantile="0.999"} 196608
|
||||
system_memory_pauses {quantile="0.9999"} 196608
|
||||
|
||||
# TYPE test_counter gauge
|
||||
test_counter 12345
|
||||
|
||||
# TYPE test_counter_float64 gauge
|
||||
test_counter_float64 54321.98
|
||||
|
||||
# TYPE test_gauge gauge
|
||||
test_gauge 23456
|
||||
|
||||
# TYPE test_gauge_float64 gauge
|
||||
test_gauge_float64 34567.89
|
||||
|
||||
# TYPE test_gauge_info gauge
|
||||
test_gauge_info {arch="amd64", commit="7caa2d8163ae3132c1c2d6978c76610caee2d949", os="linux", protocol_versions="64 65 66", version="1.10.18-unstable"} 1
|
||||
|
||||
# TYPE test_histogram_count counter
|
||||
test_histogram_count 3
|
||||
|
||||
# TYPE test_histogram summary
|
||||
test_histogram {quantile="0.5"} 2
|
||||
test_histogram {quantile="0.75"} 3
|
||||
test_histogram {quantile="0.95"} 3
|
||||
test_histogram {quantile="0.99"} 3
|
||||
test_histogram {quantile="0.999"} 3
|
||||
test_histogram {quantile="0.9999"} 3
|
||||
|
||||
# TYPE test_meter gauge
|
||||
test_meter 0
|
||||
|
||||
# TYPE test_resetting_timer_count counter
|
||||
test_resetting_timer_count 6
|
||||
|
||||
# TYPE test_resetting_timer summary
|
||||
test_resetting_timer {quantile="0.50"} 1.25e+07
|
||||
test_resetting_timer {quantile="0.95"} 1.2e+08
|
||||
test_resetting_timer {quantile="0.99"} 1.2e+08
|
||||
|
||||
# TYPE test_timer_count counter
|
||||
test_timer_count 6
|
||||
|
||||
# TYPE test_timer summary
|
||||
test_timer {quantile="0.5"} 2.25e+07
|
||||
test_timer {quantile="0.75"} 4.8e+07
|
||||
test_timer {quantile="0.95"} 1.2e+08
|
||||
test_timer {quantile="0.99"} 1.2e+08
|
||||
test_timer {quantile="0.999"} 1.2e+08
|
||||
test_timer {quantile="0.9999"} 1.2e+08
|
||||
|
||||
|
|
@ -1,372 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DuplicateMetric is the error returned by Registry.Register when a metric
|
||||
// already exists. If you mean to Register that metric you must first
|
||||
// Unregister the existing metric.
|
||||
type DuplicateMetric string
|
||||
|
||||
func (err DuplicateMetric) Error() string {
|
||||
return fmt.Sprintf("duplicate metric: %s", string(err))
|
||||
}
|
||||
|
||||
// A Registry holds references to a set of metrics by name and can iterate
|
||||
// over them, calling callback functions provided by the user.
|
||||
//
|
||||
// This is an interface so as to encourage other structs to implement
|
||||
// the Registry API as appropriate.
|
||||
type Registry interface {
|
||||
|
||||
// Call the given function for each registered metric.
|
||||
Each(func(string, interface{}))
|
||||
|
||||
// Get the metric by the given name or nil if none is registered.
|
||||
Get(string) interface{}
|
||||
|
||||
// GetAll metrics in the Registry.
|
||||
GetAll() map[string]map[string]interface{}
|
||||
|
||||
// Gets an existing metric or registers the given one.
|
||||
// The interface can be the metric to register if not found in registry,
|
||||
// or a function returning the metric for lazy instantiation.
|
||||
GetOrRegister(string, interface{}) interface{}
|
||||
|
||||
// Register the given metric under the given name.
|
||||
Register(string, interface{}) error
|
||||
|
||||
// Run all registered healthchecks.
|
||||
RunHealthchecks()
|
||||
|
||||
// Unregister the metric with the given name.
|
||||
Unregister(string)
|
||||
}
|
||||
|
||||
type orderedRegistry struct {
|
||||
StandardRegistry
|
||||
}
|
||||
|
||||
// Call the given function for each registered metric.
|
||||
func (r *orderedRegistry) Each(f func(string, interface{})) {
|
||||
var names []string
|
||||
reg := r.registered()
|
||||
for name := range reg {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
f(name, reg[name])
|
||||
}
|
||||
}
|
||||
|
||||
// NewRegistry creates a new registry.
|
||||
func NewRegistry() Registry {
|
||||
return new(StandardRegistry)
|
||||
}
|
||||
|
||||
// NewOrderedRegistry creates a new ordered registry (for testing).
|
||||
func NewOrderedRegistry() Registry {
|
||||
return new(orderedRegistry)
|
||||
}
|
||||
|
||||
// The standard implementation of a Registry uses sync.map
|
||||
// of names to metrics.
|
||||
type StandardRegistry struct {
|
||||
metrics sync.Map
|
||||
}
|
||||
|
||||
// Call the given function for each registered metric.
|
||||
func (r *StandardRegistry) Each(f func(string, interface{})) {
|
||||
for name, i := range r.registered() {
|
||||
f(name, i)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the metric by the given name or nil if none is registered.
|
||||
func (r *StandardRegistry) Get(name string) interface{} {
|
||||
item, _ := r.metrics.Load(name)
|
||||
return item
|
||||
}
|
||||
|
||||
// Gets an existing metric or creates and registers a new one. Threadsafe
|
||||
// alternative to calling Get and Register on failure.
|
||||
// The interface can be the metric to register if not found in registry,
|
||||
// or a function returning the metric for lazy instantiation.
|
||||
func (r *StandardRegistry) GetOrRegister(name string, i interface{}) interface{} {
|
||||
// fast path
|
||||
cached, ok := r.metrics.Load(name)
|
||||
if ok {
|
||||
return cached
|
||||
}
|
||||
if v := reflect.ValueOf(i); v.Kind() == reflect.Func {
|
||||
i = v.Call(nil)[0].Interface()
|
||||
}
|
||||
item, _, ok := r.loadOrRegister(name, i)
|
||||
if !ok {
|
||||
return i
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
// Register the given metric under the given name. Returns a DuplicateMetric
|
||||
// if a metric by the given name is already registered.
|
||||
func (r *StandardRegistry) Register(name string, i interface{}) error {
|
||||
// fast path
|
||||
_, ok := r.metrics.Load(name)
|
||||
if ok {
|
||||
return DuplicateMetric(name)
|
||||
}
|
||||
|
||||
if v := reflect.ValueOf(i); v.Kind() == reflect.Func {
|
||||
i = v.Call(nil)[0].Interface()
|
||||
}
|
||||
_, loaded, _ := r.loadOrRegister(name, i)
|
||||
if loaded {
|
||||
return DuplicateMetric(name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run all registered healthchecks.
|
||||
func (r *StandardRegistry) RunHealthchecks() {
|
||||
r.metrics.Range(func(key, value any) bool {
|
||||
if h, ok := value.(Healthcheck); ok {
|
||||
h.Check()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// GetAll metrics in the Registry
|
||||
func (r *StandardRegistry) GetAll() map[string]map[string]interface{} {
|
||||
data := make(map[string]map[string]interface{})
|
||||
r.Each(func(name string, i interface{}) {
|
||||
values := make(map[string]interface{})
|
||||
switch metric := i.(type) {
|
||||
case Counter:
|
||||
values["count"] = metric.Snapshot().Count()
|
||||
case CounterFloat64:
|
||||
values["count"] = metric.Snapshot().Count()
|
||||
case Gauge:
|
||||
values["value"] = metric.Snapshot().Value()
|
||||
case GaugeFloat64:
|
||||
values["value"] = metric.Snapshot().Value()
|
||||
case Healthcheck:
|
||||
values["error"] = nil
|
||||
metric.Check()
|
||||
if err := metric.Error(); nil != err {
|
||||
values["error"] = metric.Error().Error()
|
||||
}
|
||||
case Histogram:
|
||||
h := metric.Snapshot()
|
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
values["count"] = h.Count()
|
||||
values["min"] = h.Min()
|
||||
values["max"] = h.Max()
|
||||
values["mean"] = h.Mean()
|
||||
values["stddev"] = h.StdDev()
|
||||
values["median"] = ps[0]
|
||||
values["75%"] = ps[1]
|
||||
values["95%"] = ps[2]
|
||||
values["99%"] = ps[3]
|
||||
values["99.9%"] = ps[4]
|
||||
case Meter:
|
||||
m := metric.Snapshot()
|
||||
values["count"] = m.Count()
|
||||
values["1m.rate"] = m.Rate1()
|
||||
values["5m.rate"] = m.Rate5()
|
||||
values["15m.rate"] = m.Rate15()
|
||||
values["mean.rate"] = m.RateMean()
|
||||
case Timer:
|
||||
t := metric.Snapshot()
|
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
values["count"] = t.Count()
|
||||
values["min"] = t.Min()
|
||||
values["max"] = t.Max()
|
||||
values["mean"] = t.Mean()
|
||||
values["stddev"] = t.StdDev()
|
||||
values["median"] = ps[0]
|
||||
values["75%"] = ps[1]
|
||||
values["95%"] = ps[2]
|
||||
values["99%"] = ps[3]
|
||||
values["99.9%"] = ps[4]
|
||||
values["1m.rate"] = t.Rate1()
|
||||
values["5m.rate"] = t.Rate5()
|
||||
values["15m.rate"] = t.Rate15()
|
||||
values["mean.rate"] = t.RateMean()
|
||||
}
|
||||
data[name] = values
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
// Unregister the metric with the given name.
|
||||
func (r *StandardRegistry) Unregister(name string) {
|
||||
r.stop(name)
|
||||
r.metrics.LoadAndDelete(name)
|
||||
}
|
||||
|
||||
func (r *StandardRegistry) loadOrRegister(name string, i interface{}) (interface{}, bool, bool) {
|
||||
switch i.(type) {
|
||||
case Counter, CounterFloat64, Gauge, GaugeFloat64, GaugeInfo, Healthcheck, Histogram, Meter, Timer, ResettingTimer:
|
||||
default:
|
||||
return nil, false, false
|
||||
}
|
||||
item, loaded := r.metrics.LoadOrStore(name, i)
|
||||
return item, loaded, true
|
||||
}
|
||||
|
||||
func (r *StandardRegistry) registered() map[string]interface{} {
|
||||
metrics := make(map[string]interface{})
|
||||
r.metrics.Range(func(key, value any) bool {
|
||||
metrics[key.(string)] = value
|
||||
return true
|
||||
})
|
||||
return metrics
|
||||
}
|
||||
|
||||
func (r *StandardRegistry) stop(name string) {
|
||||
if i, ok := r.metrics.Load(name); ok {
|
||||
if s, ok := i.(Stoppable); ok {
|
||||
s.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stoppable defines the metrics which has to be stopped.
|
||||
type Stoppable interface {
|
||||
Stop()
|
||||
}
|
||||
|
||||
type PrefixedRegistry struct {
|
||||
underlying Registry
|
||||
prefix string
|
||||
}
|
||||
|
||||
func NewPrefixedRegistry(prefix string) Registry {
|
||||
return &PrefixedRegistry{
|
||||
underlying: NewRegistry(),
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
func NewPrefixedChildRegistry(parent Registry, prefix string) Registry {
|
||||
return &PrefixedRegistry{
|
||||
underlying: parent,
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
// Call the given function for each registered metric.
|
||||
func (r *PrefixedRegistry) Each(fn func(string, interface{})) {
|
||||
wrappedFn := func(prefix string) func(string, interface{}) {
|
||||
return func(name string, iface interface{}) {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
fn(name, iface)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseRegistry, prefix := findPrefix(r, "")
|
||||
baseRegistry.Each(wrappedFn(prefix))
|
||||
}
|
||||
|
||||
func findPrefix(registry Registry, prefix string) (Registry, string) {
|
||||
switch r := registry.(type) {
|
||||
case *PrefixedRegistry:
|
||||
return findPrefix(r.underlying, r.prefix+prefix)
|
||||
case *StandardRegistry:
|
||||
return r, prefix
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// Get the metric by the given name or nil if none is registered.
|
||||
func (r *PrefixedRegistry) Get(name string) interface{} {
|
||||
realName := r.prefix + name
|
||||
return r.underlying.Get(realName)
|
||||
}
|
||||
|
||||
// Gets an existing metric or registers the given one.
|
||||
// The interface can be the metric to register if not found in registry,
|
||||
// or a function returning the metric for lazy instantiation.
|
||||
func (r *PrefixedRegistry) GetOrRegister(name string, metric interface{}) interface{} {
|
||||
realName := r.prefix + name
|
||||
return r.underlying.GetOrRegister(realName, metric)
|
||||
}
|
||||
|
||||
// Register the given metric under the given name. The name will be prefixed.
|
||||
func (r *PrefixedRegistry) Register(name string, metric interface{}) error {
|
||||
realName := r.prefix + name
|
||||
return r.underlying.Register(realName, metric)
|
||||
}
|
||||
|
||||
// Run all registered healthchecks.
|
||||
func (r *PrefixedRegistry) RunHealthchecks() {
|
||||
r.underlying.RunHealthchecks()
|
||||
}
|
||||
|
||||
// GetAll metrics in the Registry
|
||||
func (r *PrefixedRegistry) GetAll() map[string]map[string]interface{} {
|
||||
return r.underlying.GetAll()
|
||||
}
|
||||
|
||||
// Unregister the metric with the given name. The name will be prefixed.
|
||||
func (r *PrefixedRegistry) Unregister(name string) {
|
||||
realName := r.prefix + name
|
||||
r.underlying.Unregister(realName)
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultRegistry = NewRegistry()
|
||||
EphemeralRegistry = NewRegistry()
|
||||
AccountingRegistry = NewRegistry() // registry used in swarm
|
||||
)
|
||||
|
||||
// Call the given function for each registered metric.
|
||||
func Each(f func(string, interface{})) {
|
||||
DefaultRegistry.Each(f)
|
||||
}
|
||||
|
||||
// Get the metric by the given name or nil if none is registered.
|
||||
func Get(name string) interface{} {
|
||||
return DefaultRegistry.Get(name)
|
||||
}
|
||||
|
||||
// Gets an existing metric or creates and registers a new one. Threadsafe
|
||||
// alternative to calling Get and Register on failure.
|
||||
func GetOrRegister(name string, i interface{}) interface{} {
|
||||
return DefaultRegistry.GetOrRegister(name, i)
|
||||
}
|
||||
|
||||
// Register the given metric under the given name. Returns a DuplicateMetric
|
||||
// if a metric by the given name is already registered.
|
||||
func Register(name string, i interface{}) error {
|
||||
return DefaultRegistry.Register(name, i)
|
||||
}
|
||||
|
||||
// Register the given metric under the given name. Panics if a metric by the
|
||||
// given name is already registered.
|
||||
func MustRegister(name string, i interface{}) {
|
||||
if err := Register(name, i); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Run all registered healthchecks.
|
||||
func RunHealthchecks() {
|
||||
DefaultRegistry.RunHealthchecks()
|
||||
}
|
||||
|
||||
// Unregister the metric with the given name.
|
||||
func Unregister(name string) {
|
||||
DefaultRegistry.Unregister(name)
|
||||
}
|
||||
|
|
@ -1,335 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkRegistry(b *testing.B) {
|
||||
r := NewRegistry()
|
||||
r.Register("foo", NewCounter())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Each(func(string, interface{}) {})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRegistryGetOrRegisterParallel_8(b *testing.B) {
|
||||
benchmarkRegistryGetOrRegisterParallel(b, 8)
|
||||
}
|
||||
|
||||
func BenchmarkRegistryGetOrRegisterParallel_32(b *testing.B) {
|
||||
benchmarkRegistryGetOrRegisterParallel(b, 32)
|
||||
}
|
||||
|
||||
func benchmarkRegistryGetOrRegisterParallel(b *testing.B, amount int) {
|
||||
r := NewRegistry()
|
||||
b.ResetTimer()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < amount; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.GetOrRegister("foo", NewMeter)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestRegistry(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
r.Register("foo", NewCounter())
|
||||
i := 0
|
||||
r.Each(func(name string, iface interface{}) {
|
||||
i++
|
||||
if name != "foo" {
|
||||
t.Fatal(name)
|
||||
}
|
||||
if _, ok := iface.(Counter); !ok {
|
||||
t.Fatal(iface)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
r.Unregister("foo")
|
||||
i = 0
|
||||
r.Each(func(string, interface{}) { i++ })
|
||||
if i != 0 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryDuplicate(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
if err := r.Register("foo", NewCounter()); nil != err {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := r.Register("foo", NewGauge()); nil == err {
|
||||
t.Fatal(err)
|
||||
}
|
||||
i := 0
|
||||
r.Each(func(name string, iface interface{}) {
|
||||
i++
|
||||
if _, ok := iface.(Counter); !ok {
|
||||
t.Fatal(iface)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryGet(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
r.Register("foo", NewCounter())
|
||||
if count := r.Get("foo").(Counter).Snapshot().Count(); count != 0 {
|
||||
t.Fatal(count)
|
||||
}
|
||||
r.Get("foo").(Counter).Inc(1)
|
||||
if count := r.Get("foo").(Counter).Snapshot().Count(); count != 1 {
|
||||
t.Fatal(count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryGetOrRegister(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
|
||||
// First metric wins with GetOrRegister
|
||||
_ = r.GetOrRegister("foo", NewCounter())
|
||||
m := r.GetOrRegister("foo", NewGauge())
|
||||
if _, ok := m.(Counter); !ok {
|
||||
t.Fatal(m)
|
||||
}
|
||||
|
||||
i := 0
|
||||
r.Each(func(name string, iface interface{}) {
|
||||
i++
|
||||
if name != "foo" {
|
||||
t.Fatal(name)
|
||||
}
|
||||
if _, ok := iface.(Counter); !ok {
|
||||
t.Fatal(iface)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryGetOrRegisterWithLazyInstantiation(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
|
||||
// First metric wins with GetOrRegister
|
||||
_ = r.GetOrRegister("foo", NewCounter)
|
||||
m := r.GetOrRegister("foo", NewGauge)
|
||||
if _, ok := m.(Counter); !ok {
|
||||
t.Fatal(m)
|
||||
}
|
||||
|
||||
i := 0
|
||||
r.Each(func(name string, iface interface{}) {
|
||||
i++
|
||||
if name != "foo" {
|
||||
t.Fatal(name)
|
||||
}
|
||||
if _, ok := iface.(Counter); !ok {
|
||||
t.Fatal(iface)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryUnregister(t *testing.T) {
|
||||
l := len(arbiter.meters)
|
||||
r := NewRegistry()
|
||||
r.Register("foo", NewCounter())
|
||||
r.Register("bar", NewMeter())
|
||||
r.Register("baz", NewTimer())
|
||||
if len(arbiter.meters) != l+2 {
|
||||
t.Errorf("arbiter.meters: %d != %d\n", l+2, len(arbiter.meters))
|
||||
}
|
||||
r.Unregister("foo")
|
||||
r.Unregister("bar")
|
||||
r.Unregister("baz")
|
||||
if len(arbiter.meters) != l {
|
||||
t.Errorf("arbiter.meters: %d != %d\n", l+2, len(arbiter.meters))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixedChildRegistryGetOrRegister(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
pr := NewPrefixedChildRegistry(r, "prefix.")
|
||||
|
||||
_ = pr.GetOrRegister("foo", NewCounter())
|
||||
|
||||
i := 0
|
||||
r.Each(func(name string, m interface{}) {
|
||||
i++
|
||||
if name != "prefix.foo" {
|
||||
t.Fatal(name)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixedRegistryGetOrRegister(t *testing.T) {
|
||||
r := NewPrefixedRegistry("prefix.")
|
||||
|
||||
_ = r.GetOrRegister("foo", NewCounter())
|
||||
|
||||
i := 0
|
||||
r.Each(func(name string, m interface{}) {
|
||||
i++
|
||||
if name != "prefix.foo" {
|
||||
t.Fatal(name)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixedRegistryRegister(t *testing.T) {
|
||||
r := NewPrefixedRegistry("prefix.")
|
||||
err := r.Register("foo", NewCounter())
|
||||
c := NewCounter()
|
||||
Register("bar", c)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
i := 0
|
||||
r.Each(func(name string, m interface{}) {
|
||||
i++
|
||||
if name != "prefix.foo" {
|
||||
t.Fatal(name)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixedRegistryUnregister(t *testing.T) {
|
||||
r := NewPrefixedRegistry("prefix.")
|
||||
|
||||
_ = r.Register("foo", NewCounter())
|
||||
|
||||
i := 0
|
||||
r.Each(func(name string, m interface{}) {
|
||||
i++
|
||||
if name != "prefix.foo" {
|
||||
t.Fatal(name)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
|
||||
r.Unregister("foo")
|
||||
|
||||
i = 0
|
||||
r.Each(func(name string, m interface{}) {
|
||||
i++
|
||||
})
|
||||
|
||||
if i != 0 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixedRegistryGet(t *testing.T) {
|
||||
pr := NewPrefixedRegistry("prefix.")
|
||||
name := "foo"
|
||||
pr.Register(name, NewCounter())
|
||||
|
||||
fooCounter := pr.Get(name)
|
||||
if fooCounter == nil {
|
||||
t.Fatal(name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixedChildRegistryGet(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
pr := NewPrefixedChildRegistry(r, "prefix.")
|
||||
name := "foo"
|
||||
pr.Register(name, NewCounter())
|
||||
fooCounter := pr.Get(name)
|
||||
if fooCounter == nil {
|
||||
t.Fatal(name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChildPrefixedRegistryRegister(t *testing.T) {
|
||||
r := NewPrefixedChildRegistry(DefaultRegistry, "prefix.")
|
||||
err := r.Register("foo", NewCounter())
|
||||
c := NewCounter()
|
||||
Register("bar", c)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
i := 0
|
||||
r.Each(func(name string, m interface{}) {
|
||||
i++
|
||||
if name != "prefix.foo" {
|
||||
t.Fatal(name)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChildPrefixedRegistryOfChildRegister(t *testing.T) {
|
||||
r := NewPrefixedChildRegistry(NewRegistry(), "prefix.")
|
||||
r2 := NewPrefixedChildRegistry(r, "prefix2.")
|
||||
err := r.Register("foo2", NewCounter())
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err = r2.Register("baz", NewCounter())
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
c := NewCounter()
|
||||
Register("bars", c)
|
||||
|
||||
i := 0
|
||||
r2.Each(func(name string, m interface{}) {
|
||||
i++
|
||||
if name != "prefix.prefix2.baz" {
|
||||
t.Fatal(name)
|
||||
}
|
||||
})
|
||||
if i != 1 {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkRegistries(t *testing.T) {
|
||||
r := NewPrefixedChildRegistry(NewRegistry(), "prefix.")
|
||||
r2 := NewPrefixedChildRegistry(r, "prefix2.")
|
||||
err := r.Register("foo2", NewCounter())
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
err = r2.Register("baz", NewCounter())
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
c := NewCounter()
|
||||
Register("bars", c)
|
||||
|
||||
_, prefix := findPrefix(r2, "")
|
||||
if prefix != "prefix.prefix2." {
|
||||
t.Fatal(prefix)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package metrics
|
||||
|
||||
// ResettingSample converts an ordinary sample into one that resets whenever its
|
||||
// snapshot is retrieved. This will break for multi-monitor systems, but when only
|
||||
// a single metric is being pushed out, this ensure that low-frequency events don't
|
||||
// skew th charts indefinitely.
|
||||
func ResettingSample(sample Sample) Sample {
|
||||
return &resettingSample{
|
||||
Sample: sample,
|
||||
}
|
||||
}
|
||||
|
||||
// resettingSample is a simple wrapper around a sample that resets it upon the
|
||||
// snapshot retrieval.
|
||||
type resettingSample struct {
|
||||
Sample
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the sample with the original reset.
|
||||
func (rs *resettingSample) Snapshot() SampleSnapshot {
|
||||
s := rs.Sample.Snapshot()
|
||||
rs.Sample.Clear()
|
||||
return s
|
||||
}
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Initial slice capacity for the values stored in a ResettingTimer
|
||||
const InitialResettingTimerSliceCap = 10
|
||||
|
||||
type ResettingTimerSnapshot interface {
|
||||
Count() int
|
||||
Mean() float64
|
||||
Max() int64
|
||||
Min() int64
|
||||
Percentiles([]float64) []float64
|
||||
}
|
||||
|
||||
// ResettingTimer is used for storing aggregated values for timers, which are reset on every flush interval.
|
||||
type ResettingTimer interface {
|
||||
Snapshot() ResettingTimerSnapshot
|
||||
Time(func())
|
||||
Update(time.Duration)
|
||||
UpdateSince(time.Time)
|
||||
}
|
||||
|
||||
// GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a
|
||||
// new StandardResettingTimer.
|
||||
func GetOrRegisterResettingTimer(name string, r Registry) ResettingTimer {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewResettingTimer).(ResettingTimer)
|
||||
}
|
||||
|
||||
// NewRegisteredResettingTimer constructs and registers a new StandardResettingTimer.
|
||||
func NewRegisteredResettingTimer(name string, r Registry) ResettingTimer {
|
||||
c := NewResettingTimer()
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// NewResettingTimer constructs a new StandardResettingTimer
|
||||
func NewResettingTimer() ResettingTimer {
|
||||
if !Enabled {
|
||||
return NilResettingTimer{}
|
||||
}
|
||||
return &StandardResettingTimer{
|
||||
values: make([]int64, 0, InitialResettingTimerSliceCap),
|
||||
}
|
||||
}
|
||||
|
||||
// NilResettingTimer is a no-op ResettingTimer.
|
||||
type NilResettingTimer struct{}
|
||||
|
||||
func (NilResettingTimer) Values() []int64 { return nil }
|
||||
func (n NilResettingTimer) Snapshot() ResettingTimerSnapshot { return n }
|
||||
func (NilResettingTimer) Time(f func()) { f() }
|
||||
func (NilResettingTimer) Update(time.Duration) {}
|
||||
func (NilResettingTimer) Percentiles([]float64) []float64 { return nil }
|
||||
func (NilResettingTimer) Mean() float64 { return 0.0 }
|
||||
func (NilResettingTimer) Max() int64 { return 0 }
|
||||
func (NilResettingTimer) Min() int64 { return 0 }
|
||||
func (NilResettingTimer) UpdateSince(time.Time) {}
|
||||
func (NilResettingTimer) Count() int { return 0 }
|
||||
|
||||
// StandardResettingTimer is the standard implementation of a ResettingTimer.
|
||||
// and Meter.
|
||||
type StandardResettingTimer struct {
|
||||
values []int64
|
||||
sum int64 // sum is a running count of the total sum, used later to calculate mean
|
||||
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Snapshot resets the timer and returns a read-only copy of its contents.
|
||||
func (t *StandardResettingTimer) Snapshot() ResettingTimerSnapshot {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
snapshot := &resettingTimerSnapshot{}
|
||||
if len(t.values) > 0 {
|
||||
snapshot.mean = float64(t.sum) / float64(len(t.values))
|
||||
snapshot.values = t.values
|
||||
t.values = make([]int64, 0, InitialResettingTimerSliceCap)
|
||||
}
|
||||
t.sum = 0
|
||||
return snapshot
|
||||
}
|
||||
|
||||
// Record the duration of the execution of the given function.
|
||||
func (t *StandardResettingTimer) Time(f func()) {
|
||||
ts := time.Now()
|
||||
f()
|
||||
t.Update(time.Since(ts))
|
||||
}
|
||||
|
||||
// Record the duration of an event.
|
||||
func (t *StandardResettingTimer) Update(d time.Duration) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
t.values = append(t.values, int64(d))
|
||||
t.sum += int64(d)
|
||||
}
|
||||
|
||||
// Record the duration of an event that started at a time and ends now.
|
||||
func (t *StandardResettingTimer) UpdateSince(ts time.Time) {
|
||||
t.Update(time.Since(ts))
|
||||
}
|
||||
|
||||
// resettingTimerSnapshot is a point-in-time copy of another ResettingTimer.
|
||||
type resettingTimerSnapshot struct {
|
||||
values []int64
|
||||
mean float64
|
||||
max int64
|
||||
min int64
|
||||
thresholdBoundaries []float64
|
||||
calculated bool
|
||||
}
|
||||
|
||||
// Count return the length of the values from snapshot.
|
||||
func (t *resettingTimerSnapshot) Count() int {
|
||||
return len(t.values)
|
||||
}
|
||||
|
||||
// Percentiles returns the boundaries for the input percentiles.
|
||||
// note: this method is not thread safe
|
||||
func (t *resettingTimerSnapshot) Percentiles(percentiles []float64) []float64 {
|
||||
t.calc(percentiles)
|
||||
return t.thresholdBoundaries
|
||||
}
|
||||
|
||||
// Mean returns the mean of the snapshotted values
|
||||
// note: this method is not thread safe
|
||||
func (t *resettingTimerSnapshot) Mean() float64 {
|
||||
if !t.calculated {
|
||||
t.calc(nil)
|
||||
}
|
||||
|
||||
return t.mean
|
||||
}
|
||||
|
||||
// Max returns the max of the snapshotted values
|
||||
// note: this method is not thread safe
|
||||
func (t *resettingTimerSnapshot) Max() int64 {
|
||||
if !t.calculated {
|
||||
t.calc(nil)
|
||||
}
|
||||
return t.max
|
||||
}
|
||||
|
||||
// Min returns the min of the snapshotted values
|
||||
// note: this method is not thread safe
|
||||
func (t *resettingTimerSnapshot) Min() int64 {
|
||||
if !t.calculated {
|
||||
t.calc(nil)
|
||||
}
|
||||
return t.min
|
||||
}
|
||||
|
||||
func (t *resettingTimerSnapshot) calc(percentiles []float64) {
|
||||
scores := CalculatePercentiles(t.values, percentiles)
|
||||
t.thresholdBoundaries = scores
|
||||
if len(t.values) == 0 {
|
||||
return
|
||||
}
|
||||
t.min = t.values[0]
|
||||
t.max = t.values[len(t.values)-1]
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestResettingTimer(t *testing.T) {
|
||||
tests := []struct {
|
||||
values []int64
|
||||
start int
|
||||
end int
|
||||
wantP50 float64
|
||||
wantP95 float64
|
||||
wantP99 float64
|
||||
wantMean float64
|
||||
wantMin int64
|
||||
wantMax int64
|
||||
}{
|
||||
{
|
||||
values: []int64{},
|
||||
start: 1,
|
||||
end: 11,
|
||||
wantP50: 5.5, wantP95: 10, wantP99: 10,
|
||||
wantMin: 1, wantMax: 10, wantMean: 5.5,
|
||||
},
|
||||
{
|
||||
values: []int64{},
|
||||
start: 1,
|
||||
end: 101,
|
||||
wantP50: 50.5, wantP95: 95.94999999999999, wantP99: 99.99,
|
||||
wantMin: 1, wantMax: 100, wantMean: 50.5,
|
||||
},
|
||||
{
|
||||
values: []int64{1},
|
||||
start: 0,
|
||||
end: 0,
|
||||
wantP50: 1, wantP95: 1, wantP99: 1,
|
||||
wantMin: 1, wantMax: 1, wantMean: 1,
|
||||
},
|
||||
{
|
||||
values: []int64{0},
|
||||
start: 0,
|
||||
end: 0,
|
||||
wantP50: 0, wantP95: 0, wantP99: 0,
|
||||
wantMin: 0, wantMax: 0, wantMean: 0,
|
||||
},
|
||||
{
|
||||
values: []int64{},
|
||||
start: 0,
|
||||
end: 0,
|
||||
wantP50: 0, wantP95: 0, wantP99: 0,
|
||||
wantMin: 0, wantMax: 0, wantMean: 0,
|
||||
},
|
||||
{
|
||||
values: []int64{1, 10},
|
||||
start: 0,
|
||||
end: 0,
|
||||
wantP50: 5.5, wantP95: 10, wantP99: 10,
|
||||
wantMin: 1, wantMax: 10, wantMean: 5.5,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
timer := NewResettingTimer()
|
||||
|
||||
for i := tt.start; i < tt.end; i++ {
|
||||
tt.values = append(tt.values, int64(i))
|
||||
}
|
||||
|
||||
for _, v := range tt.values {
|
||||
timer.Update(time.Duration(v))
|
||||
}
|
||||
snap := timer.Snapshot()
|
||||
|
||||
ps := snap.Percentiles([]float64{0.50, 0.95, 0.99})
|
||||
|
||||
if have, want := snap.Min(), tt.wantMin; have != want {
|
||||
t.Fatalf("%d: min: have %d, want %d", i, have, want)
|
||||
}
|
||||
if have, want := snap.Max(), tt.wantMax; have != want {
|
||||
t.Fatalf("%d: max: have %d, want %d", i, have, want)
|
||||
}
|
||||
if have, want := snap.Mean(), tt.wantMean; have != want {
|
||||
t.Fatalf("%d: mean: have %v, want %v", i, have, want)
|
||||
}
|
||||
if have, want := ps[0], tt.wantP50; have != want {
|
||||
t.Errorf("%d: p50: have %v, want %v", i, have, want)
|
||||
}
|
||||
if have, want := ps[1], tt.wantP95; have != want {
|
||||
t.Errorf("%d: p95: have %v, want %v", i, have, want)
|
||||
}
|
||||
if have, want := ps[2], tt.wantP99; have != want {
|
||||
t.Errorf("%d: p99: have %v, want %v", i, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResettingTimerWithFivePercentiles(t *testing.T) {
|
||||
tests := []struct {
|
||||
values []int64
|
||||
start int
|
||||
end int
|
||||
wantP05 float64
|
||||
wantP20 float64
|
||||
wantP50 float64
|
||||
wantP95 float64
|
||||
wantP99 float64
|
||||
wantMean float64
|
||||
wantMin int64
|
||||
wantMax int64
|
||||
}{
|
||||
{
|
||||
values: []int64{},
|
||||
start: 1,
|
||||
end: 11,
|
||||
wantP05: 1, wantP20: 2.2, wantP50: 5.5, wantP95: 10, wantP99: 10,
|
||||
wantMin: 1, wantMax: 10, wantMean: 5.5,
|
||||
},
|
||||
{
|
||||
values: []int64{},
|
||||
start: 1,
|
||||
end: 101,
|
||||
wantP05: 5.050000000000001, wantP20: 20.200000000000003, wantP50: 50.5, wantP95: 95.94999999999999, wantP99: 99.99,
|
||||
wantMin: 1, wantMax: 100, wantMean: 50.5,
|
||||
},
|
||||
{
|
||||
values: []int64{1},
|
||||
start: 0,
|
||||
end: 0,
|
||||
wantP05: 1, wantP20: 1, wantP50: 1, wantP95: 1, wantP99: 1,
|
||||
wantMin: 1, wantMax: 1, wantMean: 1,
|
||||
},
|
||||
{
|
||||
values: []int64{0},
|
||||
start: 0,
|
||||
end: 0,
|
||||
wantP05: 0, wantP20: 0, wantP50: 0, wantP95: 0, wantP99: 0,
|
||||
wantMin: 0, wantMax: 0, wantMean: 0,
|
||||
},
|
||||
{
|
||||
values: []int64{},
|
||||
start: 0,
|
||||
end: 0,
|
||||
wantP05: 0, wantP20: 0, wantP50: 0, wantP95: 0, wantP99: 0,
|
||||
wantMin: 0, wantMax: 0, wantMean: 0,
|
||||
},
|
||||
{
|
||||
values: []int64{1, 10},
|
||||
start: 0,
|
||||
end: 0,
|
||||
wantP05: 1, wantP20: 1, wantP50: 5.5, wantP95: 10, wantP99: 10,
|
||||
wantMin: 1, wantMax: 10, wantMean: 5.5,
|
||||
},
|
||||
}
|
||||
for ind, tt := range tests {
|
||||
timer := NewResettingTimer()
|
||||
|
||||
for i := tt.start; i < tt.end; i++ {
|
||||
tt.values = append(tt.values, int64(i))
|
||||
}
|
||||
|
||||
for _, v := range tt.values {
|
||||
timer.Update(time.Duration(v))
|
||||
}
|
||||
|
||||
snap := timer.Snapshot()
|
||||
|
||||
ps := snap.Percentiles([]float64{0.05, 0.20, 0.50, 0.95, 0.99})
|
||||
|
||||
if tt.wantMin != snap.Min() {
|
||||
t.Errorf("%d: min: got %d, want %d", ind, snap.Min(), tt.wantMin)
|
||||
}
|
||||
|
||||
if tt.wantMax != snap.Max() {
|
||||
t.Errorf("%d: max: got %d, want %d", ind, snap.Max(), tt.wantMax)
|
||||
}
|
||||
|
||||
if tt.wantMean != snap.Mean() {
|
||||
t.Errorf("%d: mean: got %.2f, want %.2f", ind, snap.Mean(), tt.wantMean)
|
||||
}
|
||||
if tt.wantP05 != ps[0] {
|
||||
t.Errorf("%d: p05: got %v, want %v", ind, ps[0], tt.wantP05)
|
||||
}
|
||||
if tt.wantP20 != ps[1] {
|
||||
t.Errorf("%d: p20: got %v, want %v", ind, ps[1], tt.wantP20)
|
||||
}
|
||||
if tt.wantP50 != ps[2] {
|
||||
t.Errorf("%d: p50: got %v, want %v", ind, ps[2], tt.wantP50)
|
||||
}
|
||||
if tt.wantP95 != ps[3] {
|
||||
t.Errorf("%d: p95: got %v, want %v", ind, ps[3], tt.wantP95)
|
||||
}
|
||||
if tt.wantP99 != ps[4] {
|
||||
t.Errorf("%d: p99: got %v, want %v", ind, ps[4], tt.wantP99)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,301 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"runtime/metrics"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func getOrRegisterRuntimeHistogram(name string, scale float64, r Registry) *runtimeHistogram {
|
||||
if r == nil {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
constructor := func() Histogram { return newRuntimeHistogram(scale) }
|
||||
return r.GetOrRegister(name, constructor).(*runtimeHistogram)
|
||||
}
|
||||
|
||||
// runtimeHistogram wraps a runtime/metrics histogram.
|
||||
type runtimeHistogram struct {
|
||||
v atomic.Value // v is a pointer to a metrics.Float64Histogram
|
||||
scaleFactor float64
|
||||
}
|
||||
|
||||
func newRuntimeHistogram(scale float64) *runtimeHistogram {
|
||||
h := &runtimeHistogram{scaleFactor: scale}
|
||||
h.update(new(metrics.Float64Histogram))
|
||||
return h
|
||||
}
|
||||
|
||||
func RuntimeHistogramFromData(scale float64, hist *metrics.Float64Histogram) *runtimeHistogram {
|
||||
h := &runtimeHistogram{scaleFactor: scale}
|
||||
h.update(hist)
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *runtimeHistogram) update(mh *metrics.Float64Histogram) {
|
||||
if mh == nil {
|
||||
// The update value can be nil if the current Go version doesn't support a
|
||||
// requested metric. It's just easier to handle nil here than putting
|
||||
// conditionals everywhere.
|
||||
return
|
||||
}
|
||||
|
||||
s := metrics.Float64Histogram{
|
||||
Counts: make([]uint64, len(mh.Counts)),
|
||||
Buckets: make([]float64, len(mh.Buckets)),
|
||||
}
|
||||
copy(s.Counts, mh.Counts)
|
||||
for i, b := range mh.Buckets {
|
||||
s.Buckets[i] = b * h.scaleFactor
|
||||
}
|
||||
h.v.Store(&s)
|
||||
}
|
||||
|
||||
func (h *runtimeHistogram) Clear() {
|
||||
panic("runtimeHistogram does not support Clear")
|
||||
}
|
||||
func (h *runtimeHistogram) Update(int64) {
|
||||
panic("runtimeHistogram does not support Update")
|
||||
}
|
||||
|
||||
// Snapshot returns a non-changing copy of the histogram.
|
||||
func (h *runtimeHistogram) Snapshot() HistogramSnapshot {
|
||||
hist := h.v.Load().(*metrics.Float64Histogram)
|
||||
return newRuntimeHistogramSnapshot(hist)
|
||||
}
|
||||
|
||||
type runtimeHistogramSnapshot struct {
|
||||
internal *metrics.Float64Histogram
|
||||
calculated bool
|
||||
// The following fields are (lazily) calculated based on 'internal'
|
||||
mean float64
|
||||
count int64
|
||||
min int64 // min is the lowest sample value.
|
||||
max int64 // max is the highest sample value.
|
||||
variance float64
|
||||
}
|
||||
|
||||
func newRuntimeHistogramSnapshot(h *metrics.Float64Histogram) *runtimeHistogramSnapshot {
|
||||
return &runtimeHistogramSnapshot{
|
||||
internal: h,
|
||||
}
|
||||
}
|
||||
|
||||
// calc calculates the values for the snapshot. This method is not threadsafe.
|
||||
func (h *runtimeHistogramSnapshot) calc() {
|
||||
h.calculated = true
|
||||
var (
|
||||
count int64 // number of samples
|
||||
sum float64 // approx sum of all sample values
|
||||
min int64
|
||||
max float64
|
||||
)
|
||||
if len(h.internal.Counts) == 0 {
|
||||
return
|
||||
}
|
||||
for i, c := range h.internal.Counts {
|
||||
if c == 0 {
|
||||
continue
|
||||
}
|
||||
if count == 0 { // Set min only first loop iteration
|
||||
min = int64(math.Floor(h.internal.Buckets[i]))
|
||||
}
|
||||
count += int64(c)
|
||||
sum += h.midpoint(i) * float64(c)
|
||||
// Set max on every iteration
|
||||
edge := h.internal.Buckets[i+1]
|
||||
if math.IsInf(edge, 1) {
|
||||
edge = h.internal.Buckets[i]
|
||||
}
|
||||
if edge > max {
|
||||
max = edge
|
||||
}
|
||||
}
|
||||
h.min = min
|
||||
h.max = int64(max)
|
||||
h.mean = sum / float64(count)
|
||||
h.count = count
|
||||
}
|
||||
|
||||
// Count returns the sample count.
|
||||
func (h *runtimeHistogramSnapshot) Count() int64 {
|
||||
if !h.calculated {
|
||||
h.calc()
|
||||
}
|
||||
return h.count
|
||||
}
|
||||
|
||||
// Size returns the size of the sample at the time the snapshot was taken.
|
||||
func (h *runtimeHistogramSnapshot) Size() int {
|
||||
return len(h.internal.Counts)
|
||||
}
|
||||
|
||||
// Mean returns an approximation of the mean.
|
||||
func (h *runtimeHistogramSnapshot) Mean() float64 {
|
||||
if !h.calculated {
|
||||
h.calc()
|
||||
}
|
||||
return h.mean
|
||||
}
|
||||
|
||||
func (h *runtimeHistogramSnapshot) midpoint(bucket int) float64 {
|
||||
high := h.internal.Buckets[bucket+1]
|
||||
low := h.internal.Buckets[bucket]
|
||||
if math.IsInf(high, 1) {
|
||||
// The edge of the highest bucket can be +Inf, and it's supposed to mean that this
|
||||
// bucket contains all remaining samples > low. We can't get the middle of an
|
||||
// infinite range, so just return the lower bound of this bucket instead.
|
||||
return low
|
||||
}
|
||||
if math.IsInf(low, -1) {
|
||||
// Similarly, we can get -Inf in the left edge of the lowest bucket,
|
||||
// and it means the bucket contains all remaining values < high.
|
||||
return high
|
||||
}
|
||||
return (low + high) / 2
|
||||
}
|
||||
|
||||
// StdDev approximates the standard deviation of the histogram.
|
||||
func (h *runtimeHistogramSnapshot) StdDev() float64 {
|
||||
return math.Sqrt(h.Variance())
|
||||
}
|
||||
|
||||
// Variance approximates the variance of the histogram.
|
||||
func (h *runtimeHistogramSnapshot) Variance() float64 {
|
||||
if len(h.internal.Counts) == 0 {
|
||||
return 0
|
||||
}
|
||||
if !h.calculated {
|
||||
h.calc()
|
||||
}
|
||||
if h.count <= 1 {
|
||||
// There is no variance when there are zero or one items.
|
||||
return 0
|
||||
}
|
||||
// Variance is not calculated in 'calc', because it requires a second iteration.
|
||||
// Therefore we calculate it lazily in this method, triggered either by
|
||||
// a direct call to Variance or via StdDev.
|
||||
if h.variance != 0.0 {
|
||||
return h.variance
|
||||
}
|
||||
var sum float64
|
||||
|
||||
for i, c := range h.internal.Counts {
|
||||
midpoint := h.midpoint(i)
|
||||
d := midpoint - h.mean
|
||||
sum += float64(c) * (d * d)
|
||||
}
|
||||
h.variance = sum / float64(h.count-1)
|
||||
return h.variance
|
||||
}
|
||||
|
||||
// Percentile computes the p'th percentile value.
|
||||
func (h *runtimeHistogramSnapshot) Percentile(p float64) float64 {
|
||||
threshold := float64(h.Count()) * p
|
||||
values := [1]float64{threshold}
|
||||
h.computePercentiles(values[:])
|
||||
return values[0]
|
||||
}
|
||||
|
||||
// Percentiles computes all requested percentile values.
|
||||
func (h *runtimeHistogramSnapshot) Percentiles(ps []float64) []float64 {
|
||||
// Compute threshold values. We need these to be sorted
|
||||
// for the percentile computation, but restore the original
|
||||
// order later, so keep the indexes as well.
|
||||
count := float64(h.Count())
|
||||
thresholds := make([]float64, len(ps))
|
||||
indexes := make([]int, len(ps))
|
||||
for i, percentile := range ps {
|
||||
thresholds[i] = count * math.Max(0, math.Min(1.0, percentile))
|
||||
indexes[i] = i
|
||||
}
|
||||
sort.Sort(floatsAscendingKeepingIndex{thresholds, indexes})
|
||||
|
||||
// Now compute. The result is stored back into the thresholds slice.
|
||||
h.computePercentiles(thresholds)
|
||||
|
||||
// Put the result back into the requested order.
|
||||
sort.Sort(floatsByIndex{thresholds, indexes})
|
||||
return thresholds
|
||||
}
|
||||
|
||||
func (h *runtimeHistogramSnapshot) computePercentiles(thresh []float64) {
|
||||
var totalCount float64
|
||||
for i, count := range h.internal.Counts {
|
||||
totalCount += float64(count)
|
||||
|
||||
for len(thresh) > 0 && thresh[0] < totalCount {
|
||||
thresh[0] = h.internal.Buckets[i]
|
||||
thresh = thresh[1:]
|
||||
}
|
||||
if len(thresh) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: runtime/metrics.Float64Histogram is a collection of float64s, but the methods
|
||||
// below need to return int64 to satisfy the interface. The histogram provided by runtime
|
||||
// also doesn't keep track of individual samples, so results are approximated.
|
||||
|
||||
// Max returns the highest sample value.
|
||||
func (h *runtimeHistogramSnapshot) Max() int64 {
|
||||
if !h.calculated {
|
||||
h.calc()
|
||||
}
|
||||
return h.max
|
||||
}
|
||||
|
||||
// Min returns the lowest sample value.
|
||||
func (h *runtimeHistogramSnapshot) Min() int64 {
|
||||
if !h.calculated {
|
||||
h.calc()
|
||||
}
|
||||
return h.min
|
||||
}
|
||||
|
||||
// Sum returns the sum of all sample values.
|
||||
func (h *runtimeHistogramSnapshot) Sum() int64 {
|
||||
var sum float64
|
||||
for i := range h.internal.Counts {
|
||||
sum += h.internal.Buckets[i] * float64(h.internal.Counts[i])
|
||||
}
|
||||
return int64(math.Ceil(sum))
|
||||
}
|
||||
|
||||
type floatsAscendingKeepingIndex struct {
|
||||
values []float64
|
||||
indexes []int
|
||||
}
|
||||
|
||||
func (s floatsAscendingKeepingIndex) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
func (s floatsAscendingKeepingIndex) Less(i, j int) bool {
|
||||
return s.values[i] < s.values[j]
|
||||
}
|
||||
|
||||
func (s floatsAscendingKeepingIndex) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
s.indexes[i], s.indexes[j] = s.indexes[j], s.indexes[i]
|
||||
}
|
||||
|
||||
type floatsByIndex struct {
|
||||
values []float64
|
||||
indexes []int
|
||||
}
|
||||
|
||||
func (s floatsByIndex) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
func (s floatsByIndex) Less(i, j int) bool {
|
||||
return s.indexes[i] < s.indexes[j]
|
||||
}
|
||||
|
||||
func (s floatsByIndex) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
s.indexes[i], s.indexes[j] = s.indexes[j], s.indexes[i]
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"runtime/metrics"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ Histogram = (*runtimeHistogram)(nil)
|
||||
|
||||
type runtimeHistogramTest struct {
|
||||
h metrics.Float64Histogram
|
||||
|
||||
Count int64
|
||||
Min int64
|
||||
Max int64
|
||||
Sum int64
|
||||
Mean float64
|
||||
Variance float64
|
||||
StdDev float64
|
||||
Percentiles []float64 // .5 .8 .9 .99 .995
|
||||
}
|
||||
|
||||
// This test checks the results of statistical functions implemented
|
||||
// by runtimeHistogramSnapshot.
|
||||
func TestRuntimeHistogramStats(t *testing.T) {
|
||||
tests := []runtimeHistogramTest{
|
||||
0: {
|
||||
h: metrics.Float64Histogram{
|
||||
Counts: []uint64{},
|
||||
Buckets: []float64{},
|
||||
},
|
||||
Count: 0,
|
||||
Max: 0,
|
||||
Min: 0,
|
||||
Sum: 0,
|
||||
Mean: 0,
|
||||
Variance: 0,
|
||||
StdDev: 0,
|
||||
Percentiles: []float64{0, 0, 0, 0, 0},
|
||||
},
|
||||
1: {
|
||||
// This checks the case where the highest bucket is +Inf.
|
||||
h: metrics.Float64Histogram{
|
||||
Counts: []uint64{0, 1, 2},
|
||||
Buckets: []float64{0, 0.5, 1, math.Inf(1)},
|
||||
},
|
||||
Count: 3,
|
||||
Max: 1,
|
||||
Min: 0,
|
||||
Sum: 3,
|
||||
Mean: 0.9166666,
|
||||
Percentiles: []float64{1, 1, 1, 1, 1},
|
||||
Variance: 0.020833,
|
||||
StdDev: 0.144433,
|
||||
},
|
||||
2: {
|
||||
h: metrics.Float64Histogram{
|
||||
Counts: []uint64{8, 6, 3, 1},
|
||||
Buckets: []float64{12, 16, 18, 24, 25},
|
||||
},
|
||||
Count: 18,
|
||||
Max: 25,
|
||||
Min: 12,
|
||||
Sum: 270,
|
||||
Mean: 16.75,
|
||||
Variance: 10.3015,
|
||||
StdDev: 3.2096,
|
||||
Percentiles: []float64{16, 18, 18, 24, 24},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||
s := RuntimeHistogramFromData(1.0, &test.h).Snapshot()
|
||||
|
||||
if v := s.Count(); v != test.Count {
|
||||
t.Errorf("Count() = %v, want %v", v, test.Count)
|
||||
}
|
||||
if v := s.Min(); v != test.Min {
|
||||
t.Errorf("Min() = %v, want %v", v, test.Min)
|
||||
}
|
||||
if v := s.Max(); v != test.Max {
|
||||
t.Errorf("Max() = %v, want %v", v, test.Max)
|
||||
}
|
||||
if v := s.Sum(); v != test.Sum {
|
||||
t.Errorf("Sum() = %v, want %v", v, test.Sum)
|
||||
}
|
||||
if v := s.Mean(); !approxEqual(v, test.Mean, 0.0001) {
|
||||
t.Errorf("Mean() = %v, want %v", v, test.Mean)
|
||||
}
|
||||
if v := s.Variance(); !approxEqual(v, test.Variance, 0.0001) {
|
||||
t.Errorf("Variance() = %v, want %v", v, test.Variance)
|
||||
}
|
||||
if v := s.StdDev(); !approxEqual(v, test.StdDev, 0.0001) {
|
||||
t.Errorf("StdDev() = %v, want %v", v, test.StdDev)
|
||||
}
|
||||
ps := []float64{.5, .8, .9, .99, .995}
|
||||
if v := s.Percentiles(ps); !reflect.DeepEqual(v, test.Percentiles) {
|
||||
t.Errorf("Percentiles(%v) = %v, want %v", ps, v, test.Percentiles)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func approxEqual(x, y, ε float64) bool {
|
||||
if math.IsInf(x, -1) && math.IsInf(y, -1) {
|
||||
return true
|
||||
}
|
||||
if math.IsInf(x, 1) && math.IsInf(y, 1) {
|
||||
return true
|
||||
}
|
||||
if math.IsNaN(x) && math.IsNaN(y) {
|
||||
return true
|
||||
}
|
||||
return math.Abs(x-y) < ε
|
||||
}
|
||||
|
||||
// This test verifies that requesting Percentiles in unsorted order
|
||||
// returns them in the requested order.
|
||||
func TestRuntimeHistogramStatsPercentileOrder(t *testing.T) {
|
||||
s := RuntimeHistogramFromData(1.0, &metrics.Float64Histogram{
|
||||
Counts: []uint64{1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
||||
Buckets: []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
||||
}).Snapshot()
|
||||
result := s.Percentiles([]float64{1, 0.2, 0.5, 0.1, 0.2})
|
||||
expected := []float64{10, 2, 5, 1, 2}
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Fatal("wrong result:", result)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRuntimeHistogramSnapshotRead(b *testing.B) {
|
||||
var sLatency = "7\xff\x81\x03\x01\x01\x10Float64Histogram\x01\xff\x82\x00\x01\x02\x01\x06Counts\x01\xff\x84\x00\x01\aBuckets\x01\xff\x86\x00\x00\x00\x16\xff\x83\x02\x01\x01\b[]uint64\x01\xff\x84\x00\x01\x06\x00\x00\x17\xff\x85\x02\x01\x01\t[]float64\x01\xff\x86\x00\x01\b\x00\x00\xfe\x06T\xff\x82\x01\xff\xa2\x00\xfe\r\xef\x00\x01\x02\x02\x04\x05\x04\b\x15\x17 B?6.L;$!2) \x1a? \x190aH7FY6#\x190\x1d\x14\x10\x1b\r\t\x04\x03\x01\x01\x00\x03\x02\x00\x03\x05\x05\x02\x02\x06\x04\v\x06\n\x15\x18\x13'&.\x12=H/L&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xa3\xfe\xf0\xff\x00\xf8\x95\xd6&\xe8\v.q>\xf8\x95\xd6&\xe8\v.\x81>\xf8\xdfA:\xdc\x11ʼn>\xf8\x95\xd6&\xe8\v.\x91>\xf8:\x8c0\xe2\x8ey\x95>\xf8\xdfA:\xdc\x11ř>\xf8\x84\xf7C֔\x10\x9e>\xf8\x95\xd6&\xe8\v.\xa1>\xf8:\x8c0\xe2\x8ey\xa5>\xf8\xdfA:\xdc\x11ũ>\xf8\x84\xf7C֔\x10\xae>\xf8\x95\xd6&\xe8\v.\xb1>\xf8:\x8c0\xe2\x8ey\xb5>\xf8\xdfA:\xdc\x11Ź>\xf8\x84\xf7C֔\x10\xbe>\xf8\x95\xd6&\xe8\v.\xc1>\xf8:\x8c0\xe2\x8ey\xc5>\xf8\xdfA:\xdc\x11\xc5\xc9>\xf8\x84\xf7C֔\x10\xce>\xf8\x95\xd6&\xe8\v.\xd1>\xf8:\x8c0\xe2\x8ey\xd5>\xf8\xdfA:\xdc\x11\xc5\xd9>\xf8\x84\xf7C֔\x10\xde>\xf8\x95\xd6&\xe8\v.\xe1>\xf8:\x8c0\xe2\x8ey\xe5>\xf8\xdfA:\xdc\x11\xc5\xe9>\xf8\x84\xf7C֔\x10\xee>\xf8\x95\xd6&\xe8\v.\xf1>\xf8:\x8c0\xe2\x8ey\xf5>\xf8\xdfA:\xdc\x11\xc5\xf9>\xf8\x84\xf7C֔\x10\xfe>\xf8\x95\xd6&\xe8\v.\x01?\xf8:\x8c0\xe2\x8ey\x05?\xf8\xdfA:\xdc\x11\xc5\t?\xf8\x84\xf7C֔\x10\x0e?\xf8\x95\xd6&\xe8\v.\x11?\xf8:\x8c0\xe2\x8ey\x15?\xf8\xdfA:\xdc\x11\xc5\x19?\xf8\x84\xf7C֔\x10\x1e?\xf8\x95\xd6&\xe8\v.!?\xf8:\x8c0\xe2\x8ey%?\xf8\xdfA:\xdc\x11\xc5)?\xf8\x84\xf7C֔\x10.?\xf8\x95\xd6&\xe8\v.1?\xf8:\x8c0\xe2\x8ey5?\xf8\xdfA:\xdc\x11\xc59?\xf8\x84\xf7C֔\x10>?\xf8\x95\xd6&\xe8\v.A?\xf8:\x8c0\xe2\x8eyE?\xf8\xdfA:\xdc\x11\xc5I?\xf8\x84\xf7C֔\x10N?\xf8\x95\xd6&\xe8\v.Q?\xf8:\x8c0\xe2\x8eyU?\xf8\xdfA:\xdc\x11\xc5Y?\xf8\x84\xf7C֔\x10^?\xf8\x95\xd6&\xe8\v.a?\xf8:\x8c0\xe2\x8eye?\xf8\xdfA:\xdc\x11\xc5i?\xf8\x84\xf7C֔\x10n?\xf8\x95\xd6&\xe8\v.q?\xf8:\x8c0\xe2\x8eyu?\xf8\xdfA:\xdc\x11\xc5y?\xf8\x84\xf7C֔\x10~?\xf8\x95\xd6&\xe8\v.\x81?\xf8:\x8c0\xe2\x8ey\x85?\xf8\xdfA:\xdc\x11ʼn?\xf8\x84\xf7C֔\x10\x8e?\xf8\x95\xd6&\xe8\v.\x91?\xf8:\x8c0\xe2\x8ey\x95?\xf8\xdfA:\xdc\x11ř?\xf8\x84\xf7C֔\x10\x9e?\xf8\x95\xd6&\xe8\v.\xa1?\xf8:\x8c0\xe2\x8ey\xa5?\xf8\xdfA:\xdc\x11ũ?\xf8\x84\xf7C֔\x10\xae?\xf8\x95\xd6&\xe8\v.\xb1?\xf8:\x8c0\xe2\x8ey\xb5?\xf8\xdfA:\xdc\x11Ź?\xf8\x84\xf7C֔\x10\xbe?\xf8\x95\xd6&\xe8\v.\xc1?\xf8:\x8c0\xe2\x8ey\xc5?\xf8\xdfA:\xdc\x11\xc5\xc9?\xf8\x84\xf7C֔\x10\xce?\xf8\x95\xd6&\xe8\v.\xd1?\xf8:\x8c0\xe2\x8ey\xd5?\xf8\xdfA:\xdc\x11\xc5\xd9?\xf8\x84\xf7C֔\x10\xde?\xf8\x95\xd6&\xe8\v.\xe1?\xf8:\x8c0\xe2\x8ey\xe5?\xf8\xdfA:\xdc\x11\xc5\xe9?\xf8\x84\xf7C֔\x10\xee?\xf8\x95\xd6&\xe8\v.\xf1?\xf8:\x8c0\xe2\x8ey\xf5?\xf8\xdfA:\xdc\x11\xc5\xf9?\xf8\x84\xf7C֔\x10\xfe?\xf8\x95\xd6&\xe8\v.\x01@\xf8:\x8c0\xe2\x8ey\x05@\xf8\xdfA:\xdc\x11\xc5\t@\xf8\x84\xf7C֔\x10\x0e@\xf8\x95\xd6&\xe8\v.\x11@\xf8:\x8c0\xe2\x8ey\x15@\xf8\xdfA:\xdc\x11\xc5\x19@\xf8\x84\xf7C֔\x10\x1e@\xf8\x95\xd6&\xe8\v.!@\xf8:\x8c0\xe2\x8ey%@\xf8\xdfA:\xdc\x11\xc5)@\xf8\x84\xf7C֔\x10.@\xf8\x95\xd6&\xe8\v.1@\xf8:\x8c0\xe2\x8ey5@\xf8\xdfA:\xdc\x11\xc59@\xf8\x84\xf7C֔\x10>@\xf8\x95\xd6&\xe8\v.A@\xf8:\x8c0\xe2\x8eyE@\xf8\xdfA:\xdc\x11\xc5I@\xf8\x84\xf7C֔\x10N@\xf8\x95\xd6&\xe8\v.Q@\xf8:\x8c0\xe2\x8eyU@\xf8\xdfA:\xdc\x11\xc5Y@\xf8\x84\xf7C֔\x10^@\xf8\x95\xd6&\xe8\v.a@\xf8:\x8c0\xe2\x8eye@\xf8\xdfA:\xdc\x11\xc5i@\xf8\x84\xf7C֔\x10n@\xf8\x95\xd6&\xe8\v.q@\xf8:\x8c0\xe2\x8eyu@\xf8\xdfA:\xdc\x11\xc5y@\xf8\x84\xf7C֔\x10~@\xf8\x95\xd6&\xe8\v.\x81@\xf8:\x8c0\xe2\x8ey\x85@\xf8\xdfA:\xdc\x11ʼn@\xf8\x84\xf7C֔\x10\x8e@\xf8\x95\xd6&\xe8\v.\x91@\xf8:\x8c0\xe2\x8ey\x95@\xf8\xdfA:\xdc\x11ř@\xf8\x84\xf7C֔\x10\x9e@\xf8\x95\xd6&\xe8\v.\xa1@\xf8:\x8c0\xe2\x8ey\xa5@\xf8\xdfA:\xdc\x11ũ@\xf8\x84\xf7C֔\x10\xae@\xf8\x95\xd6&\xe8\v.\xb1@\xf8:\x8c0\xe2\x8ey\xb5@\xf8\xdfA:\xdc\x11Ź@\xf8\x84\xf7C֔\x10\xbe@\xf8\x95\xd6&\xe8\v.\xc1@\xf8:\x8c0\xe2\x8ey\xc5@\xf8\xdfA:\xdc\x11\xc5\xc9@\xf8\x84\xf7C֔\x10\xce@\xf8\x95\xd6&\xe8\v.\xd1@\xf8:\x8c0\xe2\x8ey\xd5@\xf8\xdfA:\xdc\x11\xc5\xd9@\xf8\x84\xf7C֔\x10\xde@\xf8\x95\xd6&\xe8\v.\xe1@\xf8:\x8c0\xe2\x8ey\xe5@\xf8\xdfA:\xdc\x11\xc5\xe9@\xf8\x84\xf7C֔\x10\xee@\xf8\x95\xd6&\xe8\v.\xf1@\xf8:\x8c0\xe2\x8ey\xf5@\xf8\xdfA:\xdc\x11\xc5\xf9@\xf8\x84\xf7C֔\x10\xfe@\xf8\x95\xd6&\xe8\v.\x01A\xfe\xf0\x7f\x00"
|
||||
|
||||
dserialize := func(data string) *metrics.Float64Histogram {
|
||||
var res metrics.Float64Histogram
|
||||
if err := gob.NewDecoder(bytes.NewReader([]byte(data))).Decode(&res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &res
|
||||
}
|
||||
latency := RuntimeHistogramFromData(float64(time.Second), dserialize(sLatency))
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
snap := latency.Snapshot()
|
||||
// These are the fields that influxdb accesses
|
||||
_ = snap.Count()
|
||||
_ = snap.Max()
|
||||
_ = snap.Mean()
|
||||
_ = snap.Min()
|
||||
_ = snap.StdDev()
|
||||
_ = snap.Variance()
|
||||
_ = snap.Percentiles([]float64{0.25, 0.5, 0.75, 0.95, 0.99, 0.999, 0.9999})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,446 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const rescaleThreshold = time.Hour
|
||||
|
||||
type SampleSnapshot interface {
|
||||
Count() int64
|
||||
Max() int64
|
||||
Mean() float64
|
||||
Min() int64
|
||||
Percentile(float64) float64
|
||||
Percentiles([]float64) []float64
|
||||
Size() int
|
||||
StdDev() float64
|
||||
Sum() int64
|
||||
Variance() float64
|
||||
}
|
||||
|
||||
// Samples maintain a statistically-significant selection of values from
|
||||
// a stream.
|
||||
type Sample interface {
|
||||
Snapshot() SampleSnapshot
|
||||
Clear()
|
||||
Update(int64)
|
||||
}
|
||||
|
||||
// ExpDecaySample is an exponentially-decaying sample using a forward-decaying
|
||||
// priority reservoir. See Cormode et al's "Forward Decay: A Practical Time
|
||||
// Decay Model for Streaming Systems".
|
||||
//
|
||||
// <http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf>
|
||||
type ExpDecaySample struct {
|
||||
alpha float64
|
||||
count int64
|
||||
mutex sync.Mutex
|
||||
reservoirSize int
|
||||
t0, t1 time.Time
|
||||
values *expDecaySampleHeap
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
// NewExpDecaySample constructs a new exponentially-decaying sample with the
|
||||
// given reservoir size and alpha.
|
||||
func NewExpDecaySample(reservoirSize int, alpha float64) Sample {
|
||||
if !Enabled {
|
||||
return NilSample{}
|
||||
}
|
||||
s := &ExpDecaySample{
|
||||
alpha: alpha,
|
||||
reservoirSize: reservoirSize,
|
||||
t0: time.Now(),
|
||||
values: newExpDecaySampleHeap(reservoirSize),
|
||||
}
|
||||
s.t1 = s.t0.Add(rescaleThreshold)
|
||||
return s
|
||||
}
|
||||
|
||||
// SetRand sets the random source (useful in tests)
|
||||
func (s *ExpDecaySample) SetRand(prng *rand.Rand) Sample {
|
||||
s.rand = prng
|
||||
return s
|
||||
}
|
||||
|
||||
// Clear clears all samples.
|
||||
func (s *ExpDecaySample) Clear() {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.count = 0
|
||||
s.t0 = time.Now()
|
||||
s.t1 = s.t0.Add(rescaleThreshold)
|
||||
s.values.Clear()
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the sample.
|
||||
func (s *ExpDecaySample) Snapshot() SampleSnapshot {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
var (
|
||||
samples = s.values.Values()
|
||||
values = make([]int64, len(samples))
|
||||
max int64 = math.MinInt64
|
||||
min int64 = math.MaxInt64
|
||||
sum int64
|
||||
)
|
||||
for i, item := range samples {
|
||||
v := item.v
|
||||
values[i] = v
|
||||
sum += v
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
return newSampleSnapshotPrecalculated(s.count, values, min, max, sum)
|
||||
}
|
||||
|
||||
// Update samples a new value.
|
||||
func (s *ExpDecaySample) Update(v int64) {
|
||||
s.update(time.Now(), v)
|
||||
}
|
||||
|
||||
// update samples a new value at a particular timestamp. This is a method all
|
||||
// its own to facilitate testing.
|
||||
func (s *ExpDecaySample) update(t time.Time, v int64) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.count++
|
||||
if s.values.Size() == s.reservoirSize {
|
||||
s.values.Pop()
|
||||
}
|
||||
var f64 float64
|
||||
if s.rand != nil {
|
||||
f64 = s.rand.Float64()
|
||||
} else {
|
||||
f64 = rand.Float64()
|
||||
}
|
||||
s.values.Push(expDecaySample{
|
||||
k: math.Exp(t.Sub(s.t0).Seconds()*s.alpha) / f64,
|
||||
v: v,
|
||||
})
|
||||
if t.After(s.t1) {
|
||||
values := s.values.Values()
|
||||
t0 := s.t0
|
||||
s.values.Clear()
|
||||
s.t0 = t
|
||||
s.t1 = s.t0.Add(rescaleThreshold)
|
||||
for _, v := range values {
|
||||
v.k = v.k * math.Exp(-s.alpha*s.t0.Sub(t0).Seconds())
|
||||
s.values.Push(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NilSample is a no-op Sample.
|
||||
type NilSample struct{}
|
||||
|
||||
func (NilSample) Clear() {}
|
||||
func (NilSample) Snapshot() SampleSnapshot { return (*emptySnapshot)(nil) }
|
||||
func (NilSample) Update(v int64) {}
|
||||
|
||||
// SamplePercentiles returns an arbitrary percentile of the slice of int64.
|
||||
func SamplePercentile(values []int64, p float64) float64 {
|
||||
return CalculatePercentiles(values, []float64{p})[0]
|
||||
}
|
||||
|
||||
// CalculatePercentiles returns a slice of arbitrary percentiles of the slice of
|
||||
// int64. This method returns interpolated results, so e.g if there are only two
|
||||
// values, [0, 10], a 50% percentile will land between them.
|
||||
//
|
||||
// Note: As a side-effect, this method will also sort the slice of values.
|
||||
// Note2: The input format for percentiles is NOT percent! To express 50%, use 0.5, not 50.
|
||||
func CalculatePercentiles(values []int64, ps []float64) []float64 {
|
||||
scores := make([]float64, len(ps))
|
||||
size := len(values)
|
||||
if size == 0 {
|
||||
return scores
|
||||
}
|
||||
slices.Sort(values)
|
||||
for i, p := range ps {
|
||||
pos := p * float64(size+1)
|
||||
|
||||
if pos < 1.0 {
|
||||
scores[i] = float64(values[0])
|
||||
} else if pos >= float64(size) {
|
||||
scores[i] = float64(values[size-1])
|
||||
} else {
|
||||
lower := float64(values[int(pos)-1])
|
||||
upper := float64(values[int(pos)])
|
||||
scores[i] = lower + (pos-math.Floor(pos))*(upper-lower)
|
||||
}
|
||||
}
|
||||
return scores
|
||||
}
|
||||
|
||||
// sampleSnapshot is a read-only copy of another Sample.
|
||||
type sampleSnapshot struct {
|
||||
count int64
|
||||
values []int64
|
||||
|
||||
max int64
|
||||
min int64
|
||||
mean float64
|
||||
sum int64
|
||||
variance float64
|
||||
}
|
||||
|
||||
// newSampleSnapshotPrecalculated creates a read-only sampleSnapShot, using
|
||||
// precalculated sums to avoid iterating the values
|
||||
func newSampleSnapshotPrecalculated(count int64, values []int64, min, max, sum int64) *sampleSnapshot {
|
||||
if len(values) == 0 {
|
||||
return &sampleSnapshot{
|
||||
count: count,
|
||||
values: values,
|
||||
}
|
||||
}
|
||||
return &sampleSnapshot{
|
||||
count: count,
|
||||
values: values,
|
||||
max: max,
|
||||
min: min,
|
||||
mean: float64(sum) / float64(len(values)),
|
||||
sum: sum,
|
||||
}
|
||||
}
|
||||
|
||||
// newSampleSnapshot creates a read-only sampleSnapShot, and calculates some
|
||||
// numbers.
|
||||
func newSampleSnapshot(count int64, values []int64) *sampleSnapshot {
|
||||
var (
|
||||
max int64 = math.MinInt64
|
||||
min int64 = math.MaxInt64
|
||||
sum int64
|
||||
)
|
||||
for _, v := range values {
|
||||
sum += v
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
return newSampleSnapshotPrecalculated(count, values, min, max, sum)
|
||||
}
|
||||
|
||||
// Count returns the count of inputs at the time the snapshot was taken.
|
||||
func (s *sampleSnapshot) Count() int64 { return s.count }
|
||||
|
||||
// Max returns the maximal value at the time the snapshot was taken.
|
||||
func (s *sampleSnapshot) Max() int64 { return s.max }
|
||||
|
||||
// Mean returns the mean value at the time the snapshot was taken.
|
||||
func (s *sampleSnapshot) Mean() float64 { return s.mean }
|
||||
|
||||
// Min returns the minimal value at the time the snapshot was taken.
|
||||
func (s *sampleSnapshot) Min() int64 { return s.min }
|
||||
|
||||
// Percentile returns an arbitrary percentile of values at the time the
|
||||
// snapshot was taken.
|
||||
func (s *sampleSnapshot) Percentile(p float64) float64 {
|
||||
return SamplePercentile(s.values, p)
|
||||
}
|
||||
|
||||
// Percentiles returns a slice of arbitrary percentiles of values at the time
|
||||
// the snapshot was taken.
|
||||
func (s *sampleSnapshot) Percentiles(ps []float64) []float64 {
|
||||
return CalculatePercentiles(s.values, ps)
|
||||
}
|
||||
|
||||
// Size returns the size of the sample at the time the snapshot was taken.
|
||||
func (s *sampleSnapshot) Size() int { return len(s.values) }
|
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (s *sampleSnapshot) Snapshot() SampleSnapshot { return s }
|
||||
|
||||
// StdDev returns the standard deviation of values at the time the snapshot was
|
||||
// taken.
|
||||
func (s *sampleSnapshot) StdDev() float64 {
|
||||
if s.variance == 0.0 {
|
||||
s.variance = SampleVariance(s.mean, s.values)
|
||||
}
|
||||
return math.Sqrt(s.variance)
|
||||
}
|
||||
|
||||
// Sum returns the sum of values at the time the snapshot was taken.
|
||||
func (s *sampleSnapshot) Sum() int64 { return s.sum }
|
||||
|
||||
// Values returns a copy of the values in the sample.
|
||||
func (s *sampleSnapshot) Values() []int64 {
|
||||
values := make([]int64, len(s.values))
|
||||
copy(values, s.values)
|
||||
return values
|
||||
}
|
||||
|
||||
// Variance returns the variance of values at the time the snapshot was taken.
|
||||
func (s *sampleSnapshot) Variance() float64 {
|
||||
if s.variance == 0.0 {
|
||||
s.variance = SampleVariance(s.mean, s.values)
|
||||
}
|
||||
return s.variance
|
||||
}
|
||||
|
||||
// SampleVariance returns the variance of the slice of int64.
|
||||
func SampleVariance(mean float64, values []int64) float64 {
|
||||
if len(values) == 0 {
|
||||
return 0.0
|
||||
}
|
||||
var sum float64
|
||||
for _, v := range values {
|
||||
d := float64(v) - mean
|
||||
sum += d * d
|
||||
}
|
||||
return sum / float64(len(values))
|
||||
}
|
||||
|
||||
// A uniform sample using Vitter's Algorithm R.
|
||||
//
|
||||
// <http://www.cs.umd.edu/~samir/498/vitter.pdf>
|
||||
type UniformSample struct {
|
||||
count int64
|
||||
mutex sync.Mutex
|
||||
reservoirSize int
|
||||
values []int64
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
// NewUniformSample constructs a new uniform sample with the given reservoir
|
||||
// size.
|
||||
func NewUniformSample(reservoirSize int) Sample {
|
||||
if !Enabled {
|
||||
return NilSample{}
|
||||
}
|
||||
return &UniformSample{
|
||||
reservoirSize: reservoirSize,
|
||||
values: make([]int64, 0, reservoirSize),
|
||||
}
|
||||
}
|
||||
|
||||
// SetRand sets the random source (useful in tests)
|
||||
func (s *UniformSample) SetRand(prng *rand.Rand) Sample {
|
||||
s.rand = prng
|
||||
return s
|
||||
}
|
||||
|
||||
// Clear clears all samples.
|
||||
func (s *UniformSample) Clear() {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.count = 0
|
||||
s.values = make([]int64, 0, s.reservoirSize)
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the sample.
|
||||
func (s *UniformSample) Snapshot() SampleSnapshot {
|
||||
s.mutex.Lock()
|
||||
values := make([]int64, len(s.values))
|
||||
copy(values, s.values)
|
||||
count := s.count
|
||||
s.mutex.Unlock()
|
||||
return newSampleSnapshot(count, values)
|
||||
}
|
||||
|
||||
// Update samples a new value.
|
||||
func (s *UniformSample) Update(v int64) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.count++
|
||||
if len(s.values) < s.reservoirSize {
|
||||
s.values = append(s.values, v)
|
||||
} else {
|
||||
var r int64
|
||||
if s.rand != nil {
|
||||
r = s.rand.Int63n(s.count)
|
||||
} else {
|
||||
r = rand.Int63n(s.count)
|
||||
}
|
||||
if r < int64(len(s.values)) {
|
||||
s.values[int(r)] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expDecaySample represents an individual sample in a heap.
|
||||
type expDecaySample struct {
|
||||
k float64
|
||||
v int64
|
||||
}
|
||||
|
||||
func newExpDecaySampleHeap(reservoirSize int) *expDecaySampleHeap {
|
||||
return &expDecaySampleHeap{make([]expDecaySample, 0, reservoirSize)}
|
||||
}
|
||||
|
||||
// expDecaySampleHeap is a min-heap of expDecaySamples.
|
||||
// The internal implementation is copied from the standard library's container/heap
|
||||
type expDecaySampleHeap struct {
|
||||
s []expDecaySample
|
||||
}
|
||||
|
||||
func (h *expDecaySampleHeap) Clear() {
|
||||
h.s = h.s[:0]
|
||||
}
|
||||
|
||||
func (h *expDecaySampleHeap) Push(s expDecaySample) {
|
||||
n := len(h.s)
|
||||
h.s = h.s[0 : n+1]
|
||||
h.s[n] = s
|
||||
h.up(n)
|
||||
}
|
||||
|
||||
func (h *expDecaySampleHeap) Pop() expDecaySample {
|
||||
n := len(h.s) - 1
|
||||
h.s[0], h.s[n] = h.s[n], h.s[0]
|
||||
h.down(0, n)
|
||||
|
||||
n = len(h.s)
|
||||
s := h.s[n-1]
|
||||
h.s = h.s[0 : n-1]
|
||||
return s
|
||||
}
|
||||
|
||||
func (h *expDecaySampleHeap) Size() int {
|
||||
return len(h.s)
|
||||
}
|
||||
|
||||
func (h *expDecaySampleHeap) Values() []expDecaySample {
|
||||
return h.s
|
||||
}
|
||||
|
||||
func (h *expDecaySampleHeap) up(j int) {
|
||||
for {
|
||||
i := (j - 1) / 2 // parent
|
||||
if i == j || !(h.s[j].k < h.s[i].k) {
|
||||
break
|
||||
}
|
||||
h.s[i], h.s[j] = h.s[j], h.s[i]
|
||||
j = i
|
||||
}
|
||||
}
|
||||
|
||||
func (h *expDecaySampleHeap) down(i, n int) {
|
||||
for {
|
||||
j1 := 2*i + 1
|
||||
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
|
||||
break
|
||||
}
|
||||
j := j1 // left child
|
||||
if j2 := j1 + 1; j2 < n && !(h.s[j1].k < h.s[j2].k) {
|
||||
j = j2 // = 2*i + 2 // right child
|
||||
}
|
||||
if !(h.s[j].k < h.s[i].k) {
|
||||
break
|
||||
}
|
||||
h.s[i], h.s[j] = h.s[j], h.s[i]
|
||||
i = j
|
||||
}
|
||||
}
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const epsilonPercentile = .00000000001
|
||||
|
||||
// Benchmark{Compute,Copy}{1000,1000000} demonstrate that, even for relatively
|
||||
// expensive computations like Variance, the cost of copying the Sample, as
|
||||
// approximated by a make and copy, is much greater than the cost of the
|
||||
// computation for small samples and only slightly less for large samples.
|
||||
func BenchmarkCompute1000(b *testing.B) {
|
||||
s := make([]int64, 1000)
|
||||
var sum int64
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = int64(i)
|
||||
sum += int64(i)
|
||||
}
|
||||
mean := float64(sum) / float64(len(s))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
SampleVariance(mean, s)
|
||||
}
|
||||
}
|
||||
func BenchmarkCompute1000000(b *testing.B) {
|
||||
s := make([]int64, 1000000)
|
||||
var sum int64
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = int64(i)
|
||||
sum += int64(i)
|
||||
}
|
||||
mean := float64(sum) / float64(len(s))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
SampleVariance(mean, s)
|
||||
}
|
||||
}
|
||||
func BenchmarkCopy1000(b *testing.B) {
|
||||
s := make([]int64, 1000)
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = int64(i)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sCopy := make([]int64, len(s))
|
||||
copy(sCopy, s)
|
||||
}
|
||||
}
|
||||
func BenchmarkCopy1000000(b *testing.B) {
|
||||
s := make([]int64, 1000000)
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = int64(i)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sCopy := make([]int64, len(s))
|
||||
copy(sCopy, s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExpDecaySample257(b *testing.B) {
|
||||
benchmarkSample(b, NewExpDecaySample(257, 0.015))
|
||||
}
|
||||
|
||||
func BenchmarkExpDecaySample514(b *testing.B) {
|
||||
benchmarkSample(b, NewExpDecaySample(514, 0.015))
|
||||
}
|
||||
|
||||
func BenchmarkExpDecaySample1028(b *testing.B) {
|
||||
benchmarkSample(b, NewExpDecaySample(1028, 0.015))
|
||||
}
|
||||
|
||||
func BenchmarkUniformSample257(b *testing.B) {
|
||||
benchmarkSample(b, NewUniformSample(257))
|
||||
}
|
||||
|
||||
func BenchmarkUniformSample514(b *testing.B) {
|
||||
benchmarkSample(b, NewUniformSample(514))
|
||||
}
|
||||
|
||||
func BenchmarkUniformSample1028(b *testing.B) {
|
||||
benchmarkSample(b, NewUniformSample(1028))
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestExpDecaySample(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
reservoirSize int
|
||||
alpha float64
|
||||
updates int
|
||||
}{
|
||||
{100, 0.99, 10},
|
||||
{1000, 0.01, 100},
|
||||
{100, 0.99, 1000},
|
||||
} {
|
||||
sample := NewExpDecaySample(tc.reservoirSize, tc.alpha)
|
||||
for i := 0; i < tc.updates; i++ {
|
||||
sample.Update(int64(i))
|
||||
}
|
||||
snap := sample.Snapshot()
|
||||
if have, want := int(snap.Count()), tc.updates; have != want {
|
||||
t.Errorf("have %d want %d", have, want)
|
||||
}
|
||||
if have, want := snap.Size(), min(tc.updates, tc.reservoirSize); have != want {
|
||||
t.Errorf("have %d want %d", have, want)
|
||||
}
|
||||
values := snap.(*sampleSnapshot).values
|
||||
if have, want := len(values), min(tc.updates, tc.reservoirSize); have != want {
|
||||
t.Errorf("have %d want %d", have, want)
|
||||
}
|
||||
for _, v := range values {
|
||||
if v > int64(tc.updates) || v < 0 {
|
||||
t.Errorf("out of range [0, %d): %v", tc.updates, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test makes sure that the sample's priority is not amplified by using
|
||||
// nanosecond duration since start rather than second duration since start.
|
||||
// The priority becomes +Inf quickly after starting if this is done,
|
||||
// effectively freezing the set of samples until a rescale step happens.
|
||||
func TestExpDecaySampleNanosecondRegression(t *testing.T) {
|
||||
sw := NewExpDecaySample(100, 0.99)
|
||||
for i := 0; i < 100; i++ {
|
||||
sw.Update(10)
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
for i := 0; i < 100; i++ {
|
||||
sw.Update(20)
|
||||
}
|
||||
s := sw.Snapshot()
|
||||
v := s.(*sampleSnapshot).values
|
||||
avg := float64(0)
|
||||
for i := 0; i < len(v); i++ {
|
||||
avg += float64(v[i])
|
||||
}
|
||||
avg /= float64(len(v))
|
||||
if avg > 16 || avg < 14 {
|
||||
t.Errorf("out of range [14, 16]: %v\n", avg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpDecaySampleRescale(t *testing.T) {
|
||||
s := NewExpDecaySample(2, 0.001).(*ExpDecaySample)
|
||||
s.update(time.Now(), 1)
|
||||
s.update(time.Now().Add(time.Hour+time.Microsecond), 1)
|
||||
for _, v := range s.values.Values() {
|
||||
if v.k == 0.0 {
|
||||
t.Fatal("v.k == 0.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpDecaySampleSnapshot(t *testing.T) {
|
||||
now := time.Now()
|
||||
s := NewExpDecaySample(100, 0.99).(*ExpDecaySample).SetRand(rand.New(rand.NewSource(1)))
|
||||
for i := 1; i <= 10000; i++ {
|
||||
s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i))
|
||||
}
|
||||
snapshot := s.Snapshot()
|
||||
s.Update(1)
|
||||
testExpDecaySampleStatistics(t, snapshot)
|
||||
}
|
||||
|
||||
func TestExpDecaySampleStatistics(t *testing.T) {
|
||||
now := time.Now()
|
||||
s := NewExpDecaySample(100, 0.99).(*ExpDecaySample).SetRand(rand.New(rand.NewSource(1)))
|
||||
for i := 1; i <= 10000; i++ {
|
||||
s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i))
|
||||
}
|
||||
testExpDecaySampleStatistics(t, s.Snapshot())
|
||||
}
|
||||
|
||||
func TestUniformSample(t *testing.T) {
|
||||
sw := NewUniformSample(100)
|
||||
for i := 0; i < 1000; i++ {
|
||||
sw.Update(int64(i))
|
||||
}
|
||||
s := sw.Snapshot()
|
||||
if size := s.Count(); size != 1000 {
|
||||
t.Errorf("s.Count(): 1000 != %v\n", size)
|
||||
}
|
||||
if size := s.Size(); size != 100 {
|
||||
t.Errorf("s.Size(): 100 != %v\n", size)
|
||||
}
|
||||
values := s.(*sampleSnapshot).values
|
||||
|
||||
if l := len(values); l != 100 {
|
||||
t.Errorf("len(s.Values()): 100 != %v\n", l)
|
||||
}
|
||||
for _, v := range values {
|
||||
if v > 1000 || v < 0 {
|
||||
t.Errorf("out of range [0, 100): %v\n", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUniformSampleIncludesTail(t *testing.T) {
|
||||
sw := NewUniformSample(100)
|
||||
max := 100
|
||||
for i := 0; i < max; i++ {
|
||||
sw.Update(int64(i))
|
||||
}
|
||||
s := sw.Snapshot()
|
||||
v := s.(*sampleSnapshot).values
|
||||
sum := 0
|
||||
exp := (max - 1) * max / 2
|
||||
for i := 0; i < len(v); i++ {
|
||||
sum += int(v[i])
|
||||
}
|
||||
if exp != sum {
|
||||
t.Errorf("sum: %v != %v\n", exp, sum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUniformSampleSnapshot(t *testing.T) {
|
||||
s := NewUniformSample(100).(*UniformSample).SetRand(rand.New(rand.NewSource(1)))
|
||||
for i := 1; i <= 10000; i++ {
|
||||
s.Update(int64(i))
|
||||
}
|
||||
snapshot := s.Snapshot()
|
||||
s.Update(1)
|
||||
testUniformSampleStatistics(t, snapshot)
|
||||
}
|
||||
|
||||
func TestUniformSampleStatistics(t *testing.T) {
|
||||
s := NewUniformSample(100).(*UniformSample).SetRand(rand.New(rand.NewSource(1)))
|
||||
for i := 1; i <= 10000; i++ {
|
||||
s.Update(int64(i))
|
||||
}
|
||||
testUniformSampleStatistics(t, s.Snapshot())
|
||||
}
|
||||
|
||||
func benchmarkSample(b *testing.B, s Sample) {
|
||||
var memStats runtime.MemStats
|
||||
runtime.ReadMemStats(&memStats)
|
||||
pauseTotalNs := memStats.PauseTotalNs
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Update(1)
|
||||
}
|
||||
b.StopTimer()
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&memStats)
|
||||
b.Logf("GC cost: %d ns/op", int(memStats.PauseTotalNs-pauseTotalNs)/b.N)
|
||||
}
|
||||
|
||||
func testExpDecaySampleStatistics(t *testing.T, s SampleSnapshot) {
|
||||
if count := s.Count(); count != 10000 {
|
||||
t.Errorf("s.Count(): 10000 != %v\n", count)
|
||||
}
|
||||
if min := s.Min(); min != 107 {
|
||||
t.Errorf("s.Min(): 107 != %v\n", min)
|
||||
}
|
||||
if max := s.Max(); max != 10000 {
|
||||
t.Errorf("s.Max(): 10000 != %v\n", max)
|
||||
}
|
||||
if mean := s.Mean(); mean != 4965.98 {
|
||||
t.Errorf("s.Mean(): 4965.98 != %v\n", mean)
|
||||
}
|
||||
if stdDev := s.StdDev(); stdDev != 2959.825156930727 {
|
||||
t.Errorf("s.StdDev(): 2959.825156930727 != %v\n", stdDev)
|
||||
}
|
||||
ps := s.Percentiles([]float64{0.5, 0.75, 0.99})
|
||||
if ps[0] != 4615 {
|
||||
t.Errorf("median: 4615 != %v\n", ps[0])
|
||||
}
|
||||
if ps[1] != 7672 {
|
||||
t.Errorf("75th percentile: 7672 != %v\n", ps[1])
|
||||
}
|
||||
if ps[2] != 9998.99 {
|
||||
t.Errorf("99th percentile: 9998.99 != %v\n", ps[2])
|
||||
}
|
||||
}
|
||||
|
||||
func testUniformSampleStatistics(t *testing.T, s SampleSnapshot) {
|
||||
if count := s.Count(); count != 10000 {
|
||||
t.Errorf("s.Count(): 10000 != %v\n", count)
|
||||
}
|
||||
if min := s.Min(); min != 37 {
|
||||
t.Errorf("s.Min(): 37 != %v\n", min)
|
||||
}
|
||||
if max := s.Max(); max != 9989 {
|
||||
t.Errorf("s.Max(): 9989 != %v\n", max)
|
||||
}
|
||||
if mean := s.Mean(); mean != 4748.14 {
|
||||
t.Errorf("s.Mean(): 4748.14 != %v\n", mean)
|
||||
}
|
||||
if stdDev := s.StdDev(); stdDev != 2826.684117548333 {
|
||||
t.Errorf("s.StdDev(): 2826.684117548333 != %v\n", stdDev)
|
||||
}
|
||||
ps := s.Percentiles([]float64{0.5, 0.75, 0.99})
|
||||
if ps[0] != 4599 {
|
||||
t.Errorf("median: 4599 != %v\n", ps[0])
|
||||
}
|
||||
if ps[1] != 7380.5 {
|
||||
t.Errorf("75th percentile: 7380.5 != %v\n", ps[1])
|
||||
}
|
||||
if math.Abs(9986.429999999998-ps[2]) > epsilonPercentile {
|
||||
t.Errorf("99th percentile: 9986.429999999998 != %v\n", ps[2])
|
||||
}
|
||||
}
|
||||
|
||||
// TestUniformSampleConcurrentUpdateCount would expose data race problems with
|
||||
// concurrent Update and Count calls on Sample when test is called with -race
|
||||
// argument
|
||||
func TestUniformSampleConcurrentUpdateCount(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
s := NewUniformSample(100)
|
||||
for i := 0; i < 100; i++ {
|
||||
s.Update(int64(i))
|
||||
}
|
||||
quit := make(chan struct{})
|
||||
go func() {
|
||||
t := time.NewTicker(10 * time.Millisecond)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
s.Update(rand.Int63())
|
||||
case <-quit:
|
||||
t.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
for i := 0; i < 1000; i++ {
|
||||
s.Snapshot().Count()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
quit <- struct{}{}
|
||||
}
|
||||
|
||||
func BenchmarkCalculatePercentiles(b *testing.B) {
|
||||
pss := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
|
||||
var vals []int64
|
||||
for i := 0; i < 1000; i++ {
|
||||
vals = append(vals, int64(rand.Int31()))
|
||||
}
|
||||
v := make([]int64, len(vals))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
copy(v, vals)
|
||||
_ = CalculatePercentiles(v, pss)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Output each metric in the given registry to syslog periodically using
|
||||
// the given syslogger.
|
||||
func Syslog(r Registry, d time.Duration, w *syslog.Writer) {
|
||||
for range time.Tick(d) {
|
||||
r.Each(func(name string, i interface{}) {
|
||||
switch metric := i.(type) {
|
||||
case Counter:
|
||||
w.Info(fmt.Sprintf("counter %s: count: %d", name, metric.Snapshot().Count()))
|
||||
case CounterFloat64:
|
||||
w.Info(fmt.Sprintf("counter %s: count: %f", name, metric.Snapshot().Count()))
|
||||
case Gauge:
|
||||
w.Info(fmt.Sprintf("gauge %s: value: %d", name, metric.Snapshot().Value()))
|
||||
case GaugeFloat64:
|
||||
w.Info(fmt.Sprintf("gauge %s: value: %f", name, metric.Snapshot().Value()))
|
||||
case GaugeInfo:
|
||||
w.Info(fmt.Sprintf("gauge %s: value: %s", name, metric.Snapshot().Value()))
|
||||
case Healthcheck:
|
||||
metric.Check()
|
||||
w.Info(fmt.Sprintf("healthcheck %s: error: %v", name, metric.Error()))
|
||||
case Histogram:
|
||||
h := metric.Snapshot()
|
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
w.Info(fmt.Sprintf(
|
||||
"histogram %s: count: %d min: %d max: %d mean: %.2f stddev: %.2f median: %.2f 75%%: %.2f 95%%: %.2f 99%%: %.2f 99.9%%: %.2f",
|
||||
name,
|
||||
h.Count(),
|
||||
h.Min(),
|
||||
h.Max(),
|
||||
h.Mean(),
|
||||
h.StdDev(),
|
||||
ps[0],
|
||||
ps[1],
|
||||
ps[2],
|
||||
ps[3],
|
||||
ps[4],
|
||||
))
|
||||
case Meter:
|
||||
m := metric.Snapshot()
|
||||
w.Info(fmt.Sprintf(
|
||||
"meter %s: count: %d 1-min: %.2f 5-min: %.2f 15-min: %.2f mean: %.2f",
|
||||
name,
|
||||
m.Count(),
|
||||
m.Rate1(),
|
||||
m.Rate5(),
|
||||
m.Rate15(),
|
||||
m.RateMean(),
|
||||
))
|
||||
case Timer:
|
||||
t := metric.Snapshot()
|
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
w.Info(fmt.Sprintf(
|
||||
"timer %s: count: %d min: %d max: %d mean: %.2f stddev: %.2f median: %.2f 75%%: %.2f 95%%: %.2f 99%%: %.2f 99.9%%: %.2f 1-min: %.2f 5-min: %.2f 15-min: %.2f mean-rate: %.2f",
|
||||
name,
|
||||
t.Count(),
|
||||
t.Min(),
|
||||
t.Max(),
|
||||
t.Mean(),
|
||||
t.StdDev(),
|
||||
ps[0],
|
||||
ps[1],
|
||||
ps[2],
|
||||
ps[3],
|
||||
ps[4],
|
||||
t.Rate1(),
|
||||
t.Rate5(),
|
||||
t.Rate15(),
|
||||
t.RateMean(),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
23
metrics/testdata/opentsb.want
vendored
23
metrics/testdata/opentsb.want
vendored
|
|
@ -1,23 +0,0 @@
|
|||
put pre.elite.count 978307200 1337 host=hal9000
|
||||
put pre.elite.one-minute 978307200 0.00 host=hal9000
|
||||
put pre.elite.five-minute 978307200 0.00 host=hal9000
|
||||
put pre.elite.fifteen-minute 978307200 0.00 host=hal9000
|
||||
put pre.elite.mean 978307200 0.00 host=hal9000
|
||||
put pre.foo.value 978307200 {"chain_id":"5"} host=hal9000
|
||||
put pre.months.count 978307200 12 host=hal9000
|
||||
put pre.pi.value 978307200 3.140000 host=hal9000
|
||||
put pre.second.count 978307200 1 host=hal9000
|
||||
put pre.second.min 978307200 1000 host=hal9000
|
||||
put pre.second.max 978307200 1000 host=hal9000
|
||||
put pre.second.mean 978307200 1000.00 host=hal9000
|
||||
put pre.second.std-dev 978307200 0.00 host=hal9000
|
||||
put pre.second.50-percentile 978307200 1000.00 host=hal9000
|
||||
put pre.second.75-percentile 978307200 1000.00 host=hal9000
|
||||
put pre.second.95-percentile 978307200 1000.00 host=hal9000
|
||||
put pre.second.99-percentile 978307200 1000.00 host=hal9000
|
||||
put pre.second.999-percentile 978307200 1000.00 host=hal9000
|
||||
put pre.second.one-minute 978307200 0.00 host=hal9000
|
||||
put pre.second.five-minute 978307200 0.00 host=hal9000
|
||||
put pre.second.fifteen-minute 978307200 0.00 host=hal9000
|
||||
put pre.second.mean-rate 978307200 0.00 host=hal9000
|
||||
put pre.tau.count 978307200 1.570000 host=hal9000
|
||||
182
metrics/timer.go
182
metrics/timer.go
|
|
@ -1,182 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TimerSnapshot interface {
|
||||
HistogramSnapshot
|
||||
MeterSnapshot
|
||||
}
|
||||
|
||||
// Timers capture the duration and rate of events.
|
||||
type Timer interface {
|
||||
Snapshot() TimerSnapshot
|
||||
Stop()
|
||||
Time(func())
|
||||
UpdateSince(time.Time)
|
||||
Update(time.Duration)
|
||||
}
|
||||
|
||||
// GetOrRegisterTimer returns an existing Timer or constructs and registers a
|
||||
// new StandardTimer.
|
||||
// Be sure to unregister the meter from the registry once it is of no use to
|
||||
// allow for garbage collection.
|
||||
func GetOrRegisterTimer(name string, r Registry) Timer {
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
return r.GetOrRegister(name, NewTimer).(Timer)
|
||||
}
|
||||
|
||||
// NewCustomTimer constructs a new StandardTimer from a Histogram and a Meter.
|
||||
// Be sure to call Stop() once the timer is of no use to allow for garbage collection.
|
||||
func NewCustomTimer(h Histogram, m Meter) Timer {
|
||||
if !Enabled {
|
||||
return NilTimer{}
|
||||
}
|
||||
return &StandardTimer{
|
||||
histogram: h,
|
||||
meter: m,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRegisteredTimer constructs and registers a new StandardTimer.
|
||||
// Be sure to unregister the meter from the registry once it is of no use to
|
||||
// allow for garbage collection.
|
||||
func NewRegisteredTimer(name string, r Registry) Timer {
|
||||
c := NewTimer()
|
||||
if nil == r {
|
||||
r = DefaultRegistry
|
||||
}
|
||||
r.Register(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// NewTimer constructs a new StandardTimer using an exponentially-decaying
|
||||
// sample with the same reservoir size and alpha as UNIX load averages.
|
||||
// Be sure to call Stop() once the timer is of no use to allow for garbage collection.
|
||||
func NewTimer() Timer {
|
||||
if !Enabled {
|
||||
return NilTimer{}
|
||||
}
|
||||
return &StandardTimer{
|
||||
histogram: NewHistogram(NewExpDecaySample(1028, 0.015)),
|
||||
meter: NewMeter(),
|
||||
}
|
||||
}
|
||||
|
||||
// NilTimer is a no-op Timer.
|
||||
type NilTimer struct{}
|
||||
|
||||
func (NilTimer) Snapshot() TimerSnapshot { return (*emptySnapshot)(nil) }
|
||||
func (NilTimer) Stop() {}
|
||||
func (NilTimer) Time(f func()) { f() }
|
||||
func (NilTimer) Update(time.Duration) {}
|
||||
func (NilTimer) UpdateSince(time.Time) {}
|
||||
|
||||
// StandardTimer is the standard implementation of a Timer and uses a Histogram
|
||||
// and Meter.
|
||||
type StandardTimer struct {
|
||||
histogram Histogram
|
||||
meter Meter
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the timer.
|
||||
func (t *StandardTimer) Snapshot() TimerSnapshot {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
return &timerSnapshot{
|
||||
histogram: t.histogram.Snapshot(),
|
||||
meter: t.meter.Snapshot(),
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the meter.
|
||||
func (t *StandardTimer) Stop() {
|
||||
t.meter.Stop()
|
||||
}
|
||||
|
||||
// Record the duration of the execution of the given function.
|
||||
func (t *StandardTimer) Time(f func()) {
|
||||
ts := time.Now()
|
||||
f()
|
||||
t.Update(time.Since(ts))
|
||||
}
|
||||
|
||||
// Record the duration of an event, in nanoseconds.
|
||||
func (t *StandardTimer) Update(d time.Duration) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
t.histogram.Update(d.Nanoseconds())
|
||||
t.meter.Mark(1)
|
||||
}
|
||||
|
||||
// Record the duration of an event that started at a time and ends now.
|
||||
// The record uses nanoseconds.
|
||||
func (t *StandardTimer) UpdateSince(ts time.Time) {
|
||||
t.Update(time.Since(ts))
|
||||
}
|
||||
|
||||
// timerSnapshot is a read-only copy of another Timer.
|
||||
type timerSnapshot struct {
|
||||
histogram HistogramSnapshot
|
||||
meter MeterSnapshot
|
||||
}
|
||||
|
||||
// Count returns the number of events recorded at the time the snapshot was
|
||||
// taken.
|
||||
func (t *timerSnapshot) Count() int64 { return t.histogram.Count() }
|
||||
|
||||
// Max returns the maximum value at the time the snapshot was taken.
|
||||
func (t *timerSnapshot) Max() int64 { return t.histogram.Max() }
|
||||
|
||||
// Size returns the size of the sample at the time the snapshot was taken.
|
||||
func (t *timerSnapshot) Size() int { return t.histogram.Size() }
|
||||
|
||||
// Mean returns the mean value at the time the snapshot was taken.
|
||||
func (t *timerSnapshot) Mean() float64 { return t.histogram.Mean() }
|
||||
|
||||
// Min returns the minimum value at the time the snapshot was taken.
|
||||
func (t *timerSnapshot) Min() int64 { return t.histogram.Min() }
|
||||
|
||||
// Percentile returns an arbitrary percentile of sampled values at the time the
|
||||
// snapshot was taken.
|
||||
func (t *timerSnapshot) Percentile(p float64) float64 {
|
||||
return t.histogram.Percentile(p)
|
||||
}
|
||||
|
||||
// Percentiles returns a slice of arbitrary percentiles of sampled values at
|
||||
// the time the snapshot was taken.
|
||||
func (t *timerSnapshot) Percentiles(ps []float64) []float64 {
|
||||
return t.histogram.Percentiles(ps)
|
||||
}
|
||||
|
||||
// Rate1 returns the one-minute moving average rate of events per second at the
|
||||
// time the snapshot was taken.
|
||||
func (t *timerSnapshot) Rate1() float64 { return t.meter.Rate1() }
|
||||
|
||||
// Rate5 returns the five-minute moving average rate of events per second at
|
||||
// the time the snapshot was taken.
|
||||
func (t *timerSnapshot) Rate5() float64 { return t.meter.Rate5() }
|
||||
|
||||
// Rate15 returns the fifteen-minute moving average rate of events per second
|
||||
// at the time the snapshot was taken.
|
||||
func (t *timerSnapshot) Rate15() float64 { return t.meter.Rate15() }
|
||||
|
||||
// RateMean returns the meter's mean rate of events per second at the time the
|
||||
// snapshot was taken.
|
||||
func (t *timerSnapshot) RateMean() float64 { return t.meter.RateMean() }
|
||||
|
||||
// StdDev returns the standard deviation of the values at the time the snapshot
|
||||
// was taken.
|
||||
func (t *timerSnapshot) StdDev() float64 { return t.histogram.StdDev() }
|
||||
|
||||
// Sum returns the sum at the time the snapshot was taken.
|
||||
func (t *timerSnapshot) Sum() int64 { return t.histogram.Sum() }
|
||||
|
||||
// Variance returns the variance of the values at the time the snapshot was
|
||||
// taken.
|
||||
func (t *timerSnapshot) Variance() float64 { return t.histogram.Variance() }
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkTimer(b *testing.B) {
|
||||
tm := NewTimer()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tm.Update(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrRegisterTimer(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
NewRegisteredTimer("foo", r).Update(47)
|
||||
if tm := GetOrRegisterTimer("foo", r).Snapshot(); tm.Count() != 1 {
|
||||
t.Fatal(tm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimerExtremes(t *testing.T) {
|
||||
tm := NewTimer()
|
||||
tm.Update(math.MaxInt64)
|
||||
tm.Update(0)
|
||||
if stdDev := tm.Snapshot().StdDev(); stdDev != 4.611686018427388e+18 {
|
||||
t.Errorf("tm.StdDev(): 4.611686018427388e+18 != %v\n", stdDev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimerStop(t *testing.T) {
|
||||
l := len(arbiter.meters)
|
||||
tm := NewTimer()
|
||||
if l+1 != len(arbiter.meters) {
|
||||
t.Errorf("arbiter.meters: %d != %d\n", l+1, len(arbiter.meters))
|
||||
}
|
||||
tm.Stop()
|
||||
if l != len(arbiter.meters) {
|
||||
t.Errorf("arbiter.meters: %d != %d\n", l, len(arbiter.meters))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimerFunc(t *testing.T) {
|
||||
var (
|
||||
tm = NewTimer()
|
||||
testStart = time.Now()
|
||||
actualTime time.Duration
|
||||
)
|
||||
tm.Time(func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
actualTime = time.Since(testStart)
|
||||
})
|
||||
var (
|
||||
drift = time.Millisecond * 2
|
||||
measured = time.Duration(tm.Snapshot().Max())
|
||||
ceil = actualTime + drift
|
||||
floor = actualTime - drift
|
||||
)
|
||||
if measured > ceil || measured < floor {
|
||||
t.Errorf("tm.Max(): %v > %v || %v > %v\n", measured, ceil, measured, floor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimerZero(t *testing.T) {
|
||||
tm := NewTimer().Snapshot()
|
||||
if count := tm.Count(); count != 0 {
|
||||
t.Errorf("tm.Count(): 0 != %v\n", count)
|
||||
}
|
||||
if min := tm.Min(); min != 0 {
|
||||
t.Errorf("tm.Min(): 0 != %v\n", min)
|
||||
}
|
||||
if max := tm.Max(); max != 0 {
|
||||
t.Errorf("tm.Max(): 0 != %v\n", max)
|
||||
}
|
||||
if mean := tm.Mean(); mean != 0.0 {
|
||||
t.Errorf("tm.Mean(): 0.0 != %v\n", mean)
|
||||
}
|
||||
if stdDev := tm.StdDev(); stdDev != 0.0 {
|
||||
t.Errorf("tm.StdDev(): 0.0 != %v\n", stdDev)
|
||||
}
|
||||
ps := tm.Percentiles([]float64{0.5, 0.75, 0.99})
|
||||
if ps[0] != 0.0 {
|
||||
t.Errorf("median: 0.0 != %v\n", ps[0])
|
||||
}
|
||||
if ps[1] != 0.0 {
|
||||
t.Errorf("75th percentile: 0.0 != %v\n", ps[1])
|
||||
}
|
||||
if ps[2] != 0.0 {
|
||||
t.Errorf("99th percentile: 0.0 != %v\n", ps[2])
|
||||
}
|
||||
if rate1 := tm.Rate1(); rate1 != 0.0 {
|
||||
t.Errorf("tm.Rate1(): 0.0 != %v\n", rate1)
|
||||
}
|
||||
if rate5 := tm.Rate5(); rate5 != 0.0 {
|
||||
t.Errorf("tm.Rate5(): 0.0 != %v\n", rate5)
|
||||
}
|
||||
if rate15 := tm.Rate15(); rate15 != 0.0 {
|
||||
t.Errorf("tm.Rate15(): 0.0 != %v\n", rate15)
|
||||
}
|
||||
if rateMean := tm.RateMean(); rateMean != 0.0 {
|
||||
t.Errorf("tm.RateMean(): 0.0 != %v\n", rateMean)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleGetOrRegisterTimer() {
|
||||
m := "account.create.latency"
|
||||
t := GetOrRegisterTimer(m, nil)
|
||||
t.Update(47)
|
||||
fmt.Println(t.Snapshot().Max()) // Output: 47
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# check there are no formatting issues
|
||||
GOFMT_LINES=`gofmt -l . | wc -l | xargs`
|
||||
test $GOFMT_LINES -eq 0 || echo "gofmt needs to be run, ${GOFMT_LINES} files have issues"
|
||||
|
||||
# run the tests for the root package
|
||||
go test -race .
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Write sorts writes each metric in the given registry periodically to the
|
||||
// given io.Writer.
|
||||
func Write(r Registry, d time.Duration, w io.Writer) {
|
||||
for range time.Tick(d) {
|
||||
WriteOnce(r, w)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteOnce sorts and writes metrics in the given registry to the given
|
||||
// io.Writer.
|
||||
func WriteOnce(r Registry, w io.Writer) {
|
||||
var namedMetrics []namedMetric
|
||||
r.Each(func(name string, i interface{}) {
|
||||
namedMetrics = append(namedMetrics, namedMetric{name, i})
|
||||
})
|
||||
slices.SortFunc(namedMetrics, namedMetric.cmp)
|
||||
for _, namedMetric := range namedMetrics {
|
||||
switch metric := namedMetric.m.(type) {
|
||||
case Counter:
|
||||
fmt.Fprintf(w, "counter %s\n", namedMetric.name)
|
||||
fmt.Fprintf(w, " count: %9d\n", metric.Snapshot().Count())
|
||||
case CounterFloat64:
|
||||
fmt.Fprintf(w, "counter %s\n", namedMetric.name)
|
||||
fmt.Fprintf(w, " count: %f\n", metric.Snapshot().Count())
|
||||
case Gauge:
|
||||
fmt.Fprintf(w, "gauge %s\n", namedMetric.name)
|
||||
fmt.Fprintf(w, " value: %9d\n", metric.Snapshot().Value())
|
||||
case GaugeFloat64:
|
||||
fmt.Fprintf(w, "gauge %s\n", namedMetric.name)
|
||||
fmt.Fprintf(w, " value: %f\n", metric.Snapshot().Value())
|
||||
case GaugeInfo:
|
||||
fmt.Fprintf(w, "gauge %s\n", namedMetric.name)
|
||||
fmt.Fprintf(w, " value: %s\n", metric.Snapshot().Value().String())
|
||||
case Healthcheck:
|
||||
metric.Check()
|
||||
fmt.Fprintf(w, "healthcheck %s\n", namedMetric.name)
|
||||
fmt.Fprintf(w, " error: %v\n", metric.Error())
|
||||
case Histogram:
|
||||
h := metric.Snapshot()
|
||||
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
fmt.Fprintf(w, "histogram %s\n", namedMetric.name)
|
||||
fmt.Fprintf(w, " count: %9d\n", h.Count())
|
||||
fmt.Fprintf(w, " min: %9d\n", h.Min())
|
||||
fmt.Fprintf(w, " max: %9d\n", h.Max())
|
||||
fmt.Fprintf(w, " mean: %12.2f\n", h.Mean())
|
||||
fmt.Fprintf(w, " stddev: %12.2f\n", h.StdDev())
|
||||
fmt.Fprintf(w, " median: %12.2f\n", ps[0])
|
||||
fmt.Fprintf(w, " 75%%: %12.2f\n", ps[1])
|
||||
fmt.Fprintf(w, " 95%%: %12.2f\n", ps[2])
|
||||
fmt.Fprintf(w, " 99%%: %12.2f\n", ps[3])
|
||||
fmt.Fprintf(w, " 99.9%%: %12.2f\n", ps[4])
|
||||
case Meter:
|
||||
m := metric.Snapshot()
|
||||
fmt.Fprintf(w, "meter %s\n", namedMetric.name)
|
||||
fmt.Fprintf(w, " count: %9d\n", m.Count())
|
||||
fmt.Fprintf(w, " 1-min rate: %12.2f\n", m.Rate1())
|
||||
fmt.Fprintf(w, " 5-min rate: %12.2f\n", m.Rate5())
|
||||
fmt.Fprintf(w, " 15-min rate: %12.2f\n", m.Rate15())
|
||||
fmt.Fprintf(w, " mean rate: %12.2f\n", m.RateMean())
|
||||
case Timer:
|
||||
t := metric.Snapshot()
|
||||
ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
|
||||
fmt.Fprintf(w, "timer %s\n", namedMetric.name)
|
||||
fmt.Fprintf(w, " count: %9d\n", t.Count())
|
||||
fmt.Fprintf(w, " min: %9d\n", t.Min())
|
||||
fmt.Fprintf(w, " max: %9d\n", t.Max())
|
||||
fmt.Fprintf(w, " mean: %12.2f\n", t.Mean())
|
||||
fmt.Fprintf(w, " stddev: %12.2f\n", t.StdDev())
|
||||
fmt.Fprintf(w, " median: %12.2f\n", ps[0])
|
||||
fmt.Fprintf(w, " 75%%: %12.2f\n", ps[1])
|
||||
fmt.Fprintf(w, " 95%%: %12.2f\n", ps[2])
|
||||
fmt.Fprintf(w, " 99%%: %12.2f\n", ps[3])
|
||||
fmt.Fprintf(w, " 99.9%%: %12.2f\n", ps[4])
|
||||
fmt.Fprintf(w, " 1-min rate: %12.2f\n", t.Rate1())
|
||||
fmt.Fprintf(w, " 5-min rate: %12.2f\n", t.Rate5())
|
||||
fmt.Fprintf(w, " 15-min rate: %12.2f\n", t.Rate15())
|
||||
fmt.Fprintf(w, " mean rate: %12.2f\n", t.RateMean())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type namedMetric struct {
|
||||
name string
|
||||
m interface{}
|
||||
}
|
||||
|
||||
func (m namedMetric) cmp(other namedMetric) int {
|
||||
return strings.Compare(m.name, other.name)
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func TestMetricsSorting(t *testing.T) {
|
||||
var namedMetrics = []namedMetric{
|
||||
{name: "zzz"},
|
||||
{name: "bbb"},
|
||||
{name: "fff"},
|
||||
{name: "ggg"},
|
||||
}
|
||||
|
||||
slices.SortFunc(namedMetrics, namedMetric.cmp)
|
||||
for i, name := range []string{"bbb", "fff", "ggg", "zzz"} {
|
||||
if namedMetrics[i].name != name {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue