mirror of https://github.com/ethereum/go-ethereum
metrics, cmd/geth: informational metrics (prometheus, influxdb, opentsb) (#24877)
This chang creates a GaugeInfo metrics type for registering informational (textual) metrics, e.g. geth version number. It also improves the testing for backend-exporters, and uses a shared subpackage in 'internal' to provide sample datasets and ordered registry. Implements #21783 --------- Co-authored-by: Martin Holst Swende <martin@swende.se>pull/28031/head
parent
5b159498bb
commit
53f3c2ae65
@ -0,0 +1,144 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"sync" |
||||
) |
||||
|
||||
// GaugeInfos hold a GaugeInfoValue value that can be set arbitrarily.
|
||||
type GaugeInfo interface { |
||||
Snapshot() GaugeInfo |
||||
Update(GaugeInfoValue) |
||||
Value() GaugeInfoValue |
||||
} |
||||
|
||||
// GaugeInfoValue is a mappng of (string) keys to (string) 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 |
||||
} |
||||
|
||||
// NewFunctionalGauge constructs a new FunctionalGauge.
|
||||
func NewFunctionalGaugeInfo(f func() GaugeInfoValue) GaugeInfo { |
||||
if !Enabled { |
||||
return NilGaugeInfo{} |
||||
} |
||||
return &FunctionalGaugeInfo{value: f} |
||||
} |
||||
|
||||
// NewRegisteredFunctionalGauge constructs and registers a new StandardGauge.
|
||||
func NewRegisteredFunctionalGaugeInfo(name string, r Registry, f func() GaugeInfoValue) GaugeInfo { |
||||
c := NewFunctionalGaugeInfo(f) |
||||
if nil == r { |
||||
r = DefaultRegistry |
||||
} |
||||
r.Register(name, c) |
||||
return c |
||||
} |
||||
|
||||
// GaugeInfoSnapshot is a read-only copy of another GaugeInfo.
|
||||
type GaugeInfoSnapshot GaugeInfoValue |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (g GaugeInfoSnapshot) Snapshot() GaugeInfo { return g } |
||||
|
||||
// Update panics.
|
||||
func (GaugeInfoSnapshot) Update(GaugeInfoValue) { |
||||
panic("Update called on a GaugeInfoSnapshot") |
||||
} |
||||
|
||||
// Value returns the value at the time the snapshot was taken.
|
||||
func (g GaugeInfoSnapshot) Value() GaugeInfoValue { return GaugeInfoValue(g) } |
||||
|
||||
// NilGauge is a no-op Gauge.
|
||||
type NilGaugeInfo struct{} |
||||
|
||||
// Snapshot is a no-op.
|
||||
func (NilGaugeInfo) Snapshot() GaugeInfo { return NilGaugeInfo{} } |
||||
|
||||
// Update is a no-op.
|
||||
func (NilGaugeInfo) Update(v GaugeInfoValue) {} |
||||
|
||||
// Value is a no-op.
|
||||
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() GaugeInfo { |
||||
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 |
||||
} |
||||
|
||||
// Value returns the gauge's current value.
|
||||
func (g *StandardGaugeInfo) Value() GaugeInfoValue { |
||||
g.mutex.Lock() |
||||
defer g.mutex.Unlock() |
||||
return g.value |
||||
} |
||||
|
||||
// FunctionalGaugeInfo returns value from given function
|
||||
type FunctionalGaugeInfo struct { |
||||
value func() GaugeInfoValue |
||||
} |
||||
|
||||
// Value returns the gauge's current value.
|
||||
func (g FunctionalGaugeInfo) Value() GaugeInfoValue { |
||||
return g.value() |
||||
} |
||||
|
||||
// Value returns the gauge's current value in JSON string format
|
||||
func (g FunctionalGaugeInfo) ValueJsonString() string { |
||||
data, _ := json.Marshal(g.value()) |
||||
return string(data) |
||||
} |
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (g FunctionalGaugeInfo) Snapshot() GaugeInfo { return GaugeInfoSnapshot(g.Value()) } |
||||
|
||||
// Update panics.
|
||||
func (FunctionalGaugeInfo) Update(GaugeInfoValue) { |
||||
panic("Update called on a FunctionalGaugeInfo") |
||||
} |
@ -0,0 +1,75 @@ |
||||
package metrics |
||||
|
||||
import ( |
||||
"strconv" |
||||
"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"}` |
||||
if have := g.Value().String(); have != want { |
||||
t.Errorf("\nhave: %v\nwant: %v\n", have, want) |
||||
} |
||||
} |
||||
|
||||
func TestGaugeInfoSnapshot(t *testing.T) { |
||||
g := NewGaugeInfo() |
||||
g.Update(GaugeInfoValue{"value": "original"}) |
||||
snapshot := g.Snapshot() // Snapshot @chainid 5
|
||||
g.Update(GaugeInfoValue{"value": "updated"}) |
||||
// The 'g' should be updated
|
||||
if have, want := g.Value().String(), `{"value":"updated"}`; have != want { |
||||
t.Errorf("\nhave: %v\nwant: %v\n", have, want) |
||||
} |
||||
// Snapshot should be unupdated
|
||||
if have, want := snapshot.Value().String(), `{"value":"original"}`; 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) |
||||
if have, want := g.Value().String(), `{"chain_id":"5"}`; have != want { |
||||
t.Errorf("have\n%v\nwant\n%v\n", have, want) |
||||
} |
||||
} |
||||
|
||||
func TestFunctionalGaugeInfo(t *testing.T) { |
||||
info := GaugeInfoValue{"chain_id": "0"} |
||||
counter := 1 |
||||
// A "functional" gauge invokes the method to obtain the value
|
||||
fg := NewFunctionalGaugeInfo(func() GaugeInfoValue { |
||||
info["chain_id"] = strconv.Itoa(counter) |
||||
counter++ |
||||
return info |
||||
}) |
||||
fg.Value() |
||||
fg.Value() |
||||
if have, want := info["chain_id"], "2"; have != want { |
||||
t.Errorf("have %v want %v", have, want) |
||||
} |
||||
} |
||||
|
||||
func TestGetOrRegisterFunctionalGaugeInfo(t *testing.T) { |
||||
r := NewRegistry() |
||||
NewRegisteredFunctionalGaugeInfo("foo", r, func() GaugeInfoValue { |
||||
return GaugeInfoValue{ |
||||
"chain_id": "5", |
||||
} |
||||
}) |
||||
want := `{"chain_id":"5"}` |
||||
have := GetOrRegisterGaugeInfo("foo", r).Value().String() |
||||
if have != want { |
||||
t.Errorf("have\n%v\nwant\n%v\n", have, want) |
||||
} |
||||
} |
@ -0,0 +1,114 @@ |
||||
// 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 "" |
||||
} |
@ -0,0 +1,9 @@ |
||||
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=12000000i,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 |
@ -0,0 +1,9 @@ |
||||
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=12000000i,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 |
@ -0,0 +1,64 @@ |
||||
// 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 ( |
||||
"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", |
||||
}) |
||||
metrics.NewRegisteredHistogram("test/histogram", registry, metrics.NewSampleSnapshot(3, []int64{1, 2, 3})) |
||||
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()) |
||||
return registry |
||||
} |
@ -0,0 +1,48 @@ |
||||
# 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"} 12000000 |
||||
test_resetting_timer {quantile="0.95"} 120000000 |
||||
test_resetting_timer {quantile="0.99"} 120000000 |
||||
|
||||
# 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 |
||||
|
@ -0,0 +1,23 @@ |
||||
put pre.elite.count 978307200 0 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 |
Loading…
Reference in new issue