Skip to content

Commit 53f3c2a

Browse files
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 <[email protected]>
1 parent 5b15949 commit 53f3c2a

26 files changed

+750
-143
lines changed

cmd/geth/config.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ import (
2222
"fmt"
2323
"os"
2424
"reflect"
25+
"runtime"
26+
"strings"
2527
"unicode"
2628

27-
"github.com/urfave/cli/v2"
28-
2929
"github.com/ethereum/go-ethereum/accounts"
3030
"github.com/ethereum/go-ethereum/accounts/external"
3131
"github.com/ethereum/go-ethereum/accounts/keystore"
@@ -43,6 +43,7 @@ import (
4343
"github.com/ethereum/go-ethereum/node"
4444
"github.com/ethereum/go-ethereum/params"
4545
"github.com/naoina/toml"
46+
"github.com/urfave/cli/v2"
4647
)
4748

4849
var (
@@ -177,6 +178,20 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
177178
}
178179
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
179180

181+
// Create gauge with geth system and build information
182+
if eth != nil { // The 'eth' backend may be nil in light mode
183+
var protos []string
184+
for _, p := range eth.Protocols() {
185+
protos = append(protos, fmt.Sprintf("%v/%d", p.Name, p.Version))
186+
}
187+
metrics.NewRegisteredGaugeInfo("geth/info", nil).Update(metrics.GaugeInfoValue{
188+
"arch": runtime.GOARCH,
189+
"os": runtime.GOOS,
190+
"version": cfg.Node.Version,
191+
"eth_protocols": strings.Join(protos, ","),
192+
})
193+
}
194+
180195
// Configure log filter RPC API.
181196
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
182197

core/blockchain.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ var (
6060
headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil)
6161
headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil)
6262

63+
chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil)
64+
6365
accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil)
6466
accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil)
6567
accountUpdateTimer = metrics.NewRegisteredTimer("chain/account/updates", nil)
@@ -322,6 +324,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
322324
bc.currentFinalBlock.Store(nil)
323325
bc.currentSafeBlock.Store(nil)
324326

327+
// Update chain info data metrics
328+
chainInfoGauge.Update(metrics.GaugeInfoValue{"chain_id": bc.chainConfig.ChainID.String()})
329+
325330
// If Geth is initialized with an external ancient store, re-initialize the
326331
// missing chain indexes and chain flags. This procedure can survive crash
327332
// and can be resumed in next restart since chain flags are updated in last step.

metrics/exp/exp.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ func (exp *exp) getFloat(name string) *expvar.Float {
9595
return v
9696
}
9797

98+
func (exp *exp) getInfo(name string) *expvar.String {
99+
var v *expvar.String
100+
exp.expvarLock.Lock()
101+
p := expvar.Get(name)
102+
if p != nil {
103+
v = p.(*expvar.String)
104+
} else {
105+
v = new(expvar.String)
106+
expvar.Publish(name, v)
107+
}
108+
exp.expvarLock.Unlock()
109+
return v
110+
}
111+
98112
func (exp *exp) publishCounter(name string, metric metrics.Counter) {
99113
v := exp.getInt(name)
100114
v.Set(metric.Count())
@@ -113,6 +127,10 @@ func (exp *exp) publishGaugeFloat64(name string, metric metrics.GaugeFloat64) {
113127
exp.getFloat(name).Set(metric.Value())
114128
}
115129

130+
func (exp *exp) publishGaugeInfo(name string, metric metrics.GaugeInfo) {
131+
exp.getInfo(name).Set(metric.Value().String())
132+
}
133+
116134
func (exp *exp) publishHistogram(name string, metric metrics.Histogram) {
117135
h := metric.Snapshot()
118136
ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
@@ -178,6 +196,8 @@ func (exp *exp) syncToExpvar() {
178196
exp.publishGauge(name, i)
179197
case metrics.GaugeFloat64:
180198
exp.publishGaugeFloat64(name, i)
199+
case metrics.GaugeInfo:
200+
exp.publishGaugeInfo(name, i)
181201
case metrics.Histogram:
182202
exp.publishHistogram(name, i)
183203
case metrics.Meter:

metrics/gauge_info.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package metrics
2+
3+
import (
4+
"encoding/json"
5+
"sync"
6+
)
7+
8+
// GaugeInfos hold a GaugeInfoValue value that can be set arbitrarily.
9+
type GaugeInfo interface {
10+
Snapshot() GaugeInfo
11+
Update(GaugeInfoValue)
12+
Value() GaugeInfoValue
13+
}
14+
15+
// GaugeInfoValue is a mappng of (string) keys to (string) values
16+
type GaugeInfoValue map[string]string
17+
18+
func (val GaugeInfoValue) String() string {
19+
data, _ := json.Marshal(val)
20+
return string(data)
21+
}
22+
23+
// GetOrRegisterGaugeInfo returns an existing GaugeInfo or constructs and registers a
24+
// new StandardGaugeInfo.
25+
func GetOrRegisterGaugeInfo(name string, r Registry) GaugeInfo {
26+
if nil == r {
27+
r = DefaultRegistry
28+
}
29+
return r.GetOrRegister(name, NewGaugeInfo()).(GaugeInfo)
30+
}
31+
32+
// NewGaugeInfo constructs a new StandardGaugeInfo.
33+
func NewGaugeInfo() GaugeInfo {
34+
if !Enabled {
35+
return NilGaugeInfo{}
36+
}
37+
return &StandardGaugeInfo{
38+
value: GaugeInfoValue{},
39+
}
40+
}
41+
42+
// NewRegisteredGaugeInfo constructs and registers a new StandardGaugeInfo.
43+
func NewRegisteredGaugeInfo(name string, r Registry) GaugeInfo {
44+
c := NewGaugeInfo()
45+
if nil == r {
46+
r = DefaultRegistry
47+
}
48+
r.Register(name, c)
49+
return c
50+
}
51+
52+
// NewFunctionalGauge constructs a new FunctionalGauge.
53+
func NewFunctionalGaugeInfo(f func() GaugeInfoValue) GaugeInfo {
54+
if !Enabled {
55+
return NilGaugeInfo{}
56+
}
57+
return &FunctionalGaugeInfo{value: f}
58+
}
59+
60+
// NewRegisteredFunctionalGauge constructs and registers a new StandardGauge.
61+
func NewRegisteredFunctionalGaugeInfo(name string, r Registry, f func() GaugeInfoValue) GaugeInfo {
62+
c := NewFunctionalGaugeInfo(f)
63+
if nil == r {
64+
r = DefaultRegistry
65+
}
66+
r.Register(name, c)
67+
return c
68+
}
69+
70+
// GaugeInfoSnapshot is a read-only copy of another GaugeInfo.
71+
type GaugeInfoSnapshot GaugeInfoValue
72+
73+
// Snapshot returns the snapshot.
74+
func (g GaugeInfoSnapshot) Snapshot() GaugeInfo { return g }
75+
76+
// Update panics.
77+
func (GaugeInfoSnapshot) Update(GaugeInfoValue) {
78+
panic("Update called on a GaugeInfoSnapshot")
79+
}
80+
81+
// Value returns the value at the time the snapshot was taken.
82+
func (g GaugeInfoSnapshot) Value() GaugeInfoValue { return GaugeInfoValue(g) }
83+
84+
// NilGauge is a no-op Gauge.
85+
type NilGaugeInfo struct{}
86+
87+
// Snapshot is a no-op.
88+
func (NilGaugeInfo) Snapshot() GaugeInfo { return NilGaugeInfo{} }
89+
90+
// Update is a no-op.
91+
func (NilGaugeInfo) Update(v GaugeInfoValue) {}
92+
93+
// Value is a no-op.
94+
func (NilGaugeInfo) Value() GaugeInfoValue { return GaugeInfoValue{} }
95+
96+
// StandardGaugeInfo is the standard implementation of a GaugeInfo and uses
97+
// sync.Mutex to manage a single string value.
98+
type StandardGaugeInfo struct {
99+
mutex sync.Mutex
100+
value GaugeInfoValue
101+
}
102+
103+
// Snapshot returns a read-only copy of the gauge.
104+
func (g *StandardGaugeInfo) Snapshot() GaugeInfo {
105+
return GaugeInfoSnapshot(g.Value())
106+
}
107+
108+
// Update updates the gauge's value.
109+
func (g *StandardGaugeInfo) Update(v GaugeInfoValue) {
110+
g.mutex.Lock()
111+
defer g.mutex.Unlock()
112+
g.value = v
113+
}
114+
115+
// Value returns the gauge's current value.
116+
func (g *StandardGaugeInfo) Value() GaugeInfoValue {
117+
g.mutex.Lock()
118+
defer g.mutex.Unlock()
119+
return g.value
120+
}
121+
122+
// FunctionalGaugeInfo returns value from given function
123+
type FunctionalGaugeInfo struct {
124+
value func() GaugeInfoValue
125+
}
126+
127+
// Value returns the gauge's current value.
128+
func (g FunctionalGaugeInfo) Value() GaugeInfoValue {
129+
return g.value()
130+
}
131+
132+
// Value returns the gauge's current value in JSON string format
133+
func (g FunctionalGaugeInfo) ValueJsonString() string {
134+
data, _ := json.Marshal(g.value())
135+
return string(data)
136+
}
137+
138+
// Snapshot returns the snapshot.
139+
func (g FunctionalGaugeInfo) Snapshot() GaugeInfo { return GaugeInfoSnapshot(g.Value()) }
140+
141+
// Update panics.
142+
func (FunctionalGaugeInfo) Update(GaugeInfoValue) {
143+
panic("Update called on a FunctionalGaugeInfo")
144+
}

metrics/gauge_info_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package metrics
2+
3+
import (
4+
"strconv"
5+
"testing"
6+
)
7+
8+
func TestGaugeInfoJsonString(t *testing.T) {
9+
g := NewGaugeInfo()
10+
g.Update(GaugeInfoValue{
11+
"chain_id": "5",
12+
"anotherKey": "any_string_value",
13+
"third_key": "anything",
14+
},
15+
)
16+
want := `{"anotherKey":"any_string_value","chain_id":"5","third_key":"anything"}`
17+
if have := g.Value().String(); have != want {
18+
t.Errorf("\nhave: %v\nwant: %v\n", have, want)
19+
}
20+
}
21+
22+
func TestGaugeInfoSnapshot(t *testing.T) {
23+
g := NewGaugeInfo()
24+
g.Update(GaugeInfoValue{"value": "original"})
25+
snapshot := g.Snapshot() // Snapshot @chainid 5
26+
g.Update(GaugeInfoValue{"value": "updated"})
27+
// The 'g' should be updated
28+
if have, want := g.Value().String(), `{"value":"updated"}`; have != want {
29+
t.Errorf("\nhave: %v\nwant: %v\n", have, want)
30+
}
31+
// Snapshot should be unupdated
32+
if have, want := snapshot.Value().String(), `{"value":"original"}`; have != want {
33+
t.Errorf("\nhave: %v\nwant: %v\n", have, want)
34+
}
35+
}
36+
37+
func TestGetOrRegisterGaugeInfo(t *testing.T) {
38+
r := NewRegistry()
39+
NewRegisteredGaugeInfo("foo", r).Update(
40+
GaugeInfoValue{"chain_id": "5"})
41+
g := GetOrRegisterGaugeInfo("foo", r)
42+
if have, want := g.Value().String(), `{"chain_id":"5"}`; have != want {
43+
t.Errorf("have\n%v\nwant\n%v\n", have, want)
44+
}
45+
}
46+
47+
func TestFunctionalGaugeInfo(t *testing.T) {
48+
info := GaugeInfoValue{"chain_id": "0"}
49+
counter := 1
50+
// A "functional" gauge invokes the method to obtain the value
51+
fg := NewFunctionalGaugeInfo(func() GaugeInfoValue {
52+
info["chain_id"] = strconv.Itoa(counter)
53+
counter++
54+
return info
55+
})
56+
fg.Value()
57+
fg.Value()
58+
if have, want := info["chain_id"], "2"; have != want {
59+
t.Errorf("have %v want %v", have, want)
60+
}
61+
}
62+
63+
func TestGetOrRegisterFunctionalGaugeInfo(t *testing.T) {
64+
r := NewRegistry()
65+
NewRegisteredFunctionalGaugeInfo("foo", r, func() GaugeInfoValue {
66+
return GaugeInfoValue{
67+
"chain_id": "5",
68+
}
69+
})
70+
want := `{"chain_id":"5"}`
71+
have := GetOrRegisterGaugeInfo("foo", r).Value().String()
72+
if have != want {
73+
t.Errorf("have\n%v\nwant\n%v\n", have, want)
74+
}
75+
}

metrics/graphite.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ func graphite(c *GraphiteConfig) error {
7373
fmt.Fprintf(w, "%s.%s.value %d %d\n", c.Prefix, name, metric.Value(), now)
7474
case GaugeFloat64:
7575
fmt.Fprintf(w, "%s.%s.value %f %d\n", c.Prefix, name, metric.Value(), now)
76+
case GaugeInfo:
77+
fmt.Fprintf(w, "%s.%s.value %s %d\n", c.Prefix, name, metric.Value().String(), now)
7678
case Histogram:
7779
h := metric.Snapshot()
7880
ps := h.Percentiles(c.Percentiles)

metrics/influxdb/influxdb.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ func readMeter(namespace, name string, i interface{}) (string, map[string]interf
3232
"value": metric.Snapshot().Value(),
3333
}
3434
return measurement, fields
35+
case metrics.GaugeInfo:
36+
ms := metric.Snapshot()
37+
measurement := fmt.Sprintf("%s%s.gauge", namespace, name)
38+
fields := map[string]interface{}{
39+
"value": ms.Value().String(),
40+
}
41+
return measurement, fields
3542
case metrics.Histogram:
3643
ms := metric.Snapshot()
3744
if ms.Count() <= 0 {

0 commit comments

Comments
 (0)